diff --git a/store-implementation/federated-store/pom.xml b/store-implementation/federated-store/pom.xml index bf27d431dca..e1d3af14b49 100644 --- a/store-implementation/federated-store/pom.xml +++ b/store-implementation/federated-store/pom.xml @@ -44,7 +44,6 @@ uk.gov.gchq.gaffer accumulo-store ${project.parent.version} - test uk.gov.gchq.gaffer diff --git a/store-implementation/federated-store/src/main/java/uk/gov/gchq/gaffer/federatedstore/FederatedGraphStorage.java b/store-implementation/federated-store/src/main/java/uk/gov/gchq/gaffer/federatedstore/FederatedGraphStorage.java index e3ff1944f18..278c9d7aef2 100644 --- a/store-implementation/federated-store/src/main/java/uk/gov/gchq/gaffer/federatedstore/FederatedGraphStorage.java +++ b/store-implementation/federated-store/src/main/java/uk/gov/gchq/gaffer/federatedstore/FederatedGraphStorage.java @@ -17,9 +17,13 @@ package uk.gov.gchq.gaffer.federatedstore; import com.google.common.collect.Sets; +import org.apache.accumulo.core.client.Connector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import uk.gov.gchq.gaffer.accumulostore.AccumuloProperties; +import uk.gov.gchq.gaffer.accumulostore.AccumuloStore; +import uk.gov.gchq.gaffer.accumulostore.utils.TableUtils; import uk.gov.gchq.gaffer.cache.CacheServiceLoader; import uk.gov.gchq.gaffer.cache.exception.CacheOperationException; import uk.gov.gchq.gaffer.commonutil.JsonUtil; @@ -227,7 +231,7 @@ private boolean remove(final String graphId, final Predicate accessPredicate) throws StorageException { boolean rtn; - Graph graphToMove = getGraphToMove(graphId, accessPredicate); + final Graph graphToMove = getGraphToMove(graphId, accessPredicate); if (nonNull(graphToMove)) { //remove graph to be moved + FederatedAccess oldAccess = null; for (final Entry> entry : storage.entrySet()) { entry.getValue().removeIf(graph -> graph.getGraphId().equals(graphId)); + oldAccess = entry.getKey(); } //add the graph being moved. this.put(new GraphSerialisable.Builder().graph(graphToMove).build(), newFederatedAccess); + + if (isCacheEnabled()) { + //Update cache + try { + federatedStoreCache.addGraphToCache(graphToMove, newFederatedAccess, true/*true because graphLibrary should have throw error*/); + } catch (final CacheOperationException e) { + //TODO FS recovery + String s = "Error occurred updating graphAccess. GraphStorage=updated, Cache=outdated. graphId:" + graphId; + LOGGER.error(s + " graphStorage access:{} cache access:{}", newFederatedAccess, oldAccess); + throw new StorageException(s, e); + } + } + rtn = true; } else { rtn = false; @@ -568,28 +587,20 @@ private boolean changeGraphAccess(final String graphId, final FederatedAccess ne } public boolean changeGraphId(final String graphId, final String newGraphId, final User requestingUser) throws StorageException { - final Graph graphToMove = getGraphToMove(graphId, access -> access.hasWriteAccess(requestingUser)); - return changeGraphId(graphId, newGraphId, graphToMove); + return changeGraphId(graphId, newGraphId, access -> access.hasWriteAccess(requestingUser)); } public boolean changeGraphId(final String graphId, final String newGraphId, final User requestingUser, final String adminAuth) throws StorageException { - final Graph graphToMove = getGraphToMove(graphId, access -> access.hasWriteAccess(requestingUser, adminAuth)); - return changeGraphId(graphId, newGraphId, graphToMove); - } - - - @Deprecated - public boolean changeGraphIdAsAdmin(final String graphId, final String newGraphId) throws StorageException { - final Graph graphToMove = getGraphToMove(graphId, access -> true); - return changeGraphId(graphId, newGraphId, graphToMove); + return changeGraphId(graphId, newGraphId, access -> access.hasWriteAccess(requestingUser, adminAuth)); } - private boolean changeGraphId(final String graphId, final String newGraphId, final Graph graphToMove) throws StorageException { + private boolean changeGraphId(final String graphId, final String newGraphId, final Predicate accessPredicate) throws StorageException { boolean rtn; + final Graph graphToMove = getGraphToMove(graphId, accessPredicate); if (nonNull(graphToMove)) { FederatedAccess key = null; - //remove graph to be moved + //remove graph to be moved from storage for (final Entry> entry : storage.entrySet()) { final boolean removed = entry.getValue().removeIf(graph -> graph.getGraphId().equals(graphId)); if (removed) { @@ -598,16 +609,54 @@ private boolean changeGraphId(final String graphId, final String newGraphId, fin } } - final GraphConfig configWithNewGraphId = new GraphConfig.Builder() - .json(new GraphSerialisable.Builder().graph(graphToMove).build().getConfig()) - .graphId(newGraphId) - .build(); + //Update Tables + String storeClass = graphToMove.getStoreProperties().getStoreClass(); + if (nonNull(storeClass) && storeClass.startsWith(AccumuloStore.class.getPackage().getName())) { + /* + * uk.gov.gchq.gaffer.accumulostore.[AccumuloStore, SingleUseAccumuloStore, + * SingleUseMockAccumuloStore, MockAccumuloStore, MiniAccumuloStore] + */ + try { + AccumuloProperties tmpAccumuloProps = (AccumuloProperties) graphToMove.getStoreProperties(); + Connector connection = TableUtils.getConnector(tmpAccumuloProps.getInstance(), + tmpAccumuloProps.getZookeepers(), + tmpAccumuloProps.getUser(), + tmpAccumuloProps.getPassword()); + + if (connection.tableOperations().exists(graphId)) { + connection.tableOperations().offline(graphId); + connection.tableOperations().clone(graphId, newGraphId, true, null, null); + connection.tableOperations().online(newGraphId); + connection.tableOperations().delete(graphId); + } + } catch (final Exception e) { + LOGGER.warn("Error trying to update tables for graphID:{} graphToMove:{}", graphId, graphToMove); + LOGGER.warn("Error trying to update tables.", e); + } + } + + final GraphConfig configWithNewGraphId = cloneGraphConfigWithNewGraphId(newGraphId, graphToMove); //add the graph being renamed. - this.put(new GraphSerialisable.Builder() + GraphSerialisable newGraphSerialisable = new GraphSerialisable.Builder() .graph(graphToMove) .config(configWithNewGraphId) - .build(), key); + .build(); + this.put(newGraphSerialisable, key); + + //Update cache + if (isCacheEnabled()) { + try { + federatedStoreCache.addGraphToCache(newGraphSerialisable, key, true/*true because graphLibrary should have throw error*/); + } catch (final CacheOperationException e) { + //TODO FS recovery + String s = "Error occurred updating graphId. GraphStorage=updated, Cache=outdated graphId."; + LOGGER.error(s + " graphStorage graphId:{} cache graphId:{}", newGraphId, graphId); + throw new StorageException(s, e); + } + federatedStoreCache.deleteGraphFromCache(graphId); + } + rtn = true; } else { rtn = false; @@ -615,6 +664,13 @@ private boolean changeGraphId(final String graphId, final String newGraphId, fin return rtn; } + private GraphConfig cloneGraphConfigWithNewGraphId(final String newGraphId, final Graph graphToMove) { + return new GraphConfig.Builder() + .json(new GraphSerialisable.Builder().graph(graphToMove).build().getConfig()) + .graphId(newGraphId) + .build(); + } + private Graph getGraphToMove(final String graphId, final Predicate accessPredicate) { Graph graphToMove = null; for (final Entry> entry : storage.entrySet()) { diff --git a/store-implementation/federated-store/src/main/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreCache.java b/store-implementation/federated-store/src/main/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreCache.java index f9e919c39ef..ecc2d979e02 100644 --- a/store-implementation/federated-store/src/main/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreCache.java +++ b/store-implementation/federated-store/src/main/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreCache.java @@ -55,8 +55,20 @@ public Set getAllGraphIds() { * @throws CacheOperationException if there was an error trying to add to the cache */ public void addGraphToCache(final Graph graph, final FederatedAccess access, final boolean overwrite) throws CacheOperationException { - String graphId = graph.getGraphId(); - Pair pair = new Pair<>(new GraphSerialisable.Builder().graph(graph).build(), access); + addGraphToCache(new GraphSerialisable.Builder().graph(graph).build(), access, overwrite); + } + + /** + * Add the specified {@link Graph} to the cache. + * + * @param graphSerialisable the serialised {@link Graph} to be added + * @param access Access for the graph being stored. + * @param overwrite if true, overwrite any graphs already in the cache with the same ID + * @throws CacheOperationException if there was an error trying to add to the cache + */ + public void addGraphToCache(final GraphSerialisable graphSerialisable, final FederatedAccess access, final boolean overwrite) throws CacheOperationException { + String graphId = graphSerialisable.getDeserialisedConfig().getGraphId(); + Pair pair = new Pair<>(graphSerialisable, access); try { addToCache(graphId, pair, overwrite); } catch (final CacheOperationException e) { @@ -64,6 +76,10 @@ public void addGraphToCache(final Graph graph, final FederatedAccess access, fin } } + public void deleteGraphFromCache(final String graphId) { + super.deleteFromCache(graphId); + } + /** * Retrieve the {@link Graph} with the specified ID from the cache. * @@ -88,6 +104,6 @@ public GraphSerialisable getGraphSerialisableFromCache(final String graphId) { public FederatedAccess getAccessFromCache(final String graphId) { final Pair fromCache = getFromCache(graphId); - return fromCache.getSecond(); + return (isNull(fromCache)) ? null : fromCache.getSecond(); } } diff --git a/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreCacheTest.java b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreCacheTest.java index dd14ee6142d..1cc4e4c185b 100644 --- a/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreCacheTest.java +++ b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/FederatedStoreCacheTest.java @@ -92,7 +92,7 @@ public void shouldDeleteFromCache() throws CacheOperationException { assertEquals(1, cachedGraphIds.size()); assertTrue(cachedGraphIds.contains(testGraph.getGraphId())); - federatedStoreCache.deleteFromCache(testGraph.getGraphId()); + federatedStoreCache.deleteGraphFromCache(testGraph.getGraphId()); Set cachedGraphIdsAfterDelete = federatedStoreCache.getAllGraphIds(); assertEquals(0, cachedGraphIdsAfterDelete.size()); } @@ -111,7 +111,7 @@ public void shouldThrowExceptionIfGraphAlreadyExistsInCache() throws CacheOperat @Test public void shouldThrowExceptionIfGraphIdToBeRemovedIsNull() throws CacheOperationException { federatedStoreCache.addGraphToCache(testGraph, null, false); - federatedStoreCache.deleteFromCache(null); + federatedStoreCache.deleteGraphFromCache(null); assertEquals(1, federatedStoreCache.getAllGraphIds().size()); } diff --git a/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/AbstractStandaloneFederatedStoreIT.java b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/AbstractStandaloneFederatedStoreIT.java index ce64d9b7adb..d95f65b12ae 100644 --- a/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/AbstractStandaloneFederatedStoreIT.java +++ b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/AbstractStandaloneFederatedStoreIT.java @@ -16,8 +16,10 @@ package uk.gov.gchq.gaffer.federatedstore.integration; +import org.junit.AfterClass; import org.junit.jupiter.api.BeforeEach; +import uk.gov.gchq.gaffer.cache.CacheServiceLoader; import uk.gov.gchq.gaffer.commonutil.StreamUtil; import uk.gov.gchq.gaffer.federatedstore.FederatedStoreProperties; import uk.gov.gchq.gaffer.graph.Graph; @@ -36,6 +38,16 @@ public void setUp() throws Exception { _setUp(); } + @AfterClass + public void tearDown() throws Exception { + CacheServiceLoader.shutdown(); + _tearDown(); + } + + private void _tearDown() throws Exception { + } + + protected void _setUp() throws Exception { // Override if required; } diff --git a/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/FederatedAdminIT.java b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/FederatedAdminIT.java index af0df80323f..96ce161d629 100644 --- a/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/FederatedAdminIT.java +++ b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/FederatedAdminIT.java @@ -17,11 +17,14 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import org.apache.accumulo.core.client.Connector; import org.junit.jupiter.api.Test; import uk.gov.gchq.gaffer.accumulostore.AccumuloProperties; +import uk.gov.gchq.gaffer.accumulostore.utils.TableUtils; import uk.gov.gchq.gaffer.commonutil.StreamUtil; import uk.gov.gchq.gaffer.federatedstore.FederatedAccess; +import uk.gov.gchq.gaffer.federatedstore.FederatedStoreCache; import uk.gov.gchq.gaffer.federatedstore.FederatedStoreConstants; import uk.gov.gchq.gaffer.federatedstore.PublicAccessPredefinedFederatedStore; import uk.gov.gchq.gaffer.federatedstore.operation.AddGraph; @@ -30,16 +33,23 @@ import uk.gov.gchq.gaffer.federatedstore.operation.GetAllGraphIds; import uk.gov.gchq.gaffer.federatedstore.operation.GetAllGraphInfo; import uk.gov.gchq.gaffer.federatedstore.operation.RemoveGraph; +import uk.gov.gchq.gaffer.graph.GraphSerialisable; import uk.gov.gchq.gaffer.integration.AbstractStoreIT; +import uk.gov.gchq.gaffer.jsonserialisation.JSONSerialiser; import uk.gov.gchq.gaffer.store.schema.Schema; import uk.gov.gchq.gaffer.user.User; +import java.util.ArrayList; import java.util.Collections; import java.util.Map; +import java.util.Set; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static uk.gov.gchq.gaffer.federatedstore.FederatedStoreConstants.KEY_OPERATION_OPTIONS_GRAPH_IDS; @@ -48,7 +58,8 @@ public class FederatedAdminIT extends AbstractStandaloneFederatedStoreIT { public static final User ADMIN_USER = new User("admin", Collections.EMPTY_SET, Sets.newHashSet("AdminAuth")); public static final User NOT_ADMIN_USER = new User("admin", Collections.EMPTY_SET, Sets.newHashSet("NotAdminAuth")); - private static Class currentClass = new Object() { }.getClass().getEnclosingClass(); + private static Class currentClass = new Object() { + }.getClass().getEnclosingClass(); private static final AccumuloProperties ACCUMULO_PROPERTIES = AccumuloProperties.loadStoreProperties( StreamUtil.openStream(currentClass, "properties/singleUseAccumuloStore.properties")); @@ -70,6 +81,53 @@ public void _setUp() throws Exception { .build(), user); } + @Test + public void shouldRemoveGraphFromStorage() throws Exception { + //given + final String graphA = "graphA"; + graph.execute(new AddGraph.Builder() + .graphId(graphA) + .schema(new Schema()) + .storeProperties(ACCUMULO_PROPERTIES) + .build(), user); + assertTrue(Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).contains(graphA)); + + //when + final Boolean removed = graph.execute(new RemoveGraph.Builder() + .graphId(graphA) + .build(), user); + + //then + assertTrue(removed); + assertEquals(0, Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).size()); + + } + + @Test + public void shouldRemoveGraphFromCache() throws Exception { + //given + FederatedStoreCache federatedStoreCache = new FederatedStoreCache(); + final String graphA = "graphA"; + graph.execute(new AddGraph.Builder() + .graphId(graphA) + .schema(new Schema()) + .storeProperties(ACCUMULO_PROPERTIES) + .build(), user); + assertTrue(Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).contains(graphA)); + + //when + assertNotNull(federatedStoreCache.getGraphSerialisableFromCache(graphA)); + final Boolean removed = graph.execute(new RemoveGraph.Builder() + .graphId(graphA) + .build(), user); + + //then + assertTrue(removed); + GraphSerialisable graphSerialisableFromCache = federatedStoreCache.getGraphSerialisableFromCache(graphA); + assertNull(new String(JSONSerialiser.serialise(graphSerialisableFromCache, true)), graphSerialisableFromCache); + assertEquals(0, federatedStoreCache.getAllGraphIds().size()); + } + @Test public void shouldRemoveGraphForAdmin() throws Exception { //given @@ -384,8 +442,13 @@ public void shouldNotChangeGraphUserFromSomeoneElseToReplacementUserAsNonAdminWh @Test public void shouldChangeGraphIdForOwnGraph() throws Exception { //given - final String graphA = "graphA"; - final String graphB = "graphB"; + final String graphA = "graphTableA"; + final String graphB = "graphTableB"; + Connector connector = TableUtils.getConnector(ACCUMULO_PROPERTIES.getInstance(), + ACCUMULO_PROPERTIES.getZookeepers(), + ACCUMULO_PROPERTIES.getUser(), + ACCUMULO_PROPERTIES.getPassword()); + graph.execute(new AddGraph.Builder() .graphId(graphA) .schema(new Schema()) @@ -395,15 +458,25 @@ public void shouldChangeGraphIdForOwnGraph() throws Exception { assertTrue(Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).contains(graphA)); //when + boolean tableGraphABefore = connector.tableOperations().exists(graphA); + boolean tableGraphBBefore = connector.tableOperations().exists(graphB); + final Boolean changed = graph.execute(new ChangeGraphId.Builder() .graphId(graphA) .newGraphId(graphB) .build(), user); + boolean tableGraphAfter = connector.tableOperations().exists(graphA); + boolean tableGraphBAfter = connector.tableOperations().exists(graphB); + //then assertTrue(changed); assertFalse(Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).contains(graphA)); assertTrue(Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).contains(graphB)); + assertTrue(tableGraphABefore); + assertFalse(tableGraphBBefore); + assertFalse(tableGraphAfter); + assertTrue(tableGraphBAfter); } @@ -489,4 +562,120 @@ public void shouldNotChangeGraphIdForNonOwnedGraphAsNonAdminWhenRequestingAdminA assertFalse(Lists.newArrayList(graph.execute(new GetAllGraphIds(), otherUser)).contains(graphA)); assertFalse(Lists.newArrayList(graph.execute(new GetAllGraphIds(), otherUser)).contains(graphB)); } + + @Test + public void shouldStartWithEmptyCache() throws Exception { + //given + FederatedStoreCache federatedStoreCache = new FederatedStoreCache(); + + //then + assertEquals(0, federatedStoreCache.getAllGraphIds().size()); + } + + @Test + public void shouldChangeGraphIdInStorage() throws Exception { + //given + String newName = "newName"; + final String graphA = "graphA"; + graph.execute(new AddGraph.Builder() + .graphId(graphA) + .schema(new Schema()) + .storeProperties(ACCUMULO_PROPERTIES) + .build(), user); + assertTrue(Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).contains(graphA)); + + //when + final Boolean changed = graph.execute(new ChangeGraphId.Builder() + .graphId(graphA) + .newGraphId(newName) + .build(), user); + + //then + ArrayList graphIds = Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)); + + assertTrue(changed); + assertEquals(1, graphIds.size()); + assertArrayEquals(new String[]{newName}, graphIds.toArray()); + } + + @Test + public void shouldChangeGraphIdInCache() throws Exception { + //given + String newName = "newName"; + FederatedStoreCache federatedStoreCache = new FederatedStoreCache(); + final String graphA = "graphA"; + graph.execute(new AddGraph.Builder() + .graphId(graphA) + .schema(new Schema()) + .storeProperties(ACCUMULO_PROPERTIES) + .build(), user); + assertTrue(Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).contains(graphA)); + + //when + final Boolean changed = graph.execute(new ChangeGraphId.Builder() + .graphId(graphA) + .newGraphId(newName) + .build(), user); + + //then + Set graphIds = federatedStoreCache.getAllGraphIds(); + + assertTrue(changed); + assertArrayEquals(graphIds.toString(), new String[]{newName}, graphIds.toArray()); + } + + @Test + public void shouldChangeGraphAccessIdInStorage() throws Exception { + //given + final String graphA = "graphA"; + graph.execute(new AddGraph.Builder() + .graphId(graphA) + .schema(new Schema()) + .storeProperties(ACCUMULO_PROPERTIES) + .build(), user); + assertTrue(Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).contains(graphA)); + + //when + final Boolean changed = graph.execute(new ChangeGraphAccess.Builder() + .graphId(graphA) + .ownerUserId(NOT_ADMIN_USER.getUserId()) + .build(), user); + + //then + ArrayList userGraphIds = Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)); + ArrayList otherUserGraphIds = Lists.newArrayList(graph.execute(new GetAllGraphIds(), NOT_ADMIN_USER)); + + assertTrue(changed); + assertEquals(0, userGraphIds.size()); + assertEquals(1, otherUserGraphIds.size()); + assertArrayEquals(new String[]{graphA}, otherUserGraphIds.toArray()); + } + + @Test + public void shouldChangeGraphAccessIdInCache() throws Exception { + //given + FederatedStoreCache federatedStoreCache = new FederatedStoreCache(); + final String graphA = "graphA"; + graph.execute(new AddGraph.Builder() + .graphId(graphA) + .schema(new Schema()) + .storeProperties(ACCUMULO_PROPERTIES) + .build(), user); + assertTrue(Lists.newArrayList(graph.execute(new GetAllGraphIds(), user)).contains(graphA)); + + //when + FederatedAccess before = federatedStoreCache.getAccessFromCache(graphA); + final Boolean changed = graph.execute(new ChangeGraphAccess.Builder() + .graphId(graphA) + .ownerUserId(ADMIN_USER.getUserId()) + .build(), user); + FederatedAccess after = federatedStoreCache.getAccessFromCache(graphA); + + //then + assertTrue(changed); + assertNotEquals(before, after); + assertEquals(user.getUserId(), before.getAddingUserId()); + assertEquals(ADMIN_USER.getUserId(), after.getAddingUserId()); + } + } diff --git a/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/FederatedStoreRecursionIT.java b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/FederatedStoreRecursionIT.java index b11ba18e3e5..3dd87a925b4 100644 --- a/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/FederatedStoreRecursionIT.java +++ b/store-implementation/federated-store/src/test/java/uk/gov/gchq/gaffer/federatedstore/integration/FederatedStoreRecursionIT.java @@ -64,7 +64,7 @@ public class FederatedStoreRecursionIT { private User user = new User(); @Test - @Timeout(value = 60) + @Timeout(value = 20000) public void shouldNotInfinityLoopWhenAddingElements() throws Exception { /* * Structure: