All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mongock.driver.mongodb.test.template.MongoLockManagerITestBase Maven / Gradle / Ivy

The newest version!
package io.mongock.driver.mongodb.test.template;

import com.mongodb.client.FindIterable;
import com.mongodb.client.model.UpdateOptions;
import io.mongock.driver.api.lock.LockCheckException;
import io.mongock.driver.api.lock.LockManager;
import io.mongock.driver.core.lock.LockManagerDefault;
import io.mongock.driver.core.lock.LockEntry;
import io.mongock.driver.core.lock.LockRepositoryWithEntity;
import io.mongock.driver.core.lock.LockStatus;
import io.mongock.driver.mongodb.test.template.util.IntegrationTestBase;
import io.mongock.utils.TimeService;
import org.bson.Document;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Date;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;


public abstract class MongoLockManagerITestBase extends IntegrationTestBase {
  protected static final String LOCK_COLLECTION_NAME = "mongockLock";
  protected static final long LOCK_ACQUIRED_FOR_MILLIS = 5 * 60 * 1000;
  protected static final long LOCK_QUIT_TRYING_AFTER_MILLIS = 3 * LOCK_ACQUIRED_FOR_MILLIS;
  protected static final long LOCK_TRY_FRQUENCY_MILLIS = 1000L;


  protected LockManager lockManager;
  protected LockRepositoryWithEntity repository;

  @BeforeEach
  public void setUp() {
    initializeRepository();
  }

  private void getLockManager(TimeService timeService,
                              long acquireForMillis,
                              long tryFreq,
                              long quickTrying) {
    lockManager = LockManagerDefault.builder()
        .setLockRepository(repository)
        .setTimeService(timeService)
        .setLockAcquiredForMillis(acquireForMillis)
        .setLockTryFrequencyMillis(tryFreq)
        .setLockQuitTryingAfterMillis(quickTrying)
        .build();
  }

  @AfterEach
  public void tearDown() {
    getDataBase().getCollection(LOCK_COLLECTION_NAME).deleteMany(new Document());
  }


  @Test
  public void shouldAcquireLock_WhenHeld_IfSameOwner() throws LockCheckException {
    //given
    getLockManager(new TimeService(), LOCK_ACQUIRED_FOR_MILLIS, LOCK_TRY_FRQUENCY_MILLIS, LOCK_QUIT_TRYING_AFTER_MILLIS);
    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document().append("$set", getLockDbBody(lockManager.getOwner(), currentTimePlusHours(24))),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    lockManager.acquireLockDefault();
  }

  @Test
  public void shouldAcquireLock_WhenHeldByOther_IfExpired() throws LockCheckException {
    //given
    getLockManager(new TimeService(), LOCK_ACQUIRED_FOR_MILLIS, LOCK_TRY_FRQUENCY_MILLIS, LOCK_QUIT_TRYING_AFTER_MILLIS);
    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document().append("$set", getLockDbBody("otherOwner", currentTimePlusHours(-1))),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    lockManager.acquireLockDefault();
  }

  /*
  The lock is held by other. When the current tries to take it's still held, but it will keep trying for a while and
  it eventually get it as the expires time of the lock currently in db is shorter than the time the process trying to
  acquire the lock will be re-trying.
   */
  @Test
  public void shouldFinallyAcquireLock_WhenTheLockIsHeldByOther_IfTheTimeItWIllBeReTryingIsLongerThanTheCurrentLockExpireTime() throws LockCheckException {
    //given
    getLockManager(new TimeService(), LOCK_ACQUIRED_FOR_MILLIS, LOCK_TRY_FRQUENCY_MILLIS, LOCK_QUIT_TRYING_AFTER_MILLIS);
    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document().append("$set", getLockDbBody("otherOwner", System.currentTimeMillis() + 100)),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    lockManager.acquireLockDefault();
  }

  /*
  The lock is held by other. When the current tries to take it's still held, but it will keep trying for a while. However
   the time it will keep trying is not long enough and it will finally fail. So the lock will kept by the current owner
 */
  @Test
  public void shouldNotAcquireLock_WhenHeldByOther_IfExpiresAtIsGreaterThanMaxWaitTime() throws LockCheckException {
    //given
    long acquireFor = 3000L;
    getLockManager(new TimeService(), acquireFor, LOCK_TRY_FRQUENCY_MILLIS, 1000L);
    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document()
            .append("$set", getLockDbBody("otherOwner", currentTimePlusMinutes(1))),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    assertThrows(LockCheckException.class, () -> lockManager.acquireLockDefault());
  }

  @Test
  public void shouldThrowException_WhenEnsuring_IfNotAcquiredFirst() throws LockCheckException {
    //when

    getLockManager(new TimeService(), LOCK_ACQUIRED_FOR_MILLIS, LOCK_TRY_FRQUENCY_MILLIS, 1000L);

    assertThrows(LockCheckException.class, () -> lockManager.ensureLockDefault());
  }

  /**
   * If it's not expired, the lock is ensured because still belongs to the owner
   */
  @Test
  public void shouldEnsureLock_WhenHeldBySame_IfNotExpiredInDB() throws LockCheckException {
    //given
    getLockManager(new TimeService(), LOCK_ACQUIRED_FOR_MILLIS, LOCK_TRY_FRQUENCY_MILLIS, 1000L);

    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document().append("$set", getLockDbBody(lockManager.getOwner(), currentTimePlusMinutes(1))),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    lockManager.ensureLockDefault();
  }

