Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OKAPI-1191: NumberFormatException in timerId halts Okapi #1359

Merged
merged 9 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion okapi-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<artifactId>log4j-slf4j2-impl</artifactId>
</dependency>
<dependency> <!-- for log4j2 asynchronous loggers -->
<groupId>com.lmax</groupId>
Expand Down
11 changes: 11 additions & 0 deletions okapi-core/src/main/java/org/folio/okapi/bean/TimerDescriptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,15 @@ public boolean isModified() {
public void setModified(boolean modified) {
this.modified = modified;
}

/**
* Shallow copy.
*/
public TimerDescriptor copy() {
var timerDescriptor = new TimerDescriptor();
timerDescriptor.setId(id);
timerDescriptor.setRoutingEntry(routingEntry);
timerDescriptor.setModified(modified);
return timerDescriptor;
}
}
254 changes: 150 additions & 104 deletions okapi-core/src/main/java/org/folio/okapi/managers/TimerManager.java

Large diffs are not rendered by default.

103 changes: 103 additions & 0 deletions okapi-core/src/main/java/org/folio/okapi/util/TenantProductSeq.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package org.folio.okapi.util;

import java.util.Collection;
import org.folio.okapi.bean.TimerDescriptor;

/**
* [tenant]_[product]_[seq] (like test_tenant_mod-foo_2) or [product]_[seq] (like mod-foo_2).
*
* <p>Used as {@link TimerDescriptor} id.
*/
public class TenantProductSeq {
private static final String TIMER_ENTRY_SEP = "_";
private final String tenantId;
private final String product;
private final int seq;

/**
* Constructor using the three components.
*/
public TenantProductSeq(String tenantId, String product, int seq) {
this.tenantId = tenantId;
this.product = product;
this.seq = seq;
}

/**
* Like {@link #TenantProductSeq(String tenantProductSeq)} but the parameter
* tenantId replaces the value from tenantProductSeq String.
*
* @param tenantId the replacement value; if null the value from
* tenantProductSeq String is taken
*/
public TenantProductSeq(String tenantId, String tenantProductSeq) {
int pos2 = tenantProductSeq.lastIndexOf(TIMER_ENTRY_SEP);
seq = Integer.parseInt(tenantProductSeq.substring(pos2 + 1));
int pos1 = tenantProductSeq.lastIndexOf(TIMER_ENTRY_SEP, pos2 - 1);
product = tenantProductSeq.substring(pos1 + 1, pos2);
if (tenantId != null) {
this.tenantId = tenantId;
return;
}
if (pos1 == -1) {
this.tenantId = null;
} else {
this.tenantId = tenantProductSeq.substring(0, pos1);
}
}

/**
* Parse a String [tenant]_[product]_[seq] like test_tenant_mod-foo_2
* or [product]_[seq] like mod-foo_2.
*/
public TenantProductSeq(String tenantProductSeq) {
this(null, tenantProductSeq);
}

/**
* The tenant id.
*/
public String getTenantId() {
return tenantId;
}

/**
* The product, like mod-foo.
*/
public String getProduct() {
return product;
}

/**
* The timer number in the timer array in the module descriptor, starting with 0.
*/
public int getSeq() {
return seq;
}

/**
* Concatenation of the components, like mod-foo_2 or test_tenant_mod-foo_2.
*/
public String toString() {
if (tenantId == null) {
return product + TIMER_ENTRY_SEP + seq;
} else {
return tenantId + TIMER_ENTRY_SEP + product + TIMER_ENTRY_SEP + seq;
}
}

/**
* For each TimerDescriptor alter the id by removing the tenant id from the String.
*
* <p>For example test_tenant_mod-foo_2 becomes mod-foo_2.
*/
public static Collection<TimerDescriptor> stripTenantIdFromTimerId(
Collection<TimerDescriptor> collection) {

collection.forEach(timerDescriptor -> {
var old = new TenantProductSeq(timerDescriptor.getId());
timerDescriptor.setId(old.getProduct() + TIMER_ENTRY_SEP + old.getSeq());
});
return collection;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
package org.folio.okapi.managers;

import static io.vertx.core.Future.succeededFuture;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.junit5.Timeout;
import io.vertx.junit5.VertxExtension;
import io.vertx.junit5.VertxTestContext;
import java.util.List;
import org.assertj.core.api.Assertions;
import org.folio.okapi.bean.InterfaceDescriptor;
import org.folio.okapi.bean.ModuleDescriptor;
import org.folio.okapi.bean.RoutingEntry;
import org.folio.okapi.bean.Schedule;
import org.folio.okapi.bean.TimerDescriptor;
import org.folio.okapi.service.impl.TimerStoreMemory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

@Timeout(5000)
@ExtendWith(VertxExtension.class)
class TimerManagerTest {

@Test
Expand Down Expand Up @@ -37,4 +55,94 @@ void isPatchReset() {
Assertions.assertThat(TimerManager.isPatchReset(routingEntry)).isFalse();
}

@Test
void getModuleForTimer() {
var timerManager = new TimerManager(null, true);
var x = moduleDescriptor("mod-x-1.0.0", 11);
var y = moduleDescriptor("mod-y-2.3.4", 11);
var mds = List.of(x, y);
Assertions.assertThat(timerManager.getModuleForTimer(mds, "tenant_mod-x_0")).isEqualTo(x);
Assertions.assertThat(timerManager.getModuleForTimer(mds, "tenant_mod-x_11")).isNull();
Assertions.assertThat(timerManager.getModuleForTimer(mds, "tenant_mod-y_3")).isEqualTo(y);
Assertions.assertThat(timerManager.getModuleForTimer(mds, "the_test_tenant_mod-y_3")).isEqualTo(y);
Assertions.assertThat(timerManager.getModuleForTimer(mds, "invalid")).isNull();
}

ModuleDescriptor moduleDescriptor(String moduleId, int timerEntryCount) {
var routingEntries = new RoutingEntry[timerEntryCount];
for (int i = 0; i < routingEntries.length; i++) {
var routingEntry = new RoutingEntry();
routingEntry.setDelay("1");
routingEntry.setUnit("second");
routingEntries[i] = routingEntry;
}
var timers = new InterfaceDescriptor("_timer", "1.0");
timers.setHandlers(routingEntries);
timers.setInterfaceType("system");
InterfaceDescriptor [] provides = { timers };
var moduleDescriptor = new ModuleDescriptor(moduleId);
moduleDescriptor.setProvides(provides);
return moduleDescriptor;
}

TimerDescriptor timerDescriptor(String id, Integer seconds) {
var routingEntry = new RoutingEntry();
if (seconds != null) {
routingEntry.setDelay("" + seconds);
routingEntry.setUnit("second");
}
var timerDescriptor = new TimerDescriptor();
timerDescriptor.setId(id);
timerDescriptor.setRoutingEntry(routingEntry);
timerDescriptor.setModified(true);
return timerDescriptor;
}

Future<Void> testPatchTimer(TimerManager timerManager, String productSeq, Integer seconds) {
return timerManager.patchTimer("test_tenant", timerDescriptor(productSeq, seconds))
.compose(x -> timerManager.getTimer("test_tenant", productSeq))
.map(timerDescriptor -> {
var delay = timerDescriptor.getRoutingEntry().getDelay();
if (seconds == null) {
Assertions.assertThat(delay).isEqualTo("" + 1);
} else {
Assertions.assertThat(delay).isEqualTo("" + seconds);
}
return null;
});
}

@Test
void init(Vertx vertx, VertxTestContext vtc) {
var timerStore = new TimerStoreMemory(timerDescriptor("mod-y_0", 2));
var mds = List.of(moduleDescriptor("mod-x-1.0.0", 1), moduleDescriptor("mod-y-2.3.4", 2));
var tenantManager = mock(TenantManager.class);
when(tenantManager.allTenants()).thenReturn(succeededFuture(List.of("test_tenant")));
when(tenantManager.getEnabledModules("test_tenant")).thenReturn(succeededFuture(mds));
var discoveryManager = mock(DiscoveryManager.class);
var proxyService = mock(ProxyService.class);
var timerManager = new TimerManager(timerStore, true);
timerManager.init(vertx, tenantManager, discoveryManager, proxyService)
.compose(x -> testPatchTimer(timerManager, "mod-y_0", 0))
.compose(x -> testPatchTimer(timerManager, "mod-y_0", null))
.compose(x -> testPatchTimer(timerManager, "mod-y_0", 1))
.compose(x -> testPatchTimer(timerManager, "mod-y_0", 2))
.compose(x -> testPatchTimer(timerManager, "mod-y_0", 2))
.compose(x -> testPatchTimer(timerManager, "mod-x_0", 2))
.andThen(vtc.succeedingThenComplete());
}

@ParameterizedTest
@CsvSource(textBlock = """
, , false
foo, foo, false
foo_0, foo, false
t_foo_0, t, true
t_foo, t, false
""")
void belongs(String timerId, String tenantId, boolean expected) {
var timerDescriptor = new TimerDescriptor();
timerDescriptor.setId(timerId);
Assertions.assertThat(TimerManager.belongs(timerDescriptor, tenantId)).isEqualTo(expected);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.folio.okapi.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.folio.okapi.bean.TimerDescriptor;
import org.folio.okapi.service.TimerStore;

import io.vertx.core.Future;

public class TimerStoreMemory implements TimerStore {

private Map<String, TimerDescriptor> map = new HashMap<>();

public TimerStoreMemory() {
}

public TimerStoreMemory(TimerDescriptor timerDescriptor) {
put(timerDescriptor);
}

@Override
public Future<Void> init(boolean reset) {
if (reset) {
map.clear();
}
return Future.succeededFuture();
}

@Override
public Future<List<TimerDescriptor>> getAll() {
var list = new ArrayList<>(map.values());
return Future.succeededFuture(list);
}

@Override
public Future<Void> put(TimerDescriptor timerDescriptor) {
map.put(timerDescriptor.getId(), timerDescriptor);
return Future.succeededFuture();
}

@Override
public Future<Boolean> delete(String id) {
var timerDescriptor = map.remove(id);
return Future.succeededFuture(timerDescriptor != null);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.folio.okapi.service.impl;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;

import org.folio.okapi.bean.TimerDescriptor;
import org.folio.okapi.util.PgTestBase;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.junit5.Timeout;
import io.vertx.junit5.VertxExtension;
import io.vertx.junit5.VertxTestContext;

@Timeout(5000)
@ExtendWith(VertxExtension.class)
class TimerStorePostgresTest extends PgTestBase {

static TimerStorePostgres timerStorePostgres;

@BeforeAll
static void beforeAll(Vertx vertx) {
var conf = new JsonObject()
.put("postgres_host", POSTGRESQL_CONTAINER.getHost())
.put("postgres_port", POSTGRESQL_CONTAINER.getFirstMappedPort() + "")
.put("postgres_database", POSTGRESQL_CONTAINER.getDatabaseName())
.put("postgres_username", POSTGRESQL_CONTAINER.getUsername())
.put("postgres_password", POSTGRESQL_CONTAINER.getPassword());
var postgresHandle = new PostgresHandle(vertx, conf);
timerStorePostgres = new TimerStorePostgres(postgresHandle);
}

@Test
void test(VertxTestContext vtc) {
timerStorePostgres.init(false)
.compose(x -> timerStorePostgres.getAll())
.onComplete(vtc.succeeding(list -> assertThat(list, is(empty()))))
.compose(x -> timerStorePostgres.put(timerDescriptor("test_tenant_mod-expire_0")))
.compose(x -> timerStorePostgres.getAll())
.onComplete(vtc.succeeding(list -> {
assertThat(list, hasSize(1));
assertThat(list.get(0).getId(), is("test_tenant_mod-expire_0"));
}))
.compose(x -> timerStorePostgres.delete("test_tenant_mod-expire_0"))
.compose(x -> timerStorePostgres.getAll())
.onComplete(vtc.succeeding(list -> {
assertThat(list, is(empty()));
vtc.completeNow();
}));
}

TimerDescriptor timerDescriptor(String id) {
var timerDescriptor = new TimerDescriptor();
timerDescriptor.setId(id);
return timerDescriptor;
}
}
Loading
Loading