MINOR: fix: scheduled apps (#17167)

* fix: scheduled apps

run scheduling procedures for apps with Scheduled or ScheduledOrManual schedule type

* format
This commit is contained in:
Imri Paran 2024-07-25 15:46:01 +02:00 committed by GitHub
parent 70f0268e26
commit 012aa9b804
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 71 additions and 44 deletions

View File

@ -3,6 +3,7 @@ package org.openmetadata.service.apps;
import static org.openmetadata.service.apps.scheduler.AbstractOmAppJobListener.JOB_LISTENER_NAME; import static org.openmetadata.service.apps.scheduler.AbstractOmAppJobListener.JOB_LISTENER_NAME;
import static org.openmetadata.service.apps.scheduler.AppScheduler.APP_NAME; import static org.openmetadata.service.apps.scheduler.AppScheduler.APP_NAME;
import static org.openmetadata.service.exception.CatalogExceptionMessage.NO_MANUAL_TRIGGER_ERR; import static org.openmetadata.service.exception.CatalogExceptionMessage.NO_MANUAL_TRIGGER_ERR;
import static org.openmetadata.service.resources.apps.AppResource.SCHEDULED_TYPES;
import java.util.List; import java.util.List;
import lombok.Getter; import lombok.Getter;
@ -24,6 +25,7 @@ import org.openmetadata.schema.metadataIngestion.ApplicationPipeline;
import org.openmetadata.schema.metadataIngestion.SourceConfig; import org.openmetadata.schema.metadataIngestion.SourceConfig;
import org.openmetadata.schema.services.connections.metadata.OpenMetadataConnection; import org.openmetadata.schema.services.connections.metadata.OpenMetadataConnection;
import org.openmetadata.schema.type.EntityReference; import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.ProviderType; import org.openmetadata.schema.type.ProviderType;
import org.openmetadata.schema.type.Relationship; import org.openmetadata.schema.type.Relationship;
import org.openmetadata.service.Entity; import org.openmetadata.service.Entity;
@ -68,8 +70,8 @@ public class AbstractNativeApplication implements NativeApplication {
&& app.getAppSchedule().getScheduleTimeline().equals(ScheduleTimeline.NONE))) { && app.getAppSchedule().getScheduleTimeline().equals(ScheduleTimeline.NONE))) {
return; return;
} }
if (app.getAppType() == AppType.Internal if (app.getAppType().equals(AppType.Internal)
&& app.getScheduleType().equals(ScheduleType.Scheduled)) { && (SCHEDULED_TYPES.contains(app.getScheduleType()))) {
try { try {
ApplicationHandler.getInstance().removeOldJobs(app); ApplicationHandler.getInstance().removeOldJobs(app);
ApplicationHandler.getInstance().migrateQuartzConfig(app); ApplicationHandler.getInstance().migrateQuartzConfig(app);
@ -82,7 +84,7 @@ public class AbstractNativeApplication implements NativeApplication {
} }
scheduleInternal(); scheduleInternal();
} else if (app.getAppType() == AppType.External } else if (app.getAppType() == AppType.External
&& app.getScheduleType().equals(ScheduleType.Scheduled)) { && (SCHEDULED_TYPES.contains(app.getScheduleType()))) {
scheduleExternal(); scheduleExternal();
} }
} }
@ -114,6 +116,7 @@ public class AbstractNativeApplication implements NativeApplication {
try { try {
bindExistingIngestionToApplication(ingestionPipelineRepository); bindExistingIngestionToApplication(ingestionPipelineRepository);
updateAppConfig(ingestionPipelineRepository, this.getApp().getAppConfiguration());
} catch (EntityNotFoundException ex) { } catch (EntityNotFoundException ex) {
ApplicationConfig config = ApplicationConfig config =
JsonUtils.convertValue(this.getApp().getAppConfiguration(), ApplicationConfig.class); JsonUtils.convertValue(this.getApp().getAppConfiguration(), ApplicationConfig.class);
@ -151,6 +154,17 @@ public class AbstractNativeApplication implements NativeApplication {
} }
} }
private void updateAppConfig(IngestionPipelineRepository repository, Object appConfiguration) {
String fqn = FullyQualifiedName.add(SERVICE_NAME, this.getApp().getName());
IngestionPipeline updated = repository.findByName(fqn, Include.NON_DELETED);
ApplicationPipeline appPipeline =
JsonUtils.convertValue(updated.getSourceConfig().getConfig(), ApplicationPipeline.class);
IngestionPipeline original = JsonUtils.deepCopy(updated, IngestionPipeline.class);
updated.setSourceConfig(
updated.getSourceConfig().withConfig(appPipeline.withAppConfig(appConfiguration)));
repository.update(null, original, updated);
}
private void createAndBindIngestionPipeline( private void createAndBindIngestionPipeline(
IngestionPipelineRepository ingestionPipelineRepository, ApplicationConfig config) { IngestionPipelineRepository ingestionPipelineRepository, ApplicationConfig config) {
MetadataServiceRepository serviceEntityRepository = MetadataServiceRepository serviceEntityRepository =

View File

@ -101,6 +101,8 @@ public class AppResource extends EntityResource<App, AppRepository> {
private PipelineServiceClientInterface pipelineServiceClient; private PipelineServiceClientInterface pipelineServiceClient;
static final String FIELDS = "owner"; static final String FIELDS = "owner";
private SearchRepository searchRepository; private SearchRepository searchRepository;
public static List<ScheduleType> SCHEDULED_TYPES =
List.of(ScheduleType.Scheduled, ScheduleType.ScheduledOrManual);
@Override @Override
public void initialize(OpenMetadataApplicationConfig config) { public void initialize(OpenMetadataApplicationConfig config) {
@ -145,7 +147,7 @@ public class AppResource extends EntityResource<App, AppRepository> {
} }
// Schedule // Schedule
if (app.getScheduleType().equals(ScheduleType.Scheduled)) { if (SCHEDULED_TYPES.contains(app.getScheduleType())) {
ApplicationHandler.getInstance() ApplicationHandler.getInstance()
.installApplication(app, Entity.getCollectionDAO(), searchRepository); .installApplication(app, Entity.getCollectionDAO(), searchRepository);
} }
@ -559,7 +561,7 @@ public class AppResource extends EntityResource<App, AppRepository> {
securityContext, securityContext,
getResourceContext(), getResourceContext(),
new OperationContext(Entity.APPLICATION, MetadataOperation.CREATE)); new OperationContext(Entity.APPLICATION, MetadataOperation.CREATE));
if (app.getScheduleType().equals(ScheduleType.Scheduled)) { if (SCHEDULED_TYPES.contains(app.getScheduleType())) {
ApplicationHandler.getInstance() ApplicationHandler.getInstance()
.installApplication(app, Entity.getCollectionDAO(), searchRepository); .installApplication(app, Entity.getCollectionDAO(), searchRepository);
ApplicationHandler.getInstance() ApplicationHandler.getInstance()
@ -604,7 +606,7 @@ public class AppResource extends EntityResource<App, AppRepository> {
AppScheduler.getInstance().deleteScheduledApplication(app); AppScheduler.getInstance().deleteScheduledApplication(app);
Response response = patchInternal(uriInfo, securityContext, id, patch); Response response = patchInternal(uriInfo, securityContext, id, patch);
App updatedApp = (App) response.getEntity(); App updatedApp = (App) response.getEntity();
if (app.getScheduleType().equals(ScheduleType.Scheduled)) { if (SCHEDULED_TYPES.contains(app.getScheduleType())) {
ApplicationHandler.getInstance() ApplicationHandler.getInstance()
.installApplication(updatedApp, Entity.getCollectionDAO(), searchRepository); .installApplication(updatedApp, Entity.getCollectionDAO(), searchRepository);
} }
@ -648,7 +650,7 @@ public class AppResource extends EntityResource<App, AppRepository> {
AppScheduler.getInstance().deleteScheduledApplication(app); AppScheduler.getInstance().deleteScheduledApplication(app);
Response response = patchInternal(uriInfo, securityContext, fqn, patch); Response response = patchInternal(uriInfo, securityContext, fqn, patch);
App updatedApp = (App) response.getEntity(); App updatedApp = (App) response.getEntity();
if (app.getScheduleType().equals(ScheduleType.Scheduled)) { if (SCHEDULED_TYPES.contains(app.getScheduleType())) {
ApplicationHandler.getInstance() ApplicationHandler.getInstance()
.installApplication(updatedApp, Entity.getCollectionDAO(), searchRepository); .installApplication(updatedApp, Entity.getCollectionDAO(), searchRepository);
} }
@ -683,7 +685,7 @@ public class AppResource extends EntityResource<App, AppRepository> {
new EntityUtil.Fields(repository.getMarketPlace().getAllowedFields())); new EntityUtil.Fields(repository.getMarketPlace().getAllowedFields()));
App app = getApplication(definition, create, securityContext.getUserPrincipal().getName()); App app = getApplication(definition, create, securityContext.getUserPrincipal().getName());
AppScheduler.getInstance().deleteScheduledApplication(app); AppScheduler.getInstance().deleteScheduledApplication(app);
if (app.getScheduleType().equals(ScheduleType.Scheduled)) { if (SCHEDULED_TYPES.contains(app.getScheduleType())) {
ApplicationHandler.getInstance() ApplicationHandler.getInstance()
.installApplication(app, Entity.getCollectionDAO(), searchRepository); .installApplication(app, Entity.getCollectionDAO(), searchRepository);
} }
@ -782,7 +784,7 @@ public class AppResource extends EntityResource<App, AppRepository> {
Response response = restoreEntity(uriInfo, securityContext, restore.getId()); Response response = restoreEntity(uriInfo, securityContext, restore.getId());
if (response.getStatus() == Response.Status.OK.getStatusCode()) { if (response.getStatus() == Response.Status.OK.getStatusCode()) {
App app = (App) response.getEntity(); App app = (App) response.getEntity();
if (app.getScheduleType().equals(ScheduleType.Scheduled)) { if (SCHEDULED_TYPES.contains(app.getScheduleType())) {
ApplicationHandler.getInstance() ApplicationHandler.getInstance()
.installApplication(app, Entity.getCollectionDAO(), searchRepository); .installApplication(app, Entity.getCollectionDAO(), searchRepository);
} }
@ -818,7 +820,7 @@ public class AppResource extends EntityResource<App, AppRepository> {
@Context SecurityContext securityContext) { @Context SecurityContext securityContext) {
App app = App app =
repository.getByName(uriInfo, name, new EntityUtil.Fields(repository.getAllowedFields())); repository.getByName(uriInfo, name, new EntityUtil.Fields(repository.getAllowedFields()));
if (app.getScheduleType().equals(ScheduleType.Scheduled)) { if (SCHEDULED_TYPES.contains(app.getScheduleType())) {
ApplicationHandler.getInstance() ApplicationHandler.getInstance()
.installApplication(app, repository.getDaoCollection(), searchRepository); .installApplication(app, repository.getDaoCollection(), searchRepository);
return Response.status(Response.Status.OK).entity("App is Scheduled.").build(); return Response.status(Response.Status.OK).entity("App is Scheduled.").build();

View File

@ -3,12 +3,16 @@ package org.openmetadata.service.resources.apps;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST; import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.OK; import static javax.ws.rs.core.Response.Status.OK;
import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS; import static org.openmetadata.service.util.TestUtils.ADMIN_AUTH_HEADERS;
import static org.openmetadata.service.util.TestUtils.assertEventually;
import static org.openmetadata.service.util.TestUtils.assertResponseContains; import static org.openmetadata.service.util.TestUtils.assertResponseContains;
import static org.openmetadata.service.util.TestUtils.readResponse; import static org.openmetadata.service.util.TestUtils.readResponse;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.retry.RetryRegistry;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.Objects;
import javax.ws.rs.client.WebTarget; import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import lombok.SneakyThrows; import lombok.SneakyThrows;
@ -26,6 +30,7 @@ import org.openmetadata.service.Entity;
import org.openmetadata.service.exception.EntityNotFoundException; import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.resources.EntityResourceTest; import org.openmetadata.service.resources.EntityResourceTest;
import org.openmetadata.service.security.SecurityUtil; import org.openmetadata.service.security.SecurityUtil;
import org.openmetadata.service.util.RetryableAssertionError;
import org.openmetadata.service.util.TestUtils; import org.openmetadata.service.util.TestUtils;
@Slf4j @Slf4j
@ -38,6 +43,14 @@ public class AppsResourceTest extends EntityResourceTest<App, CreateApp> {
supportedNameCharacters = "_-."; supportedNameCharacters = "_-.";
} }
public static RetryRegistry appTriggerRetry =
RetryRegistry.of(
RetryConfig.custom()
.maxAttempts(60) // about 30 seconds
.waitDuration(Duration.ofMillis(500))
.retryExceptions(RetryableAssertionError.class)
.build());
@Override @Override
@SneakyThrows @SneakyThrows
public CreateApp createRequest(String name) { public CreateApp createRequest(String name) {
@ -78,34 +91,31 @@ public class AppsResourceTest extends EntityResourceTest<App, CreateApp> {
} }
@Test @Test
void post_trigger_app_200() throws HttpResponseException, InterruptedException { void post_trigger_app_200() throws HttpResponseException {
postTriggerApp("SearchIndexingApplication", ADMIN_AUTH_HEADERS); String appName = "SearchIndexingApplication";
AppRunRecord latestRun = null; postTriggerApp(appName, ADMIN_AUTH_HEADERS);
while (latestRun == null) { assertAppRanAfterTrigger(appName);
try { }
latestRun = getLatestAppRun("SearchIndexingApplication", ADMIN_AUTH_HEADERS);
Thread.sleep(1000); private void assertAppRanAfterTrigger(String appName) {
} catch (HttpResponseException ex) { assertEventually(
LOG.info("Waiting for the app to start running"); "appIsRunning",
} () -> {
} try {
assert latestRun.getStatus().equals(AppRunRecord.Status.RUNNING); assert Objects.nonNull(getLatestAppRun(appName, ADMIN_AUTH_HEADERS));
TimeUnit timeout = TimeUnit.SECONDS; } catch (HttpResponseException ex) {
long timeoutValue = 30; throw new AssertionError(ex);
long startTime = System.currentTimeMillis(); }
while (latestRun.getStatus().equals(AppRunRecord.Status.RUNNING)) { },
// skip this loop in CI because it causes weird problems appTriggerRetry);
if (TestUtils.isCI()) { assertEventually(
break; "appSuccess",
} () -> {
assert !latestRun.getStatus().equals(AppRunRecord.Status.FAILED); assert getLatestAppRun(appName, ADMIN_AUTH_HEADERS)
if (System.currentTimeMillis() - startTime > timeout.toMillis(timeoutValue)) { .getStatus()
throw new AssertionError( .equals(AppRunRecord.Status.SUCCESS);
String.format("Expected the app to succeed within %d %s", timeoutValue, timeout)); },
} appTriggerRetry);
TimeUnit.MILLISECONDS.sleep(500);
latestRun = getLatestAppRun("SearchIndexingApplication", ADMIN_AUTH_HEADERS);
}
} }
@Test @Test

View File

@ -227,10 +227,6 @@ public final class TestUtils {
} }
} }
public static boolean isCI() {
return System.getenv("CI") != null;
}
public enum UpdateType { public enum UpdateType {
CREATED, // Not updated instead entity was created CREATED, // Not updated instead entity was created
NO_CHANGE, // PUT/PATCH made no change to the entity and the version remains the same NO_CHANGE, // PUT/PATCH made no change to the entity and the version remains the same
@ -669,9 +665,14 @@ public final class TestUtils {
} }
public static void assertEventually(String name, CheckedRunnable runnable) { public static void assertEventually(String name, CheckedRunnable runnable) {
assertEventually(name, runnable, elasticSearchRetryRegistry);
}
public static void assertEventually(
String name, CheckedRunnable runnable, RetryRegistry retryRegistry) {
try { try {
Retry.decorateCheckedRunnable( Retry.decorateCheckedRunnable(
elasticSearchRetryRegistry.retry(name), retryRegistry.retry(name),
() -> { () -> {
try { try {
runnable.run(); runnable.run();