Skip to content

Commit

Permalink
Elide Async Feature Unit and Integration Tests (#1311)
Browse files Browse the repository at this point in the history
* Added integration,unit test setup for Async Module with few tests

Co-authored-by: moizarafat <[email protected]>

* Adding Licence headers, Sample entity and working Async test for POST,GET

Co-authored-by: moizarafat <[email protected]>

* Adding additional integration tests

Co-authored-by: moizarafat <[email protected]>

* Adding remaining integration tests and updating javadocs

Co-authored-by: moizarafat <[email protected]>

* Adding all unit tests

Co-authored-by: moizarafat <[email protected]>

* Updating tests to work with Singleton changes

Co-authored-by: moizarafat <[email protected]>

* Modifying tests to work with singleton logic

Co-authored-by: moizarafat <[email protected]>

* Resolving some codacy errors

Co-authored-by: moizarafat <[email protected]>

* Adding DSL for GraphQL and addressing review comments

Co-authored-by: moizarafat <[email protected]>

* Fixing unit tests and checkstyle errors

Co-authored-by: moizarafat <[email protected]>

* Moving tests to new format

Co-authored-by: moizarafat <[email protected]>

* Fixing session not closed error and checkstyle errors

* Fixing imports

Co-authored-by: moizarafat <[email protected]>

* Removing unused method and updating harness

Co-authored-by: moizarafat <[email protected]>

* Updating unit tests

Co-authored-by: moizarafat <[email protected]>

* Updating ResourceConfig for AsyncTest

Co-authored-by: moizarafat <[email protected]>

* Moving logic for resource config to new test binder

Co-authored-by: moizarafat <[email protected]>

* Adding bindFactory logic for async services

Co-authored-by: moizarafat <[email protected]>

* Fixing IT

* Fixing IT

Co-authored-by: Abhino <[email protected]>

* Adding Remaining integration tests

Co-authored-by: moizarafat <[email protected]>

* Consolidating filter logic for integration tests

Co-authored-by: moizarafat <[email protected]>

* Fixing legacy Hibernate entity manager store (so it doesn't recycle the entity manager

* Adding additional integration tests for Standalone and Spring boot

Co-authored-by: moizarafat <[email protected]>

* Fixing codacy errors

Co-authored-by: moizarafat <[email protected]>

* Review Comments

Co-authored-by: abhino <[email protected]>

Co-authored-by: avijay <[email protected]>
Co-authored-by: moizarafat <[email protected]>
Co-authored-by: moiz arafat <[email protected]>
Co-authored-by: Aaron Klish <[email protected]>
  • Loading branch information
5 people authored May 11, 2020
1 parent a98f8f9 commit 39767cd
Show file tree
Hide file tree
Showing 31 changed files with 1,558 additions and 100 deletions.
41 changes: 41 additions & 0 deletions elide-async/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,65 @@
<artifactId>elide-graphql</artifactId>
<version>5.0.0-pr9-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>

<!-- Test -->
<!-- JUnit -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>

<!-- H2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>

<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<reuseForks>false</reuseForks>
<forkCount>1</forkCount>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
import com.yahoo.elide.security.ChangeSpec;
import com.yahoo.elide.security.RequestScope;

import java.security.Principal;
import java.util.Optional;

public class UpdatePrincipalNameHook implements LifeCycleHook<AsyncQuery> {

@Override
public void execute(LifeCycleHookBinding.Operation operation, AsyncQuery query,
RequestScope requestScope, Optional<ChangeSpec> changes) {
query.setPrincipalName(requestScope.getUser().getName());
Principal principal = requestScope.getUser().getPrincipal();
if (principal != null) {
query.setPrincipalName(principal.getName());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
public class AsyncQuery extends AsyncBase implements PrincipalOwned {
@Id
@Column(columnDefinition = "varchar(36)")
private UUID id; //Can be generated or provided.
private UUID id; //Provided.

private String query; //JSON-API PATH or GraphQL payload.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ public static class AsyncQueryOwner extends OperationCheck<Object> {
@Override
public boolean ok(Object object, RequestScope requestScope, Optional<ChangeSpec> changeSpec) {
Principal principal = requestScope.getUser().getPrincipal();
return ((PrincipalOwned) object).getPrincipalName().equals(principal.getName());
boolean status = false;
String principalName = ((PrincipalOwned) object).getPrincipalName();
if (principalName == null && (principal == null || principal.getName() == null)) {
status = true;
} else {
status = principalName.equals(principal.getName());
}
return status;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

/**
* Service to execute Async queries.
* It will schedule task to track long running queries and kills them.
Expand All @@ -40,7 +39,7 @@ private AsyncCleanerService(Elide elide, Integer maxRunTimeMinutes, Integer quer
// Setting up query cleaner that marks long running query as TIMEDOUT.
ScheduledExecutorService cleaner = Executors.newSingleThreadScheduledExecutor();
AsyncQueryCleanerThread cleanUpTask = new AsyncQueryCleanerThread(queryRunTimeThresholdMinutes, elide,
queryCleanupDays, asyncQueryDao);
queryCleanupDays, asyncQueryDao);

// Since there will be multiple hosts running the elide service,
// setting up random delays to avoid all of them trying to cleanup at the same time.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.yahoo.elide.graphql.QueryRunner;
import com.yahoo.elide.security.User;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
Expand All @@ -27,6 +28,7 @@
* It will also schedule task to update orphan query statuses after
* host/app crash or restart.
*/
@Getter
@Slf4j
public class AsyncExecutorService {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void run() {
* This method deletes the historical queries based on threshold.
* */
@SuppressWarnings("unchecked")
private void deleteAsyncQuery() {
protected void deleteAsyncQuery() {

String cleanupDateFormatted = evaluateFormattedFilterDate(Calendar.DATE, queryCleanupDays);

Expand All @@ -57,7 +57,7 @@ private void deleteAsyncQuery() {
* were interrupted due to host crash/app shutdown to TIMEDOUT.
* */
@SuppressWarnings("unchecked")
private void timeoutAsyncQuery() {
protected void timeoutAsyncQuery() {

String filterDateFormatted = evaluateFormattedFilterDate(Calendar.MINUTE, maxRunTimeMinutes);
String filterExpression = "status=in=(" + QueryStatus.PROCESSING.toString() + ","
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.yahoo.elide.jsonapi.models.JsonApiDocument;
import com.yahoo.elide.request.EntityProjection;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

Expand All @@ -38,10 +39,13 @@
*/
@Singleton
@Slf4j
@Getter
public class DefaultAsyncQueryDAO implements AsyncQueryDAO {

@Setter private Elide elide;
@Setter private DataStore dataStore;
private EntityDictionary dictionary;
private RSQLFilterDialect filterParser;

// Default constructor is needed for standalone implementation for override in getAsyncQueryDao
public DefaultAsyncQueryDAO() {
Expand All @@ -50,6 +54,8 @@ public DefaultAsyncQueryDAO() {
public DefaultAsyncQueryDAO(Elide elide, DataStore dataStore) {
this.elide = elide;
this.dataStore = dataStore;
dictionary = elide.getElideSettings().getDictionary();
filterParser = new RSQLFilterDialect(dictionary);
}

@Override
Expand Down Expand Up @@ -94,8 +100,6 @@ public Collection<AsyncQuery> updateStatusAsyncQueryCollection(String filterExpr
private Collection<AsyncQuery> updateAsyncQueryCollection(String filterExpression,
UpdateQuery updateFunction) {
log.debug("updateAsyncQueryCollection");
EntityDictionary dictionary = elide.getElideSettings().getDictionary();
RSQLFilterDialect filterParser = new RSQLFilterDialect(dictionary);

Collection<AsyncQuery> asyncQueryList = null;

Expand All @@ -117,7 +121,7 @@ private Collection<AsyncQuery> updateAsyncQueryCollection(String filterExpressio
updateFunction.update(query);
tx.save(query, scope);
}
return itr;
return loaded;
});
} catch (ParseException e) {
log.error("Exception: {}", e);
Expand All @@ -129,13 +133,13 @@ private Collection<AsyncQuery> updateAsyncQueryCollection(String filterExpressio
@SuppressWarnings("unchecked")
public Collection<AsyncQuery> deleteAsyncQueryAndResultCollection(String filterExpression) {
log.debug("deleteAsyncQueryAndResultCollection");
EntityDictionary dictionary = elide.getElideSettings().getDictionary();
RSQLFilterDialect filterParser = new RSQLFilterDialect(dictionary);

Collection<AsyncQuery> asyncQueryList = null;

try {
FilterExpression filter = filterParser.parseFilterExpression(filterExpression,
AsyncQuery.class, false);
executeInTransaction(dataStore, (tx, scope) -> {
asyncQueryList = (Collection<AsyncQuery>) executeInTransaction(dataStore, (tx, scope) -> {

EntityProjection asyncQueryCollection = EntityProjection.builder()
.type(AsyncQuery.class)
Expand All @@ -151,12 +155,12 @@ public Collection<AsyncQuery> deleteAsyncQueryAndResultCollection(String filterE
tx.delete(query, scope);
}
}
return null;
return loaded;
});
} catch (ParseException e) {
log.error("Exception: {}", e);
}
return null;
return asyncQueryList;
}

@Override
Expand Down Expand Up @@ -194,8 +198,8 @@ protected Object executeInTransaction(DataStore dataStore, Transactional action)
RequestScope scope = new RequestScope("query", NO_VERSION, jsonApiDoc,
tx, null, queryParams, elide.getElideSettings());
result = action.execute(tx, scope);
tx.commit(scope);
tx.flush(scope);
tx.commit(scope);
} catch (IOException e) {
log.error("IOException: {}", e);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2020, Yahoo Inc.
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root for terms.
*/
package com.yahoo.elide.async.service;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.mock;

import com.yahoo.elide.Elide;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class AsyncCleanerServiceTest {

private AsyncCleanerService service;

@BeforeAll
public void setupMocks() {
Elide elide = mock(Elide.class);

AsyncQueryDAO dao = mock(DefaultAsyncQueryDAO.class);
AsyncCleanerService.init(elide, 5, 60, dao);
service = AsyncCleanerService.getInstance();
}

@Test
public void testCleanerSet() {
assertNotNull(service);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2020, Yahoo Inc.
* Licensed under the Apache License, Version 2.0
* See LICENSE file in project root for terms.
*/
package com.yahoo.elide.async.service;

import static com.yahoo.elide.core.EntityDictionary.NO_VERSION;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import com.yahoo.elide.Elide;
import com.yahoo.elide.ElideSettingsBuilder;
import com.yahoo.elide.async.models.AsyncQuery;
import com.yahoo.elide.async.models.QueryStatus;
import com.yahoo.elide.core.EntityDictionary;
import com.yahoo.elide.core.datastore.inmemory.HashMapDataStore;
import com.yahoo.elide.security.User;
import com.yahoo.elide.security.checks.Check;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

import java.util.HashMap;
import java.util.Map;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class AsyncExecutorServiceTest {

private AsyncExecutorService service;
private Elide elide;
private AsyncQueryDAO asyncQueryDao;

@BeforeAll
public void setupMocks() {
HashMapDataStore inMemoryStore = new HashMapDataStore(AsyncQuery.class.getPackage());
Map<String, Class<? extends Check>> checkMappings = new HashMap<>();

elide = new Elide(
new ElideSettingsBuilder(inMemoryStore)
.withEntityDictionary(new EntityDictionary(checkMappings))
.build());

asyncQueryDao = mock(DefaultAsyncQueryDAO.class);

AsyncExecutorService.init(elide, 5, 60, asyncQueryDao);

service = AsyncExecutorService.getInstance();
}

@Test
public void testAsyncExecutorServiceSet() {
assertEquals(elide, service.getElide());
assertNotNull(service.getRunners());
assertEquals(60, service.getMaxRunTime());
assertNotNull(service.getExecutor());
assertNotNull(service.getInterruptor());
assertEquals(asyncQueryDao, service.getAsyncQueryDao());
}

@Test
public void testExecuteQuery() {
AsyncQuery queryObj = mock(AsyncQuery.class);
User testUser = mock(User.class);

service.executeQuery(queryObj, testUser, NO_VERSION);

verify(asyncQueryDao, times(0)).updateStatus(queryObj, QueryStatus.QUEUED);
}
}
Loading

0 comments on commit 39767cd

Please sign in to comment.