SEBSERV-238 improved proctoring room assignments for connecting clients

(re)mark a client connection for update if the join instruction was not
be able to send to the SEB client. In this case this shall be tried
again until it works.
This commit is contained in:
anhefti 2021-11-08 12:56:05 +01:00
parent c89a609615
commit 9ebd7827ac
6 changed files with 124 additions and 53 deletions

View file

@ -666,4 +666,12 @@ public final class Utils {
}
}
public static CharSequence trim(final CharSequence sequence) {
if (sequence == null) {
return null;
}
return StringUtils.trim(sequence.toString());
}
}

View file

@ -132,6 +132,8 @@ public interface ClientConnectionDAO extends
key = "#connectionToken")
Result<Void> removeFromProctoringRoom(Long connectionId, String connectionToken);
Result<Void> markForProctoringUpdate(Long id);
/** Deletes the given ClientConnection data.
*
* This evicts all entries from the CONNECTION_TOKENS_CACHE.

View file

@ -389,6 +389,19 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
});
}
@Override
@Transactional
public Result<Void> markForProctoringUpdate(final Long id) {
return Result.tryCatch(() -> {
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(new ClientConnectionRecord(
id,
null, null, null, null, null,
null, null, null, null, null, null,
null,
1));
});
}
@Override
@Transactional
public Result<Void> removeFromProctoringRoom(final Long connectionId, final String connectionToken) {

View file

@ -44,6 +44,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
@ -249,7 +250,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_SERVER_URL,
proctoringServiceSettings.serverURL);
StringUtils.trim(proctoringServiceSettings.serverURL));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
@ -261,13 +262,13 @@ public class ExamAdminServiceImpl implements ExamAdminService {
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_APP_KEY,
proctoringServiceSettings.appKey);
StringUtils.trim(proctoringServiceSettings.appKey));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_APP_SECRET,
this.cryptor.encrypt(proctoringServiceSettings.appSecret)
this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.appSecret))
.getOrThrow()
.toString());
@ -276,13 +277,13 @@ public class ExamAdminServiceImpl implements ExamAdminService {
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_SDK_KEY,
proctoringServiceSettings.sdkKey);
StringUtils.trim(proctoringServiceSettings.sdkKey));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_SDK_SECRET,
this.cryptor.encrypt(proctoringServiceSettings.sdkSecret)
this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.sdkSecret))
.getOrThrow()
.toString());
}
@ -309,7 +310,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_ENABLE_PROCTORING)
.map(rec -> BooleanUtils.toBoolean(rec.getValue()));
.map(rec -> BooleanUtils.toBoolean(rec.getValue()))
.onError(error -> log.error("Failed to verify proctoring enabled for exam: {}", examId, error));
if (result.hasError()) {
return Result.of(false);
}

View file

@ -216,7 +216,7 @@ public class SEBClientInstructionServiceImpl implements SEBClientInstructionServ
.toString();
if (log.isDebugEnabled()) {
log.debug("Send SEB client instruction: {} to: {} ", connectionToken, instructionJSON);
log.debug("Send SEB client instruction: {} to: {} ", instructionJSON, connectionToken);
}
return instructionJSON;

View file

@ -282,22 +282,29 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
private void assignToCollectingRoom(final ClientConnectionRecord cc) {
try {
final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
cc.getExamId(),
cc.getConnectionToken());
if (cc.getRemoteProctoringRoomId() == null) {
this.clientConnectionDAO
.assignToProctoringRoom(
cc.getId(),
cc.getConnectionToken(),
proctoringRoom.id)
final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
cc.getExamId(),
cc.getConnectionToken());
if (log.isDebugEnabled()) {
log.debug("Assigning new SEB client to proctoring room: {}, connection: {}",
proctoringRoom.id,
cc);
}
this.clientConnectionDAO
.assignToProctoringRoom(
cc.getId(),
cc.getConnectionToken(),
proctoringRoom.id)
.getOrThrow();
}
applyProcotringInstruction(cc)
.getOrThrow();
applyProcotringInstruction(
cc.getExamId(),
cc.getConnectionToken())
.getOrThrow();
} catch (final Exception e) {
log.error("Failed to assign connection to collecting room: {}", cc, e);
}
@ -598,11 +605,11 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
});
}
private Result<Void> applyProcotringInstruction(
final Long examId,
final String connectionToken) {
private Result<Void> applyProcotringInstruction(final ClientConnectionRecord cc) {
return Result.tryCatch(() -> {
final Long examId = cc.getExamId();
final String connectionToken = cc.getConnectionToken();
final ProctoringServiceSettings settings = this.examAdminService
.getProctoringServiceSettings(examId)
.getOrThrow();
@ -624,17 +631,32 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
townhallRoom.subject)
.getOrThrow();
sendJoinInstruction(
examId,
connectionToken,
roomConnection,
examProctoringService);
try {
sendJoinInstruction(
examId,
connectionToken,
roomConnection,
examProctoringService);
} catch (final Exception e) {
log.error("Failed to send join for town-hall room assignment to connection: {}", cc);
this.clientConnectionDAO
.markForProctoringUpdate(cc.getId())
.onError(error -> log.error("Failed to mark connection for proctoring update: ", error));
}
} else {
try {
sendJoinCollectingRoomInstructions(
settings,
Arrays.asList(connectionToken),
examProctoringService);
sendJoinCollectingRoomInstruction(
settings,
examProctoringService,
connectionToken);
} catch (final Exception e) {
log.error("Failed to send join for collecting room assignment to connection: {}", cc);
this.clientConnectionDAO
.markForProctoringUpdate(cc.getId())
.onError(error -> log.error("Failed to mark connection for proctoring update: ", error));
}
}
});
@ -653,23 +675,30 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
clientConnectionTokens
.stream()
.forEach(connectionToken -> {
final ProctoringRoomConnection proctoringConnection = examProctoringService
.getClientRoomConnection(
proctoringSettings,
try {
final ProctoringRoomConnection proctoringConnection = examProctoringService
.getClientRoomConnection(
proctoringSettings,
connectionToken,
roomName,
(StringUtils.isNotBlank(subject)) ? subject : roomName)
.onError(error -> log.error(
"Failed to get client room connection data for {} cause: {}",
connectionToken,
error.getMessage()))
.get();
if (proctoringConnection != null) {
sendJoinInstruction(
proctoringSettings.examId,
connectionToken,
roomName,
(StringUtils.isNotBlank(subject)) ? subject : roomName)
.onError(error -> log.error(
"Failed to get client room connection data for {} cause: {}",
connectionToken,
error.getMessage()))
.get();
if (proctoringConnection != null) {
sendJoinInstruction(
proctoringSettings.examId,
connectionToken,
proctoringConnection,
examProctoringService);
proctoringConnection,
examProctoringService);
}
} catch (final Exception e) {
log.error("Failed to send join to break-out room: {} connection: {}",
roomName,
roomName,
e);
}
});
@ -688,10 +717,18 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
clientConnectionTokens
.stream()
.forEach(connectionToken -> sendJoinCollectingRoomInstruction(
proctoringSettings,
examProctoringService,
connectionToken));
.forEach(connectionToken -> {
try {
sendJoinCollectingRoomInstruction(
proctoringSettings,
examProctoringService,
connectionToken);
} catch (final Exception e) {
log.error(
"Failed to send proctoring room join instruction to single client. Skip and proceed with other clients. {}",
e.getMessage());
}
});
}
private void sendJoinCollectingRoomInstruction(
@ -721,8 +758,10 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
clientConnection.clientConnection.connectionToken,
proctoringConnection,
examProctoringService);
} catch (final Exception e) {
log.error("Failed to send proctoring room join instruction to client: {}", connectionToken, e);
throw e;
}
}
@ -732,6 +771,12 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
final ProctoringRoomConnection proctoringConnection,
final ExamProctoringService examProctoringService) {
if (log.isDebugEnabled()) {
log.debug("Send proctoring join instruction to connection: {}, room: {}",
connectionToken,
proctoringConnection.roomName);
}
final Map<String, String> attributes = examProctoringService
.createJoinInstructionAttributes(proctoringConnection);
@ -742,7 +787,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
attributes,
connectionToken,
true)
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error));
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error))
.getOrThrow();
}
}