  /**
   * If it's  expired, the lock should be ensured because no one has requested, so it should be extended for the same
   * owner
   */
  @Test
  public void shouldEnsureLock_WhenHeldBySame_IfExpiredInDB() throws LockCheckException {
    //given
    getLockManager(new TimeService(), LOCK_ACQUIRED_FOR_MILLIS, LOCK_TRY_FRQUENCY_MILLIS, 1000L);
    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document().append("$set", getLockDbBody(lockManager.getOwner(), currentTimePlusMinutes(-10))),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    lockManager.ensureLockDefault();
  }

  @Test
  public void shouldEnsureLock_WhenAcquiredPreviously_IfSameOwner() throws LockCheckException {
    //given
    getLockManager(new TimeService(), LOCK_ACQUIRED_FOR_MILLIS, LOCK_TRY_FRQUENCY_MILLIS, 1000L);

    lockManager.acquireLockDefault();

    //when
    lockManager.ensureLockDefault();
  }


  @Test
  public void shouldNotEnsureLock_WhenHeldByOtherAndExpiredInDB_ifHasNotBeenRequestedPreviously() throws LockCheckException {
    //given
    getLockManager(new TimeService(), LOCK_ACQUIRED_FOR_MILLIS, LOCK_TRY_FRQUENCY_MILLIS, 1000L);
    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document().append("$set", getLockDbBody("other", currentTimePlusMinutes(-10))),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    assertThrows(LockCheckException.class, () -> lockManager.ensureLockDefault());
  }

  @Test
  public void shouldNotEnsureLock_WhenHeldByOther_IfNotExpiredInDB() throws LockCheckException {
    //given
    getLockManager(new TimeService(), LOCK_ACQUIRED_FOR_MILLIS, LOCK_TRY_FRQUENCY_MILLIS, 1000L);
    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document().append("$set", getLockDbBody("other", currentTimePlusMinutes(10))),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    assertThrows(LockCheckException.class, () -> lockManager.ensureLockDefault());
  }

  @Test
  public void shouldReleaseLock_WhenHeldBySameOwner() {
    //given
    getLockManager(new TimeService(), 3000L, LOCK_TRY_FRQUENCY_MILLIS, 1000L);
    lockManager.acquireLockDefault();
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    lockManager.releaseLockDefault();

    //then
    FindIterable resultAfter = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNull(resultAfter.first(), "Lock should be removed from DB");
  }

  @Test
  public void shouldNotReleaseLock_IfHeldByOtherOwner() {
    //given
    getLockManager(new TimeService(), 3000L, LOCK_TRY_FRQUENCY_MILLIS, 1000L);
    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document().append("$set", getLockDbBody("otherOwner", currentTimePlusMinutes(10))),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    lockManager.releaseLockDefault();

    //then
    FindIterable resultAfter = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultAfter.first(), "Lock should be removed from DB");
  }

  @Test
  public void releaseLockShouldBeIdempotent_WhenHeldBySameOwner() {
    //given
    getLockManager(new TimeService(), 3000L, 3000L, 1000L);

    lockManager.acquireLockDefault();

    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    lockManager.releaseLockDefault();
    lockManager.releaseLockDefault();

    //then
    FindIterable resultAfter = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNull(resultAfter.first(), "Lock should be removed from DB");
  }

  @Test
  public void releaseLockShouldBeIdempotent_WhenHeldByOtherOwner() {
    //given
    getLockManager(new TimeService(), 3000L, LOCK_TRY_FRQUENCY_MILLIS, 1000L);
    getDataBase().getCollection(LOCK_COLLECTION_NAME).updateMany(
        new Document(),
        new Document().append("$set", getLockDbBody("otherOwner", currentTimePlusMinutes(10))),
        new UpdateOptions().upsert(true));
    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultBefore.first(), "Precondition: Lock should be in database");

    //when
    lockManager.releaseLockDefault();
    lockManager.releaseLockDefault();

    //then
    FindIterable resultAfter = getDataBase().getCollection(LOCK_COLLECTION_NAME)
        .find(new Document().append("key", lockManager.getDefaultKey()));
    assertNotNull(resultAfter.first(), "Lock should be removed from DB");
  }

  @Test
  public void releaseLockShouldNotThrowAnyException_WhenLockNoPresent() {
    //given
    getLockManager(new TimeService(), 3000L, LOCK_TRY_FRQUENCY_MILLIS, 1000L);

    FindIterable resultBefore = getDataBase().getCollection(LOCK_COLLECTION_NAME).find();
    assertNull(resultBefore.first(), "Precondition: Lock should not be in database");

    //when
    lockManager.releaseLockDefault();

    //then
    FindIterable resultAfter = getDataBase().getCollection(LOCK_COLLECTION_NAME).find();
    assertNull(resultAfter.first(), "Lock should be removed from DB");
  }

  private Document getLockDbBody(String owner, long expiresAt) {
    LockEntry lockEntry = new LockEntry(lockManager.getDefaultKey(), LockStatus.LOCK_HELD.name(), owner, new Date(expiresAt));
    return repository.toEntity(lockEntry);
  }

  private long currentTimePlusHours(int hours) {
    return currentTimePlusMinutes(hours * 60);
  }

  private long currentTimePlusMinutes(int minutes) {
    long millis = minutes * 60 * 1000;
    return System.currentTimeMillis() + millis;
  }

  private int millisToMinutes(long millis) {
    return (int) (millis / (1000 * 60));
  }


  protected abstract void initializeRepository();
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy