SEBSERV-317 also archive exam config and release SEB restriction

This commit is contained in:
anhefti 2022-06-13 16:17:26 +02:00
parent 22c0dd872d
commit b44c5f4eb2
12 changed files with 162 additions and 38 deletions

View file

@ -87,7 +87,7 @@ public final class CircuitBreaker<T> {
/** Create new CircuitBreakerSupplier. /** Create new CircuitBreakerSupplier.
* *
* @param asyncRunner the AsyncRunner used to create asynchronous calls on the given supplier function * @param asyncRunner the AsyncRunner used to create asynchronous calls on the given supplier function
* @param maxFailingAttempts the number of maximal failing attempts before go form CLOSE into HALF_OPEN state * @param maxFailingAttempts the number of maximal failing attempts before go from CLOSE into HALF_OPEN state
* @param maxBlockingTime the maximal time that an call attempt can block until an error is responded * @param maxBlockingTime the maximal time that an call attempt can block until an error is responded
* @param timeToRecover the time the circuit breaker needs to cool-down on OPEN-STATE before going back to HALF_OPEN * @param timeToRecover the time the circuit breaker needs to cool-down on OPEN-STATE before going back to HALF_OPEN
* state */ * state */
@ -169,7 +169,7 @@ public final class CircuitBreaker<T> {
final long currentBlockingTime = Utils.getMillisecondsNow() - startTime; final long currentBlockingTime = Utils.getMillisecondsNow() - startTime;
final int failing = this.failingCount.incrementAndGet(); final int failing = this.failingCount.incrementAndGet();
if (failing > this.maxFailingAttempts || currentBlockingTime > this.maxBlockingTime) { if (failing >= this.maxFailingAttempts || currentBlockingTime > this.maxBlockingTime) {
// brake thought to HALF_OPEN state and return error // brake thought to HALF_OPEN state and return error
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Changing state from Open to Half Open"); log.debug("Changing state from Open to Half Open");

View file

@ -445,21 +445,21 @@ public class ExamForm implements TemplateComposer {
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(this.examSEBRestrictionSettings.settingsFunction(this.pageService)) .withExec(this.examSEBRestrictionSettings.settingsFunction(this.pageService))
.withAttribute(ExamSEBRestrictionSettings.PAGE_CONTEXT_ATTR_LMS_ID, String.valueOf(exam.lmsSetupId)) .withAttribute(ExamSEBRestrictionSettings.PAGE_CONTEXT_ATTR_LMS_ID, String.valueOf(exam.lmsSetupId))
.withAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY, String.valueOf(!modifyGrant)) .withAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY, String.valueOf(!modifyGrant || !editable))
.noEventPropagation() .noEventPropagation()
.publishIf(() -> sebRestrictionAvailable && readonly) .publishIf(() -> sebRestrictionAvailable && readonly)
.newAction(ActionDefinition.EXAM_ENABLE_SEB_RESTRICTION) .newAction(ActionDefinition.EXAM_ENABLE_SEB_RESTRICTION)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(action -> this.examSEBRestrictionSettings.setSEBRestriction(action, true, this.restService)) .withExec(action -> this.examSEBRestrictionSettings.setSEBRestriction(action, true, this.restService))
.publishIf(() -> sebRestrictionAvailable && readonly && editable && !importFromQuizData .publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData
&& BooleanUtils.isFalse(isRestricted)) && BooleanUtils.isFalse(isRestricted))
.newAction(ActionDefinition.EXAM_DISABLE_SEB_RESTRICTION) .newAction(ActionDefinition.EXAM_DISABLE_SEB_RESTRICTION)
.withConfirm(() -> ACTION_MESSAGE_SEB_RESTRICTION_RELEASE) .withConfirm(() -> ACTION_MESSAGE_SEB_RESTRICTION_RELEASE)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(action -> this.examSEBRestrictionSettings.setSEBRestriction(action, false, this.restService)) .withExec(action -> this.examSEBRestrictionSettings.setSEBRestriction(action, false, this.restService))
.publishIf(() -> sebRestrictionAvailable && readonly && editable && !importFromQuizData .publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData
&& BooleanUtils.isTrue(isRestricted)) && BooleanUtils.isTrue(isRestricted))
.newAction(ActionDefinition.EXAM_PROCTORING_ON) .newAction(ActionDefinition.EXAM_PROCTORING_ON)

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.*; import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -64,6 +65,10 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@WebServiceProfile @WebServiceProfile
public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO { public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
private static final List<String> ACTIVE_EXAM_STATE_NAMES = Arrays.asList(
ExamStatus.FINISHED.name(),
ExamStatus.ARCHIVED.name());
private final ExamRecordMapper examRecordMapper; private final ExamRecordMapper examRecordMapper;
private final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper; private final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper;
private final ConfigurationNodeRecordMapper configurationNodeRecordMapper; private final ConfigurationNodeRecordMapper configurationNodeRecordMapper;
@ -344,7 +349,8 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<Collection<Long>> getExamIdsForConfigNodeId(final Long configurationNodeId) { public Result<Collection<Long>> getExamIdsForConfigNodeId(final Long configurationNodeId) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectByExample() return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
.where( .where(
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId, ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
isEqualTo(configurationNodeId)) isEqualTo(configurationNodeId))
@ -383,16 +389,16 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
.build() .build()
.execute() .execute()
.stream() .stream()
.filter(rec -> !isExamFinished(rec.getExamId())) .filter(rec -> !isExamActive(rec.getExamId()))
.findFirst() .findFirst()
.isPresent()); .isPresent());
} }
private boolean isExamFinished(final Long examId) { private boolean isExamActive(final Long examId) {
try { try {
return this.examRecordMapper.countByExample() return this.examRecordMapper.countByExample()
.where(ExamRecordDynamicSqlSupport.id, isEqualTo(examId)) .where(ExamRecordDynamicSqlSupport.id, isEqualTo(examId))
.and(ExamRecordDynamicSqlSupport.status, isEqualTo(ExamStatus.FINISHED.name())) .and(ExamRecordDynamicSqlSupport.status, isIn(ACTIVE_EXAM_STATE_NAMES))
.build() .build()
.execute() >= 1; .execute() >= 1;
} catch (final Exception e) { } catch (final Exception e) {

View file

@ -97,6 +97,13 @@ public interface ExamAdminService {
* @return ExamProctoringService instance */ * @return ExamProctoringService instance */
Result<ExamProctoringService> getExamProctoringService(final Long examId); Result<ExamProctoringService> getExamProctoringService(final Long examId);
/** This archives a finished exam and set it to archived state as well as the assigned
* exam configurations that are also set to archived state.
*
* @param exam The exam to archive
* @return Result refer to the archived exam or to an error when happened */
Result<Exam> archiveExam(Exam exam);
/** Used to check threshold consistency for a given list of thresholds. /** Used to check threshold consistency for a given list of thresholds.
* Checks if all values are present (none null value) * Checks if all values are present (none null value)
* Checks if there are duplicates * Checks if there are duplicates

View file

@ -20,17 +20,24 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService;
@ -49,17 +56,23 @@ public class ExamAdminServiceImpl implements ExamAdminService {
private final ExamDAO examDAO; private final ExamDAO examDAO;
private final ProctoringAdminService proctoringServiceSettingsService; private final ProctoringAdminService proctoringServiceSettingsService;
private final AdditionalAttributesDAO additionalAttributesDAO; private final AdditionalAttributesDAO additionalAttributesDAO;
private final ConfigurationNodeDAO configurationNodeDAO;
private final ExamConfigurationMapDAO examConfigurationMapDAO;
private final LmsAPIService lmsAPIService; private final LmsAPIService lmsAPIService;
protected ExamAdminServiceImpl( protected ExamAdminServiceImpl(
final ExamDAO examDAO, final ExamDAO examDAO,
final ProctoringAdminService proctoringServiceSettingsService, final ProctoringAdminService proctoringServiceSettingsService,
final AdditionalAttributesDAO additionalAttributesDAO, final AdditionalAttributesDAO additionalAttributesDAO,
final ConfigurationNodeDAO configurationNodeDAO,
final ExamConfigurationMapDAO examConfigurationMapDAO,
final LmsAPIService lmsAPIService) { final LmsAPIService lmsAPIService) {
this.examDAO = examDAO; this.examDAO = examDAO;
this.proctoringServiceSettingsService = proctoringServiceSettingsService; this.proctoringServiceSettingsService = proctoringServiceSettingsService;
this.additionalAttributesDAO = additionalAttributesDAO; this.additionalAttributesDAO = additionalAttributesDAO;
this.configurationNodeDAO = configurationNodeDAO;
this.examConfigurationMapDAO = examConfigurationMapDAO;
this.lmsAPIService = lmsAPIService; this.lmsAPIService = lmsAPIService;
} }
@ -114,7 +127,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
return this.lmsAPIService return this.lmsAPIService
.getLmsAPITemplate(exam.lmsSetupId) .getLmsAPITemplate(exam.lmsSetupId)
.map(lmsAPI -> !lmsAPI.hasSEBClientRestriction(exam)); .map(lmsAPI -> lmsAPI.hasSEBClientRestriction(exam))
.onError(error -> log.error("Failed to check SEB restriction: ", error));
} }
@Override @Override
@ -182,4 +196,55 @@ public class ExamAdminServiceImpl implements ExamAdminService {
}); });
} }
@Override
public Result<Exam> archiveExam(final Exam exam) {
return Result.tryCatch(() -> {
if (exam.status != ExamStatus.FINISHED) {
throw new APIMessageException(
APIMessage.ErrorMessage.INTEGRITY_VALIDATION.of("Exam is in wrong status to archive."));
}
if (log.isDebugEnabled()) {
log.debug("Archiving exam: {}", exam);
}
if (this.isRestricted(exam).getOr(false)) {
if (log.isDebugEnabled()) {
log.debug("Archiving exam, SEB restriction still active, try to release: {}", exam);
}
this.lmsAPIService
.getLmsAPITemplate(exam.lmsSetupId)
.flatMap(lms -> lms.releaseSEBClientRestriction(exam))
.onError(error -> log.error(
"Failed to release SEB client restriction for archiving exam: ",
error));
}
final Exam result = this.examDAO
.updateState(exam.id, ExamStatus.ARCHIVED, null)
.getOrThrow();
this.examConfigurationMapDAO
.getConfigurationNodeIds(result.id)
.getOrThrow()
.stream()
.forEach(configNodeId -> {
if (this.examConfigurationMapDAO.checkNoActiveExamReferences(configNodeId).getOr(false)) {
log.debug("Also set exam configuration to archived: ", configNodeId);
this.configurationNodeDAO.save(
new ConfigurationNode(
configNodeId, null, null, null, null, null,
null, ConfigurationStatus.ARCHIVED, null, null))
.onError(error -> log.error("Failed to set exam configuration to archived state: ",
error));
}
});
return result;
});
}
} }

View file

@ -37,7 +37,7 @@ public interface SEBRestrictionAPI {
* *
* A SEB Restriction is available if there it can get from LMS and if there is either a Config-Key * A SEB Restriction is available if there it can get from LMS and if there is either a Config-Key
* or a BrowserExam-Key set or both. If none of this keys is set, the SEB Restriction is been * or a BrowserExam-Key set or both. If none of this keys is set, the SEB Restriction is been
* considdered to not set on the LMS. * considered to not set on the LMS.
* *
* @param exam exam the exam to get the SEB restriction data for * @param exam exam the exam to get the SEB restriction data for
* @return true if there is a SEB restriction set on the LMS for the exam or false otherwise */ * @return true if there is a SEB restriction set on the LMS for the exam or false otherwise */

View file

@ -130,7 +130,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
environment.getProperty( environment.getProperty(
"sebserver.webservice.circuitbreaker.chaptersRequest.attempts", "sebserver.webservice.circuitbreaker.chaptersRequest.attempts",
Integer.class, Integer.class,
3), 1),
environment.getProperty( environment.getProperty(
"sebserver.webservice.circuitbreaker.chaptersRequest.blockingTime", "sebserver.webservice.circuitbreaker.chaptersRequest.blockingTime",
Long.class, Long.class,
@ -158,7 +158,7 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
environment.getProperty( environment.getProperty(
"sebserver.webservice.circuitbreaker.sebrestriction.attempts", "sebserver.webservice.circuitbreaker.sebrestriction.attempts",
Integer.class, Integer.class,
2), 1),
environment.getProperty( environment.getProperty(
"sebserver.webservice.circuitbreaker.sebrestriction.blockingTime", "sebserver.webservice.circuitbreaker.sebrestriction.blockingTime",
Long.class, Long.class,
@ -388,20 +388,24 @@ public class LmsAPITemplateAdapter implements LmsAPITemplate {
return this.restrictionRequest.protectedRun(() -> this.sebRestrictionAPI return this.restrictionRequest.protectedRun(() -> this.sebRestrictionAPI
.getSEBClientRestriction(exam) .getSEBClientRestriction(exam)
.onError(error -> log.error( .onError(error -> {
"Failed to get SEB restrictions: {}", if (error instanceof NoSEBRestrictionException) {
error.getMessage())) return;
}
log.error("Failed to get SEB restrictions: {}", error.getMessage());
})
.getOrThrow()); .getOrThrow());
} }
@Override @Override
public boolean hasSEBClientRestriction(final Exam exam) { public boolean hasSEBClientRestriction(final Exam exam) {
if (this.sebRestrictionAPI == null) { final Result<SEBRestriction> sebClientRestriction = getSEBClientRestriction(exam);
if (sebClientRestriction.hasError()) {
return false; return false;
} }
return this.sebRestrictionAPI final SEBRestriction sebRestriction = sebClientRestriction.get();
.getSEBClientRestriction(exam).hasError(); return !sebRestriction.configKeys.isEmpty() || !sebRestriction.browserExamKeys.isEmpty();
} }
@Override @Override

View file

@ -30,7 +30,6 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionAPI;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
/** The open edX SEB course restriction API implementation. /** The open edX SEB course restriction API implementation.
* *
@ -134,8 +133,9 @@ public class OpenEdxCourseRestriction implements SEBRestrictionAPI {
} }
return SEBRestriction.from(exam.id, data); return SEBRestriction.from(exam.id, data);
} catch (final HttpClientErrorException ce) { } catch (final HttpClientErrorException ce) {
if (ce.getStatusCode() == HttpStatus.NOT_FOUND || ce.getStatusCode() == HttpStatus.UNAUTHORIZED) { if (ce.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new NoSEBRestrictionException(ce); // No SEB restriction is set for the specified exam, return an empty one
return new SEBRestriction(exam.id, null, null, null);
} }
throw ce; throw ce;
} }

View file

@ -47,7 +47,6 @@ import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder; import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
@ -229,8 +228,9 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
checkWritePrivilege(institutionId); checkWritePrivilege(institutionId);
return this.examDAO.byPK(modelId) return this.examDAO.byPK(modelId)
.flatMap(this::checkWriteAccess) .flatMap(this::checkWriteAccess)
.flatMap(this::checkArchive) .flatMap(this.examAdminService::archiveExam)
.flatMap(exam -> this.examDAO.updateState(exam.id, ExamStatus.ARCHIVED, null)) // .flatMap(this::checkArchive)
// .flatMap(exam -> this.examDAO.updateState(exam.id, ExamStatus.ARCHIVED, null))
.flatMap(this::logModify) .flatMap(this::logModify)
.getOrThrow(); .getOrThrow();
} }
@ -581,15 +581,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
}); });
} }
private Result<Exam> checkArchive(final Exam exam) {
if (exam.status != ExamStatus.FINISHED) {
throw new APIMessageException(
APIMessage.ErrorMessage.INTEGRITY_VALIDATION.of("Exam is in wrong status to archive."));
}
return Result.of(exam);
}
static Function<Collection<Exam>, List<Exam>> pageSort(final String sort) { static Function<Collection<Exam>, List<Exam>> pageSort(final String sort) {
final String sortBy = PageSortOrder.decode(sort); final String sortBy = PageSortOrder.decode(sort);

View file

@ -1,11 +1,11 @@
server.address=localhost server.address=localhost
server.port=8090 server.port=8080
sebserver.gui.http.external.scheme=http sebserver.gui.http.external.scheme=http
sebserver.gui.entrypoint=/gui sebserver.gui.entrypoint=/gui
sebserver.gui.webservice.protocol=http sebserver.gui.webservice.protocol=http
sebserver.gui.webservice.address=localhost sebserver.gui.webservice.address=localhost
sebserver.gui.webservice.port=8090 sebserver.gui.webservice.port=8080
sebserver.gui.webservice.apipath=/admin-api/v1 sebserver.gui.webservice.apipath=/admin-api/v1
# defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page # defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page
sebserver.gui.webservice.poll-interval=1000 sebserver.gui.webservice.poll-interval=1000

View file

@ -39,10 +39,61 @@ public class CircuitBreakerTest {
assertNotNull(this.asyncService); assertNotNull(this.asyncService);
} }
@Test
public void testMaxAttempts1() {
final CircuitBreaker<String> circuitBreaker =
this.asyncService.createCircuitBreaker(1, 500, 1000);
final AtomicInteger attemptCounter = new AtomicInteger(0);
final Supplier<String> tester = () -> {
attemptCounter.getAndIncrement();
throw new RuntimeException("Test Error");
};
final Result<String> result = circuitBreaker.protectedRun(tester); // 1. call...
assertTrue(result.hasError());
assertEquals(State.HALF_OPEN, circuitBreaker.getState());
assertEquals("1", String.valueOf(attemptCounter.get()));
}
@Test
public void testMaxAttempts4() {
final CircuitBreaker<String> circuitBreaker =
this.asyncService.createCircuitBreaker(4, 500, 1000);
final AtomicInteger attemptCounter = new AtomicInteger(0);
final Supplier<String> tester = () -> {
attemptCounter.getAndIncrement();
throw new RuntimeException("Test Error");
};
final Result<String> result = circuitBreaker.protectedRun(tester); // 1. call...
assertTrue(result.hasError());
assertEquals(State.HALF_OPEN, circuitBreaker.getState());
assertEquals("4", String.valueOf(attemptCounter.get()));
}
@Test
public void testMaxAttempts0() {
final CircuitBreaker<String> circuitBreaker =
this.asyncService.createCircuitBreaker(0, 500, 1000);
final AtomicInteger attemptCounter = new AtomicInteger(0);
final Supplier<String> tester = () -> {
attemptCounter.getAndIncrement();
throw new RuntimeException("Test Error");
};
final Result<String> result = circuitBreaker.protectedRun(tester); // 1. call...
assertTrue(result.hasError());
assertEquals(State.HALF_OPEN, circuitBreaker.getState());
assertEquals("1", String.valueOf(attemptCounter.get()));
}
@Test @Test
public void roundtrip1() throws InterruptedException { public void roundtrip1() throws InterruptedException {
final CircuitBreaker<String> circuitBreaker = final CircuitBreaker<String> circuitBreaker =
this.asyncService.createCircuitBreaker(3, 500, 1000); this.asyncService.createCircuitBreaker(4, 500, 1000);
final Supplier<String> tester = tester(100, 5, 10); final Supplier<String> tester = tester(100, 5, 10);

View file

@ -42,7 +42,7 @@ public class MemoizingCircuitBreakerTest {
@Test @Test
public void roundtrip1() throws InterruptedException { public void roundtrip1() throws InterruptedException {
final MemoizingCircuitBreaker<String> circuitBreaker = this.asyncService.createMemoizingCircuitBreaker( final MemoizingCircuitBreaker<String> circuitBreaker = this.asyncService.createMemoizingCircuitBreaker(
tester(100, 5, 10), 3, 500, 1000, true, 1000); tester(100, 5, 10), 4, 500, 1000, true, 1000);
assertNull(circuitBreaker.getCached()); assertNull(circuitBreaker.getCached());