From 273d9fd92312ea2b107f80dc2c5a7f6d4498fd58 Mon Sep 17 00:00:00 2001 From: anhefti Date: Wed, 17 Feb 2021 10:21:19 +0100 Subject: [PATCH] Fixed master and created tests --- .../dao/impl/WebserviceInfoDAOImpl.java | 66 ++++++++++---- .../session/impl/ExamSessionControlTask.java | 6 -- .../config/application-ws.properties | 1 + .../integration/api/admin/WebserviceTest.java | 85 +++++++++++++++++++ 4 files changed, 136 insertions(+), 22 deletions(-) create mode 100644 src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/WebserviceTest.java diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/WebserviceInfoDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/WebserviceInfoDAOImpl.java index 325e57ef..d0cbf95b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/WebserviceInfoDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/WebserviceInfoDAOImpl.java @@ -35,13 +35,16 @@ public class WebserviceInfoDAOImpl implements WebserviceInfoDAO { private final WebserviceServerInfoRecordMapper webserviceServerInfoRecordMapper; private final long masterDelayTimeThreshold; + private final boolean forceMaster; public WebserviceInfoDAOImpl( final WebserviceServerInfoRecordMapper webserviceServerInfoRecordMapper, + @Value("${sebserver.webservice.forceMaster:false}") final boolean forceMaster, @Value("${sebserver.webservice.master.delay.threshold:10000}") final long masterDelayTimeThreshold) { this.webserviceServerInfoRecordMapper = webserviceServerInfoRecordMapper; this.masterDelayTimeThreshold = masterDelayTimeThreshold; + this.forceMaster = forceMaster; } @Transactional @@ -68,18 +71,24 @@ public class WebserviceInfoDAOImpl implements WebserviceInfoDAO { if (masters != null && !masters.isEmpty()) { if (masters.size() > 1) { + log.error("There are more then one master registered: ", masters); log.info("Reset masters and set this webservice as new master"); - masters.stream() - .forEach(masterRec -> this.webserviceServerInfoRecordMapper.updateByPrimaryKeySelective( - new WebserviceServerInfoRecord(masterRec.getId(), null, null, 0, 0L))); - this.setMasterTo(uuid); - return true; + + masters.stream().forEach(masterRec -> this.webserviceServerInfoRecordMapper + .updateByPrimaryKeySelective( + new WebserviceServerInfoRecord( + masterRec.getId(), + null, + null, + 0, + 0L))); + return this.setMasterTo(uuid); } final WebserviceServerInfoRecord masterRec = masters.get(0); if (masterRec.getUuid().equals(uuid)) { - // this webservice was the master and update time to remain being master + // This webservice is the master. Update time-stamp to remain being master final long now = Utils.getMillisecondsNow(); this.webserviceServerInfoRecordMapper.updateByPrimaryKeySelective( new WebserviceServerInfoRecord(masterRec.getId(), null, null, null, now)); @@ -91,20 +100,18 @@ public class WebserviceInfoDAOImpl implements WebserviceInfoDAO { return true; } else { // Another webservice is master. Check if still alive... + // Force this service to become master if the other master is not alive anymore + // Or if this service is forced to be the master service final long now = Utils.getMillisecondsNow(); final long lastUpdateSince = now - masterRec.getUpdateTime(); - if (lastUpdateSince > this.masterDelayTimeThreshold) { - log.info("Change webservice master form uuid: {} to uuid: {}", masterRec.getUuid(), uuid); - this.webserviceServerInfoRecordMapper.updateByPrimaryKeySelective( - new WebserviceServerInfoRecord(masterRec.getId(), null, null, 0, 0L)); - setMasterTo(uuid); + if (lastUpdateSince > this.masterDelayTimeThreshold || this.forceMaster) { + forceMaster(uuid, masterRec.getUuid(), masterRec.getId()); return true; } } } else { - // if we have no master yet - setMasterTo(uuid); - return true; + // We have no master yet so set this as master service + return setMasterTo(uuid); } return false; @@ -115,14 +122,41 @@ public class WebserviceInfoDAOImpl implements WebserviceInfoDAO { } } - private void setMasterTo(final String uuid) { + private void forceMaster(final String uuid, final String otherUUID, final Long otherId) { + + log.info("Change webservice master form uuid: {} to uuid: {}", otherUUID, uuid); + + this.webserviceServerInfoRecordMapper.updateByPrimaryKeySelective( + new WebserviceServerInfoRecord(otherId, null, null, 0, 0L)); + setMasterTo(uuid); + } + + private boolean setMasterTo(final String uuid) { log.info("Set webservice {} as master", uuid); final long now = Utils.getMillisecondsNow(); - this.webserviceServerInfoRecordMapper.updateByExampleSelective( + + // check if this is registered + final List entries = this.webserviceServerInfoRecordMapper.selectByExample() + .where(WebserviceServerInfoRecordDynamicSqlSupport.uuid, SqlBuilder.isEqualTo(uuid)) + .build() + .execute(); + + if (entries == null || entries.isEmpty()) { + log.warn("The webservice with uuid: {} is not registered and cannot become a master", uuid); + return false; + } + + final Integer execute = this.webserviceServerInfoRecordMapper.updateByExampleSelective( new WebserviceServerInfoRecord(null, null, null, 1, now)) .where(WebserviceServerInfoRecordDynamicSqlSupport.uuid, SqlBuilder.isEqualTo(uuid)) .build() .execute(); + if (execute == null || execute.intValue() <= 0) { + log.error("Failed to update webservice with uuid: {} to become master", uuid); + return false; + } + + return true; } @Transactional diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java index 48992209..f60add00 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionControlTask.java @@ -49,12 +49,6 @@ class ExamSessionControlTask implements DisposableBean { private final String examTaskCron; private final long pingUpdateRate; - // TODO considering to have some caching of running exams end dates here to improve performance - // the end check task has than only to first update missing running exams and then - // check the exam ending within the cached end date of the exam. if an exam has ended by - // applying the check to the cached value, the process can double-check if the end date has - // no change and update if needed or end the exam and remove from cache. - protected ExamSessionControlTask( final ExamDAO examDAO, final SEBClientConnectionService sebClientConnectionService, diff --git a/src/main/resources/config/application-ws.properties b/src/main/resources/config/application-ws.properties index efa28906..fcd19e39 100644 --- a/src/main/resources/config/application-ws.properties +++ b/src/main/resources/config/application-ws.properties @@ -32,6 +32,7 @@ sebserver.webservice.api.admin.clientSecret=${sebserver.password} sebserver.webservice.internalSecret=${sebserver.password} ### webservice networking +sebserver.webservice.forceMaster=false sebserver.webservice.distributed=false sebserver.webservice.http.external.scheme=https sebserver.webservice.http.external.servername= diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/WebserviceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/WebserviceTest.java new file mode 100644 index 00000000..3770935c --- /dev/null +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/WebserviceTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.webservice.integration.api.admin; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import ch.ethz.seb.sebserver.webservice.WebserviceInfo; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.WebserviceInfoDAO; + +public class WebserviceTest extends AdministrationAPIIntegrationTester { + + private static final String WEBSERVICE_1 = "WEBSERVICE_1"; + private static final String WEBSERVICE_2 = "WEBSERVICE_2"; + + @Autowired + private WebserviceInfoDAO webserviceInfoDAO; + @Autowired + private WebserviceInfo webserviceInfo; + + @Before + public void init() { + this.webserviceInfoDAO.unregister(this.webserviceInfo.getWebserviceUUID()); + this.webserviceInfoDAO.register(WEBSERVICE_1, "0.0.0.1"); + this.webserviceInfoDAO.register(WEBSERVICE_2, "0.0.0.1"); + } + + @After + public void cleanup() { + this.webserviceInfoDAO.unregister(WEBSERVICE_1); + this.webserviceInfoDAO.unregister(WEBSERVICE_2); + this.webserviceInfoDAO.register( + this.webserviceInfo.getWebserviceUUID(), + this.webserviceInfo.getLocalHostAddress()); + } + + @Test + public void testFistBecomeMaster() { + assertTrue(this.webserviceInfoDAO.isMaster(WEBSERVICE_1)); + assertFalse(this.webserviceInfoDAO.isMaster(WEBSERVICE_2)); + assertTrue(this.webserviceInfoDAO.isMaster(WEBSERVICE_1)); + assertTrue(this.webserviceInfoDAO.isMaster(WEBSERVICE_1)); + } + + @Test + public void testUnregister_OtherBecomeMaster() { + assertTrue(this.webserviceInfoDAO.isMaster(WEBSERVICE_1)); + assertTrue(this.webserviceInfoDAO.unregister(WEBSERVICE_1)); + assertFalse(this.webserviceInfoDAO.isMaster(WEBSERVICE_1)); + assertTrue(this.webserviceInfoDAO.isMaster(WEBSERVICE_2)); + } + + @Test + public void testOtherBecomeMasterAfterTimout() { + assertTrue(this.webserviceInfoDAO.isMaster(WEBSERVICE_1)); + assertFalse(this.webserviceInfoDAO.isMaster(WEBSERVICE_2)); + + try { + Thread.sleep(5000); + } catch (final InterruptedException e) { + } + + // Still not master + assertFalse(this.webserviceInfoDAO.isMaster(WEBSERVICE_2)); + + try { + Thread.sleep(6000); + } catch (final InterruptedException e) { + } + + assertTrue(this.webserviceInfoDAO.isMaster(WEBSERVICE_2)); + } + +}