SEBSERV-250 improved webservice init

This commit is contained in:
anhefti 2021-12-07 10:41:54 +01:00
parent bab4b95609
commit dfb8afb740
18 changed files with 235 additions and 144 deletions

View file

@ -10,11 +10,16 @@ package ch.ethz.seb.sebserver;
import org.springframework.context.ApplicationEvent;
import ch.ethz.seb.sebserver.webservice.WebserviceInit;
public class SEBServerInitEvent extends ApplicationEvent {
private static final long serialVersionUID = -3608628289559324471L;
public SEBServerInitEvent(final Object source) {
super(source);
public final WebserviceInit webserviceInit;
public SEBServerInitEvent(final WebserviceInit webserviceInit) {
super(webserviceInit);
this.webserviceInit = webserviceInit;
}
}

View file

@ -43,16 +43,10 @@ public class GuiInit implements ApplicationListener<ApplicationReadyEvent> {
this.sebServerInit.init();
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> **** GUI Service starting up... ****");
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> GUI Service successfully successfully started up!");
SEBServerInit.INIT_LOGGER.info("---->");
// final String webServiceProtocol = this.environment.getProperty("sebserver.gui.webservice.protocol", "http");
// final String webServiceAddress = this.environment.getRequiredProperty("sebserver.gui.webservice.address");
// final String webServicePort = this.environment.getProperty("sebserver.gui.webservice.port", "80");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
SEBServerInit.INIT_LOGGER.info("----> *** GUI Service starting up... ***");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info(
"----> Webservice connection: {}",
@ -82,6 +76,11 @@ public class GuiInit implements ApplicationListener<ApplicationReadyEvent> {
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Webservice admin API basic access: --" + webServiceAPIBasicAccess + "--");
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
SEBServerInit.INIT_LOGGER.info("----> *** GUI Service successfully successfully started up! ***");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
}
}

View file

@ -60,6 +60,8 @@ public class WebserviceInfo {
private final boolean isDistributed;
private final String webserviceUUID;
private final long distributedPingUpdateInterval;
private Map<String, String> lmsExternalAddressAlias;
public WebserviceInfo(final Environment environment) {
@ -74,6 +76,11 @@ public class WebserviceInfo {
this.discoveryEndpoint = environment.getRequiredProperty(WEB_SERVICE_EXAM_API_DISCOVERY_ENDPOINT_KEY);
this.contextPath = environment.getProperty(WEB_SERVICE_CONTEXT_PATH, "");
this.distributedPingUpdateInterval = environment.getProperty(
"sebserver.webservice.distributed.pingUpdate",
Long.class,
3000L);
if (StringUtils.isEmpty(this.webserverName)) {
log.warn("NOTE: External server name, property : 'sebserver.webservice.http.external.servername' "
+ "is not set from configuration. The external server name is set to the server address!");
@ -160,6 +167,10 @@ public class WebserviceInfo {
return this.serverURLPrefix + this.discoveryEndpoint;
}
public long getDistributedPingUpdateInterval() {
return this.distributedPingUpdateInterval;
}
public String getLocalHostName() {
try {
return InetAddress.getLocalHost().getHostName();

View file

@ -15,6 +15,7 @@ import javax.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Import;
@ -31,6 +32,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.WebserviceInfoDAO;
@Import(DataSourceAutoConfiguration.class)
public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent> {
private final ApplicationContext applicationContext;
private final SEBServerInit sebServerInit;
private final Environment environment;
private final WebserviceInfo webserviceInfo;
@ -41,20 +43,26 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
protected WebserviceInit(
final SEBServerInit sebServerInit,
final Environment environment,
final WebserviceInfo webserviceInfo,
final AdminUserInitializer adminUserInitializer,
final ApplicationEventPublisher applicationEventPublisher,
final WebserviceInfoDAO webserviceInfoDAO,
final DBIntegrityChecker dbIntegrityChecker) {
final DBIntegrityChecker dbIntegrityChecker,
final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.sebServerInit = sebServerInit;
this.environment = environment;
this.environment = applicationContext.getEnvironment();
this.webserviceInfo = webserviceInfo;
this.adminUserInitializer = adminUserInitializer;
this.applicationEventPublisher = applicationEventPublisher;
this.webserviceInfoDAO = webserviceInfoDAO;
this.dbIntegrityChecker = dbIntegrityChecker;
}
public ApplicationContext getApplicationContext() {
return this.applicationContext;
}
@Override
@ -62,17 +70,21 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
this.sebServerInit.init();
SEBServerInit.INIT_LOGGER.info("----> **** Webservice starting up... ****");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
SEBServerInit.INIT_LOGGER.info("----> *** Webservice starting up... ***");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> Register Webservice: {}", this.webserviceInfo.getWebserviceUUID());
try {
this.webserviceInfoDAO.register(
final boolean register = this.webserviceInfoDAO.register(
this.webserviceInfo.getWebserviceUUID(),
InetAddress.getLocalHost().getHostAddress());
if (register) {
SEBServerInit.INIT_LOGGER.info("----> Successfully register Webservice instance");
}
} catch (final Exception e) {
SEBServerInit.INIT_LOGGER.error("Failed to register webservice: ", e);
SEBServerInit.INIT_LOGGER.error("----> Failed to register webservice: ", e);
}
SEBServerInit.INIT_LOGGER.info("----> ");
@ -81,23 +93,31 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
this.applicationEventPublisher.publishEvent(new SEBServerInitEvent(this));
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> **** Webservice successfully started up! **** ");
// Run the data base integrity checks and fixes if configured
this.dbIntegrityChecker.checkIntegrity();
// Create an initial admin account if requested and not already in the data-base
this.adminUserInitializer.initAdminAccount();
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
SEBServerInit.INIT_LOGGER.info("----> *** Webservice Info: ***");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> *** Info:");
SEBServerInit.INIT_LOGGER.info("----> JDBC connection pool max size: {}",
this.environment.getProperty("spring.datasource.hikari.maximumPoolSize"));
if (this.webserviceInfo.isDistributed()) {
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> Distributed Setup: {}", this.webserviceInfo.getWebserviceUUID());
SEBServerInit.INIT_LOGGER.info("------> Ping update time: {}",
SEBServerInit.INIT_LOGGER.info("----> Ping update time: {}",
this.environment.getProperty("sebserver.webservice.distributed.pingUpdate"));
SEBServerInit.INIT_LOGGER.info("------> Connection update time: {}",
SEBServerInit.INIT_LOGGER.info("----> Connection update time: {}",
this.environment.getProperty("sebserver.webservice.distributed.connectionUpdate", "2000"));
}
try {
SEBServerInit.INIT_LOGGER.info("----> ");
SEBServerInit.INIT_LOGGER.info("----> Server address: {}", this.environment.getProperty("server.address"));
SEBServerInit.INIT_LOGGER.info("----> Server port: {}", this.environment.getProperty("server.port"));
SEBServerInit.INIT_LOGGER.info("---->");
@ -122,19 +142,18 @@ public class WebserviceInit implements ApplicationListener<ApplicationReadyEvent
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Property Override Test: {}", this.webserviceInfo.getTestProperty());
// Run the data base integrity checks and fixes if configured
this.dbIntegrityChecker.checkIntegrity();
// Create an initial admin account if requested and not already in the data-base
this.adminUserInitializer.initAdminAccount();
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
SEBServerInit.INIT_LOGGER.info("----> *** Webservice successfully started up! ***");
SEBServerInit.INIT_LOGGER.info("----> *********************************************************");
}
@PreDestroy
public void gracefulShutdown() {
SEBServerInit.INIT_LOGGER.info("**** Gracefully Shutdown of SEB Server instance {} ****",
SEBServerInit.INIT_LOGGER.info("*********************************************************");
SEBServerInit.INIT_LOGGER.info("**** Gracefully Shutdown of SEB Server instance {}",
this.webserviceInfo.getHostAddress());
SEBServerInit.INIT_LOGGER.info("---->");
SEBServerInit.INIT_LOGGER.info("----> Unregister Webservice: {}", this.webserviceInfo.getWebserviceUUID());

View file

@ -50,12 +50,12 @@ public interface ClientPingMapper {
int update(UpdateStatementProvider updateStatement);
@SelectProvider(type = SqlProviderAdapter.class, method = "select")
@ResultType(ClientEventLastPingRecord.class)
@ResultType(ClientLastPingRecord.class)
@ConstructorArgs({
@Arg(column = "id", javaType = Long.class, jdbcType = JdbcType.BIGINT),
@Arg(column = "value", javaType = Long.class, jdbcType = JdbcType.BIGINT)
})
Collection<ClientEventLastPingRecord> selectMany(SelectStatementProvider select);
Collection<ClientLastPingRecord> selectMany(SelectStatementProvider select);
default Long selectPingTimeByPrimaryKey(final Long id_) {
return SelectDSL.selectWithMapper(
@ -78,7 +78,7 @@ public interface ClientPingMapper {
.execute();
}
default QueryExpressionDSL<MyBatis3SelectModelAdapter<Collection<ClientEventLastPingRecord>>> selectByExample() {
default QueryExpressionDSL<MyBatis3SelectModelAdapter<Collection<ClientLastPingRecord>>> selectByExample() {
return SelectDSL.selectWithMapper(
this::selectMany,
@ -95,12 +95,12 @@ public interface ClientPingMapper {
.execute();
}
final class ClientEventLastPingRecord {
final class ClientLastPingRecord {
public final Long id;
public final Long lastPingTime;
public ClientEventLastPingRecord(
public ClientLastPingRecord(
final Long id,
final Long value) {

View file

@ -41,6 +41,8 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionR
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientInstructionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientInstructionRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
@ -63,15 +65,18 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
private final ClientEventRecordMapper clientEventRecordMapper;
private final ClientInstructionRecordMapper clientInstructionRecordMapper;
private final ClientIndicatorRecordMapper clientIndicatorRecordMapper;
protected ClientConnectionDAOImpl(
final ClientConnectionRecordMapper clientConnectionRecordMapper,
final ClientEventRecordMapper clientEventRecordMapper,
final ClientInstructionRecordMapper clientInstructionRecordMapper) {
final ClientInstructionRecordMapper clientInstructionRecordMapper,
final ClientIndicatorRecordMapper clientIndicatorRecordMapper) {
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.clientEventRecordMapper = clientEventRecordMapper;
this.clientInstructionRecordMapper = clientInstructionRecordMapper;
this.clientIndicatorRecordMapper = clientIndicatorRecordMapper;
}
@Override
@ -474,7 +479,15 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
return Collections.emptyList();
}
// first delete all related client events
// delete all related client indicators
this.clientIndicatorRecordMapper.deleteByExample()
.where(
ClientIndicatorRecordDynamicSqlSupport.clientConnectionId,
SqlBuilder.isIn(ids))
.build()
.execute();
// delete all related client events
this.clientEventRecordMapper.deleteByExample()
.where(
ClientEventRecordDynamicSqlSupport.clientConnectionId,

View file

@ -237,18 +237,19 @@ public class ExamAdminServiceImpl implements ExamAdminService {
@Override
public Result<Boolean> isProctoringEnabled(final Long examId) {
final Result<Boolean> result = this.additionalAttributesDAO.getAdditionalAttribute(
return this.additionalAttributesDAO.getAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_ENABLE_PROCTORING)
.map(rec -> BooleanUtils.toBoolean(rec.getValue()))
.onError(error -> log.warn("Failed to verify proctoring enabled for exam: {}, {}",
examId,
error.getMessage()));
if (result.hasError()) {
return Result.of(false);
}
return result;
.onErrorDo(error -> {
if (log.isDebugEnabled()) {
log.warn("Failed to verify proctoring enabled for exam: {}, {}",
examId,
error.getMessage());
}
return false;
});
}
@Override

View file

@ -15,6 +15,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.ClientIndicatorType;
/** A client indicator is a indicator value holder for a specific Indicator
* on a running client connection.
@ -31,6 +32,11 @@ public interface ClientIndicator extends IndicatorValue {
* @param cachingEnabled defines whether indicator value caching is enabled or not. */
void init(Indicator indicatorDefinition, Long connectionId, boolean active, boolean cachingEnabled);
/** Get the client indicator type
*
* @return the client indicator type */
ClientIndicatorType indicatorType();
/** Get the exam identifier of the client connection of this ClientIndicator
*
* @return the exam identifier of the client connection of this ClientIndicator */

View file

@ -44,7 +44,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedPingCache;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedPingService;
import ch.ethz.seb.sebserver.webservice.weblayer.api.APIConstraintViolationException;
@Lazy
@ -70,7 +70,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
private final SEBClientInstructionService sebInstructionService;
private final SEBClientNotificationService sebClientNotificationService;
private final ExamAdminService examAdminService;
private final DistributedPingCache distributedPingCache;
private final DistributedPingService distributedPingCache;
private final boolean isDistributedSetup;
protected SEBClientConnectionServiceImpl(
@ -80,7 +80,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
final SEBClientInstructionService sebInstructionService,
final SEBClientNotificationService sebClientNotificationService,
final ExamAdminService examAdminService,
final DistributedPingCache distributedPingCache) {
final DistributedPingService distributedPingCache) {
this.examSessionService = examSessionService;
this.examSessionCacheService = examSessionService.getExamSessionCacheService();

View file

@ -11,18 +11,14 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.Executor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientPingMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator.DistributedPingService.PingUpdate;
public abstract class AbstractPingIndicator extends AbstractClientIndicator {
@ -30,17 +26,11 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
private final Set<EventType> EMPTY_SET = Collections.unmodifiableSet(EnumSet.noneOf(EventType.class));
private final Executor executor;
protected final DistributedPingCache distributedPingCache;
protected final DistributedPingService distributedPingCache;
protected PingUpdate pingUpdate = null;
protected AbstractPingIndicator(
final DistributedPingCache distributedPingCache,
@Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor executor) {
protected AbstractPingIndicator(final DistributedPingService distributedPingCache) {
super();
this.executor = executor;
this.distributedPingCache = distributedPingCache;
}
@ -55,9 +45,9 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
if (!this.cachingEnabled && this.active) {
try {
createPingUpdate();
this.pingUpdate = this.distributedPingCache.createPingUpdate(connectionId);
} catch (final Exception e) {
createPingUpdate();
this.pingUpdate = this.distributedPingCache.createPingUpdate(connectionId);
}
}
}
@ -74,15 +64,7 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
}
}
// Update last ping time on persistent storage asynchronously within a defines thread pool with no
// waiting queue to skip further ping updates if all update threads are busy
try {
this.executor.execute(this.pingUpdate);
} catch (final Exception e) {
if (log.isDebugEnabled()) {
log.warn("Failed to schedule ping task: {}" + e.getMessage());
}
}
this.distributedPingCache.updatePingAsync(this.pingUpdate);
}
}
@ -93,21 +75,15 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
}
try {
createPingUpdate();
this.pingUpdate = this.distributedPingCache.createPingUpdate(this.connectionId);
if (this.pingUpdate == null) {
createPingUpdate();
this.pingUpdate = this.distributedPingCache.createPingUpdate(this.connectionId);
}
} catch (final Exception e) {
log.error("Failed to recover ping record for connection: {}", this.connectionId, e);
}
}
private void createPingUpdate() {
this.pingUpdate = new PingUpdate(
this.distributedPingCache.getClientPingMapper(),
this.distributedPingCache.initPingForConnection(this.connectionId));
}
@Override
public Set<EventType> observedEvents() {
return this.EMPTY_SET;
@ -115,26 +91,4 @@ public abstract class AbstractPingIndicator extends AbstractClientIndicator {
public abstract ClientEventRecord updateLogEvent(final long now);
static final class PingUpdate implements Runnable {
private final ClientPingMapper clientPingMapper;
final Long pingRecord;
public PingUpdate(final ClientPingMapper clientPingMapper, final Long pingRecord) {
this.clientPingMapper = clientPingMapper;
this.pingRecord = pingRecord;
}
@Override
public void run() {
try {
this.clientPingMapper
.updatePingTime(this.pingRecord, Utils.getMillisecondsNow());
} catch (final Exception e) {
log.error("Failed to update ping: {}", e.getMessage());
}
}
}
}

View file

@ -45,4 +45,9 @@ public class BatteryStatusIndicator extends AbstractLogNumberIndicator {
return IndicatorType.BATTERY_STATUS;
}
@Override
public ClientIndicatorType indicatorType() {
return ClientIndicatorType.BATTERY_STATUS;
}
}

View file

@ -13,6 +13,7 @@ import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;
@ -20,50 +21,78 @@ import org.joda.time.DateTimeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.SEBServerInit;
import ch.ethz.seb.sebserver.SEBServerInitEvent;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientPingMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientPingMapper.ClientEventLastPingRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientPingMapper.ClientLastPingRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientIndicatorRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientIndicatorRecord;
@Lazy
@Component
@WebServiceProfile
public class DistributedPingCache implements DisposableBean {
public class DistributedPingService implements DisposableBean {
private static final Logger log = LoggerFactory.getLogger(DistributedPingCache.class);
private static final Logger log = LoggerFactory.getLogger(DistributedPingService.class);
private final Executor pingUpdateExecutor;
private final ClientIndicatorRecordMapper clientIndicatorRecordMapper;
private final ClientPingMapper clientPingMapper;
private final long pingUpdateTolerance;
private long pingUpdateTolerance;
private ScheduledFuture<?> taskRef;
private final Map<Long, Long> pingCache = new ConcurrentHashMap<>();
private long lastUpdate = 0L;
public DistributedPingCache(
public DistributedPingService(
@Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor pingUpdateExecutor,
final ClientIndicatorRecordMapper clientIndicatorRecordMapper,
final ClientPingMapper clientPingMapper,
final WebserviceInfo webserviceInfo,
final TaskScheduler taskScheduler,
@Value("${sebserver.webservice.distributed.pingUpdate:3000}") final long pingUpdate) {
final ClientPingMapper clientPingMapper) {
this.pingUpdateExecutor = pingUpdateExecutor;
this.clientIndicatorRecordMapper = clientIndicatorRecordMapper;
this.clientPingMapper = clientPingMapper;
this.pingUpdateTolerance = pingUpdate * 2 / 3;
}
@EventListener(SEBServerInitEvent.class)
public void init(final SEBServerInitEvent initEvent) {
final ApplicationContext applicationContext = initEvent.webserviceInit.getApplicationContext();
final WebserviceInfo webserviceInfo = applicationContext.getBean(WebserviceInfo.class);
if (webserviceInfo.isDistributed()) {
SEBServerInit.INIT_LOGGER.info("------>");
SEBServerInit.INIT_LOGGER.info("------> Activate distributed ping service:");
final TaskScheduler taskScheduler = applicationContext.getBean(TaskScheduler.class);
final long distributedPingUpdateInterval = webserviceInfo.getDistributedPingUpdateInterval();
this.pingUpdateTolerance = distributedPingUpdateInterval * 2 / 3;
SEBServerInit.INIT_LOGGER.info("------> with distributedPingUpdateInterval: {}",
distributedPingUpdateInterval);
SEBServerInit.INIT_LOGGER.info("------> with taskScheduler: {}", taskScheduler);
try {
this.taskRef = taskScheduler.scheduleAtFixedRate(this::updatePings, pingUpdate);
this.taskRef = taskScheduler.scheduleAtFixedRate(
this::persistentPingUpdate,
distributedPingUpdateInterval);
SEBServerInit.INIT_LOGGER.info("------> distributed ping service successfully initialized!");
} catch (final Exception e) {
SEBServerInit.INIT_LOGGER.error("------> Failed to initialize distributed ping service:", e);
log.error("Failed to initialize distributed ping cache update task");
this.taskRef = null;
}
@ -141,10 +170,10 @@ public class DistributedPingCache implements DisposableBean {
log.debug("*** Delete ping record for SEB connection: {}", connectionId);
}
final Collection<ClientEventLastPingRecord> records = this.clientPingMapper
final Collection<ClientLastPingRecord> records = this.clientPingMapper
.selectByExample()
.where(ClientEventRecordDynamicSqlSupport.clientConnectionId, isEqualTo(connectionId))
.and(ClientEventRecordDynamicSqlSupport.type, isEqualTo(ClientIndicatorType.LAST_PING.id))
.where(ClientIndicatorRecordDynamicSqlSupport.clientConnectionId, isEqualTo(connectionId))
.and(ClientIndicatorRecordDynamicSqlSupport.type, isEqualTo(ClientIndicatorType.LAST_PING.id))
.build()
.execute();
@ -194,8 +223,7 @@ public class DistributedPingCache implements DisposableBean {
}
}
private void updatePings() {
public void persistentPingUpdate() {
if (this.pingCache.isEmpty()) {
return;
}
@ -215,7 +243,7 @@ public class DistributedPingCache implements DisposableBean {
final Map<Long, Long> mapping = this.clientPingMapper
.selectByExample()
.where(
ClientEventRecordDynamicSqlSupport.type,
ClientIndicatorRecordDynamicSqlSupport.type,
isEqualTo(ClientIndicatorType.LAST_PING.id))
.build()
.execute()
@ -235,6 +263,45 @@ public class DistributedPingCache implements DisposableBean {
this.lastUpdate = millisecondsNow;
}
// Update last ping time on persistent storage asynchronously within a defines thread pool with no
// waiting queue to skip further ping updates if all update threads are busy
void updatePingAsync(final PingUpdate pingUpdate) {
try {
this.pingUpdateExecutor.execute(pingUpdate);
} catch (final Exception e) {
if (log.isDebugEnabled()) {
log.warn("Failed to schedule ping task: {}" + e.getMessage());
}
}
}
PingUpdate createPingUpdate(final Long connectionId) {
return new PingUpdate(
this.getClientPingMapper(),
this.initPingForConnection(connectionId));
}
static final class PingUpdate implements Runnable {
private final ClientPingMapper clientPingMapper;
final Long pingRecord;
public PingUpdate(final ClientPingMapper clientPingMapper, final Long pingRecord) {
this.clientPingMapper = clientPingMapper;
this.pingRecord = pingRecord;
}
@Override
public void run() {
try {
this.clientPingMapper
.updatePingTime(this.pingRecord, Utils.getMillisecondsNow());
} catch (final Exception e) {
log.error("Failed to update ping: {}", e.getMessage());
}
}
}
@Override
public void destroy() throws Exception {
if (this.taskRef != null) {

View file

@ -31,4 +31,9 @@ public final class ErrorLogCountClientIndicator extends AbstractLogLevelCountInd
return IndicatorType.ERROR_COUNT;
}
@Override
public ClientIndicatorType indicatorType() {
return ClientIndicatorType.ERROR_LOG_COUNT;
}
}

View file

@ -31,4 +31,9 @@ public class InfoLogCountClientIndicator extends AbstractLogLevelCountIndicator
return IndicatorType.INFO_COUNT;
}
@Override
public ClientIndicatorType indicatorType() {
return ClientIndicatorType.INFO_LOG_COUNT;
}
}

View file

@ -10,12 +10,10 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.concurrent.Executor;
import org.joda.time.DateTimeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
@ -24,7 +22,6 @@ import org.springframework.stereotype.Component;
import com.fasterxml.jackson.annotation.JsonIgnore;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
@ -47,10 +44,8 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
private boolean missingPing = false;
private boolean hidden = false;
public PingIntervalClientIndicator(
final DistributedPingCache distributedPingCache,
@Qualifier(AsyncServiceSpringConfig.EXAM_API_PING_SERVICE_EXECUTOR_BEAN_NAME) final Executor executor) {
super(distributedPingCache, executor);
public PingIntervalClientIndicator(final DistributedPingService distributedPingCache) {
super(distributedPingCache);
this.cachingEnabled = true;
}
@ -89,6 +84,11 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
}
@Override
public ClientIndicatorType indicatorType() {
return ClientIndicatorType.LAST_PING;
}
@JsonIgnore
public final boolean isMissingPing() {
return this.missingPing;

View file

@ -45,4 +45,9 @@ public class WLANStatusIndicator extends AbstractLogNumberIndicator {
return IndicatorType.WLAN_STATUS;
}
@Override
public ClientIndicatorType indicatorType() {
return ClientIndicatorType.WLAN_STATUS;
}
}

View file

@ -30,4 +30,9 @@ public class WarnLogCountClientIndicator extends AbstractLogLevelCountIndicator
public IndicatorType getType() {
return IndicatorType.WARN_COUNT;
}
@Override
public ClientIndicatorType indicatorType() {
return ClientIndicatorType.WARN_LOG_COUNT;
}
}

View file

@ -10,8 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.Executor;
import org.joda.time.DateTimeUtils;
import org.junit.After;
import org.junit.Test;
@ -20,7 +18,6 @@ import org.mockito.Mockito;
import com.fasterxml.jackson.core.JsonProcessingException;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
public class PingIntervalClientIndicatorTest {
@ -34,12 +31,10 @@ public class PingIntervalClientIndicatorTest {
DateTimeUtils.setCurrentMillisProvider(() -> 1L);
final ClientEventDAO clientEventDAO = Mockito.mock(ClientEventDAO.class);
final DistributedPingCache distributedPingCache = Mockito.mock(DistributedPingCache.class);
final Executor executor = Mockito.mock(Executor.class);
final DistributedPingService distributedPingCache = Mockito.mock(DistributedPingService.class);
final PingIntervalClientIndicator pingIntervalClientIndicator =
new PingIntervalClientIndicator(distributedPingCache, executor);
new PingIntervalClientIndicator(distributedPingCache);
assertEquals("0.0", String.valueOf(pingIntervalClientIndicator.getValue()));
}
@ -48,12 +43,10 @@ public class PingIntervalClientIndicatorTest {
DateTimeUtils.setCurrentMillisProvider(() -> 1L);
final ClientEventDAO clientEventDAO = Mockito.mock(ClientEventDAO.class);
final DistributedPingCache distributedPingCache = Mockito.mock(DistributedPingCache.class);
final Executor executor = Mockito.mock(Executor.class);
final DistributedPingService distributedPingCache = Mockito.mock(DistributedPingService.class);
final PingIntervalClientIndicator pingIntervalClientIndicator =
new PingIntervalClientIndicator(distributedPingCache, executor);
new PingIntervalClientIndicator(distributedPingCache);
assertEquals("0.0", String.valueOf(pingIntervalClientIndicator.getValue()));
DateTimeUtils.setCurrentMillisProvider(() -> 10L);
@ -65,12 +58,10 @@ public class PingIntervalClientIndicatorTest {
public void testSerialization() throws JsonProcessingException {
DateTimeUtils.setCurrentMillisProvider(() -> 1L);
final ClientEventDAO clientEventDAO = Mockito.mock(ClientEventDAO.class);
final DistributedPingCache distributedPingCache = Mockito.mock(DistributedPingCache.class);
final Executor executor = Mockito.mock(Executor.class);
final DistributedPingService distributedPingCache = Mockito.mock(DistributedPingService.class);
final PingIntervalClientIndicator pingIntervalClientIndicator =
new PingIntervalClientIndicator(distributedPingCache, executor);
new PingIntervalClientIndicator(distributedPingCache);
final JSONMapper jsonMapper = new JSONMapper();
final String json = jsonMapper.writeValueAsString(pingIntervalClientIndicator);
assertEquals("{\"indicatorValue\":0.0,\"indicatorType\":\"LAST_PING\"}", json);