From ee0968ad5167ff2ae0815fb25b010aa39e6cd4c8 Mon Sep 17 00:00:00 2001
From: Vaibhav Sethi
Date: Mon, 27 Jan 2025 13:25:38 +0530
Subject: [PATCH] [CDAP-21096] Split ProgramLifecycleService for Appfabric
service and processor
Replace inheritance with composition
---
.../guice/AppFabricServiceRuntimeModule.java | 28 +-
.../AbstractProgramRuntimeService.java | 150 +++-
.../app/runtime/ProgramRuntimeService.java | 66 +-
.../java/io/cdap/cdap/app/store/Store.java | 11 +
.../handlers/ProgramLifecycleHttpHandler.java | 720 +++---------------
.../handlers/ProgramRuntimeHttpHandler.java | 566 ++++++++++++++
.../handlers/util/NamespaceHelper.java | 56 ++
.../handlers/util/ProgramHandlerUtil.java | 119 +++
.../app/preview/DefaultPreviewRunner.java | 6 +-
.../app/runtime/ProgramStartRequest.java | 53 ++
.../InMemoryProgramRuntimeService.java | 5 +-
.../app/services/ProgramLifecycleService.java | 642 +++++-----------
.../ProgramNotificationSubscriberService.java | 12 +-
.../SystemProgramManagementService.java | 6 +-
.../cdap/internal/app/store/DefaultStore.java | 13 +
.../CapabilityManagementService.java | 4 +-
.../AbstractProgramRuntimeServiceTest.java | 3 +-
.../cdap/cdap/internal/AppFabricClient.java | 12 +-
.../services/ProgramLifecycleServiceTest.java | 24 +-
.../SystemProgramManagementServiceTest.java | 16 +-
.../CapabilityManagementServiceTest.java | 23 +-
.../cdap/gateway/router/RouterPathLookup.java | 70 +-
.../gateway/router/RouterPathLookupTest.java | 45 +-
.../RemoteProgramRunRecordFetcher.java | 2 +-
24 files changed, 1445 insertions(+), 1207 deletions(-)
create mode 100644 cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/ProgramRuntimeHttpHandler.java
create mode 100644 cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/util/NamespaceHelper.java
create mode 100644 cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/util/ProgramHandlerUtil.java
create mode 100644 cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/ProgramStartRequest.java
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/app/guice/AppFabricServiceRuntimeModule.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/app/guice/AppFabricServiceRuntimeModule.java
index 3d4e18e861c5..50a89d181d50 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/app/guice/AppFabricServiceRuntimeModule.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/app/guice/AppFabricServiceRuntimeModule.java
@@ -78,6 +78,7 @@
import io.cdap.cdap.gateway.handlers.ProfileHttpHandler;
import io.cdap.cdap.gateway.handlers.ProgramLifecycleHttpHandler;
import io.cdap.cdap.gateway.handlers.ProgramLifecycleHttpHandlerInternal;
+import io.cdap.cdap.gateway.handlers.ProgramRuntimeHttpHandler;
import io.cdap.cdap.gateway.handlers.ProvisionerHttpHandler;
import io.cdap.cdap.gateway.handlers.SourceControlManagementHttpHandler;
import io.cdap.cdap.gateway.handlers.TransactionHttpHandler;
@@ -114,11 +115,11 @@
import io.cdap.cdap.internal.app.runtime.schedule.store.TriggerMisfireLogger;
import io.cdap.cdap.internal.app.runtime.workflow.BasicWorkflowStateWriter;
import io.cdap.cdap.internal.app.runtime.workflow.WorkflowStateWriter;
+import io.cdap.cdap.internal.app.services.FlowControlService;
import io.cdap.cdap.internal.app.services.LocalRunRecordCorrectorService;
import io.cdap.cdap.internal.app.services.NoopRunRecordCorrectorService;
import io.cdap.cdap.internal.app.services.ProgramLifecycleService;
import io.cdap.cdap.internal.app.services.RunRecordCorrectorService;
-import io.cdap.cdap.internal.app.services.FlowControlService;
import io.cdap.cdap.internal.app.services.ScheduledRunRecordCorrectorService;
import io.cdap.cdap.internal.app.store.DefaultStore;
import io.cdap.cdap.internal.bootstrap.guice.BootstrapModules;
@@ -249,17 +250,11 @@ protected void configure() {
Names.named("appfabric.handler.hooks"));
handlerHookNamesBinder.addBinding().toInstance(Constants.Service.APP_FABRIC_HTTP);
- // TODO (CDAP-21112): Remove the addtional handler binding for in-memory and use the binding from
- // AppFabricServiceModule, after fixing in-memory cache issue in ProgramRuntimeService and
- // RunRecordMonitorService.
+ // For ProgramLifecycleHttpHandlerTest the ProgramRuntimeHttpHandler needs to be present
+ // in the appfabric service.
Multibinder handlerBinder = Multibinder.newSetBinder(
binder(), HttpHandler.class, Names.named(Constants.AppFabric.SERVER_HANDLERS_BINDING));
- handlerBinder.addBinding().to(BootstrapHttpHandler.class);
- handlerBinder.addBinding().to(AppLifecycleHttpHandler.class);
- handlerBinder.addBinding().to(AppLifecycleHttpHandlerInternal.class);
- handlerBinder.addBinding().to(ProgramLifecycleHttpHandler.class);
- handlerBinder.addBinding().to(ProgramLifecycleHttpHandlerInternal.class);
- handlerBinder.addBinding().to(WorkflowHttpHandler.class);
+ handlerBinder.addBinding().to(ProgramRuntimeHttpHandler.class);
// TODO: Uncomment after CDAP-7688 is resolved
// servicesNamesBinder.addBinding().toInstance(Constants.Service.MESSAGING_SERVICE);
@@ -529,6 +524,11 @@ protected void configure() {
handlerBinder.addBinding().to(CredentialProviderHttpHandler.class);
handlerBinder.addBinding().to(CredentialProviderHttpHandlerInternal.class);
handlerBinder.addBinding().to(OperationHttpHandler.class);
+ handlerBinder.addBinding().to(AppLifecycleHttpHandler.class);
+ handlerBinder.addBinding().to(AppLifecycleHttpHandlerInternal.class);
+ handlerBinder.addBinding().to(ProgramLifecycleHttpHandler.class);
+ handlerBinder.addBinding().to(ProgramLifecycleHttpHandlerInternal.class);
+ handlerBinder.addBinding().to(WorkflowHttpHandler.class);
FeatureFlagsProvider featureFlagsProvider = new DefaultFeatureFlagsProvider(cConf);
if (Feature.NAMESPACED_SERVICE_ACCOUNTS.isEnabled(featureFlagsProvider)) {
@@ -545,14 +545,8 @@ protected void configure() {
Multibinder processorHandlerBinder = Multibinder.newSetBinder(
binder(), HttpHandler.class, Names.named(AppFabric.PROCESSOR_HANDLERS_BINDING));
CommonHandlers.add(processorHandlerBinder);
- // TODO (CDAP-21112): Move HTTP handler from Appfabric processor to server after fixing
- // ProgramRuntimeService and RunRecordMonitorService.
+ processorHandlerBinder.addBinding().to(ProgramRuntimeHttpHandler.class);
processorHandlerBinder.addBinding().to(BootstrapHttpHandler.class);
- processorHandlerBinder.addBinding().to(AppLifecycleHttpHandler.class);
- processorHandlerBinder.addBinding().to(AppLifecycleHttpHandlerInternal.class);
- processorHandlerBinder.addBinding().to(ProgramLifecycleHttpHandler.class);
- processorHandlerBinder.addBinding().to(ProgramLifecycleHttpHandlerInternal.class);
- processorHandlerBinder.addBinding().to(WorkflowHttpHandler.class);
}
}
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/app/runtime/AbstractProgramRuntimeService.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/app/runtime/AbstractProgramRuntimeService.java
index db1bb11f2dcc..d664d16171ff 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/app/runtime/AbstractProgramRuntimeService.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/app/runtime/AbstractProgramRuntimeService.java
@@ -18,6 +18,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.google.common.io.Closeables;
@@ -26,12 +27,14 @@
import com.google.inject.Inject;
import io.cdap.cdap.app.deploy.ProgramRunDispatcherContext;
import io.cdap.cdap.app.program.ProgramDescriptor;
+import io.cdap.cdap.common.BadRequestException;
import io.cdap.cdap.common.app.RunIds;
import io.cdap.cdap.common.conf.CConfiguration;
import io.cdap.cdap.common.conf.Constants;
import io.cdap.cdap.common.twill.TwillAppNames;
import io.cdap.cdap.internal.app.deploy.ProgramRunDispatcherFactory;
import io.cdap.cdap.internal.app.runtime.AbstractListener;
+import io.cdap.cdap.internal.app.runtime.ProgramOptionConstants;
import io.cdap.cdap.internal.app.runtime.service.SimpleRuntimeInfo;
import io.cdap.cdap.proto.InMemoryProgramLiveInfo;
import io.cdap.cdap.proto.NotRunningProgramLiveInfo;
@@ -46,6 +49,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
@@ -59,6 +64,7 @@
import org.apache.twill.api.TwillController;
import org.apache.twill.api.TwillRunner;
import org.apache.twill.api.TwillRunnerService;
+import org.apache.twill.api.logging.LogEntry;
import org.apache.twill.common.Threads;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -121,27 +127,38 @@ void setRemoteTwillRunnerService(
@Override
public final RuntimeInfo run(ProgramDescriptor programDescriptor, ProgramOptions options,
RunId runId) {
- ProgramRunDispatcherContext dispatcherContext = new ProgramRunDispatcherContext(
- programDescriptor, options, runId,
- isDistributed());
ProgramId programId = programDescriptor.getProgramId();
ProgramRunId programRunId = programId.run(runId);
- DelayedProgramController controller = new DelayedProgramController(programRunId);
- RuntimeInfo runtimeInfo = createRuntimeInfo(controller, programId,
- dispatcherContext::executeCleanupTasks);
- updateRuntimeInfo(runtimeInfo);
- executor.execute(() -> {
- try {
- controller.setProgramController(
- programRunDispatcherFactory.getProgramRunDispatcher(programId.getType())
- .dispatchProgram(dispatcherContext));
- } catch (Exception e) {
- controller.failed(e);
- programStateWriter.error(programRunId, e);
- LOG.error("Exception while trying to run program run {}", programRunId, e);
+ Lock lock = runtimeInfosLock.writeLock();
+ lock.lock();
+ try {
+ RuntimeInfo runtimeInfo = lookup(programRunId.getParent(), runId);
+ if (runtimeInfo != null) {
+ return runtimeInfo;
}
- });
- return runtimeInfo;
+
+ ProgramRunDispatcherContext dispatcherContext = new ProgramRunDispatcherContext(
+ programDescriptor, options, runId,
+ isDistributed());
+ DelayedProgramController controller = new DelayedProgramController(programRunId);
+ runtimeInfo = createRuntimeInfo(controller, programId,
+ dispatcherContext::executeCleanupTasks);
+ updateRuntimeInfo(runtimeInfo);
+ executor.execute(() -> {
+ try {
+ controller.setProgramController(
+ programRunDispatcherFactory.getProgramRunDispatcher(programId.getType())
+ .dispatchProgram(dispatcherContext));
+ } catch (Exception e) {
+ controller.failed(e);
+ programStateWriter.error(programRunId, e);
+ LOG.error("Exception while trying to run program run {}", programRunId, e);
+ }
+ });
+ return runtimeInfo;
+ } finally {
+ lock.unlock();
+ }
}
@Override
@@ -239,6 +256,28 @@ public List listAll(ProgramType... types) {
return runningPrograms;
}
+ @Override
+ public void resetProgramLogLevels(ProgramId programId, Set loggerNames,
+ @Nullable String runId) throws Exception {
+ if (!EnumSet.of(ProgramType.SERVICE, ProgramType.WORKER).contains(programId.getType())) {
+ throw new BadRequestException(
+ String.format("Resetting log levels for program type %s is not supported",
+ programId.getType().getPrettyName()));
+ }
+ resetLogLevels(programId, loggerNames, runId);
+ }
+
+ @Override
+ public void updateProgramLogLevels(ProgramId programId, Map logLevels,
+ @Nullable String runId) throws Exception {
+ if (!EnumSet.of(ProgramType.SERVICE, ProgramType.WORKER).contains(programId.getType())) {
+ throw new BadRequestException(
+ String.format("Updating log levels for program type %s is not supported",
+ programId.getType().getPrettyName()));
+ }
+ updateLogLevels(programId, logLevels, runId);
+ }
+
@Override
protected void startUp() throws Exception {
// Limits to at max poolSize number of concurrent program launch.
@@ -436,4 +475,79 @@ private void cleanupRuntimeInfo(@Nullable RuntimeInfo info) {
Closeables.closeQuietly((Closeable) info);
}
}
+
+ /**
+ * Helper method to get the {@link LogLevelUpdater} for the program.
+ */
+ private LogLevelUpdater getLogLevelUpdater(RuntimeInfo runtimeInfo) throws Exception {
+ ProgramController programController = runtimeInfo.getController();
+ if (!(programController instanceof LogLevelUpdater)) {
+ throw new BadRequestException(
+ "Update log levels at runtime is only supported in distributed mode");
+ }
+ return ((LogLevelUpdater) programController);
+ }
+
+ /**
+ * Helper method to update log levels for Worker or Service.
+ */
+ private void updateLogLevels(ProgramId programId, Map logLevels,
+ @Nullable String runId) throws Exception {
+ ProgramRuntimeService.RuntimeInfo runtimeInfo = findRuntimeInfo(programId, runId).values()
+ .stream()
+ .findFirst().orElse(null);
+ if (runtimeInfo != null) {
+ LogLevelUpdater logLevelUpdater = getLogLevelUpdater(runtimeInfo);
+ logLevelUpdater.updateLogLevels(logLevels, null);
+ }
+ }
+
+ /**
+ * Helper method to reset log levels for Worker or Service.
+ */
+ private void resetLogLevels(ProgramId programId, Set loggerNames, @Nullable String runId)
+ throws Exception {
+ ProgramRuntimeService.RuntimeInfo runtimeInfo = findRuntimeInfo(programId, runId).values()
+ .stream()
+ .findFirst().orElse(null);
+ if (runtimeInfo != null) {
+ LogLevelUpdater logLevelUpdater = getLogLevelUpdater(runtimeInfo);
+ logLevelUpdater.resetLogLevels(loggerNames, null);
+ }
+ }
+
+ @Override
+ public void setInstances(ProgramId programId, int instances, int oldInstances)
+ throws ExecutionException, InterruptedException, BadRequestException {
+ ProgramRuntimeService.RuntimeInfo runtimeInfo = findRuntimeInfo(programId);
+ if (runtimeInfo != null) {
+ runtimeInfo.getController().command(ProgramOptionConstants.INSTANCES,
+ ImmutableMap.of("runnable", programId.getProgram(),
+ "newInstances", String.valueOf(instances),
+ "oldInstances", String.valueOf(oldInstances))).get();
+ }
+ }
+
+ private Map findRuntimeInfo(
+ ProgramId programId, @Nullable String runId) throws BadRequestException {
+
+ if (runId != null) {
+ RunId run;
+ try {
+ run = RunIds.fromString(runId);
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException("Error parsing run-id.", e);
+ }
+ ProgramRuntimeService.RuntimeInfo runtimeInfo = lookup(programId, run);
+ return runtimeInfo == null ? Collections.emptyMap()
+ : Collections.singletonMap(run, runtimeInfo);
+ }
+ return new HashMap<>(list(programId));
+ }
+
+ @Nullable
+ protected ProgramRuntimeService.RuntimeInfo findRuntimeInfo(ProgramId programId)
+ throws BadRequestException {
+ return findRuntimeInfo(programId, null).values().stream().findFirst().orElse(null);
+ }
}
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/app/runtime/ProgramRuntimeService.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/app/runtime/ProgramRuntimeService.java
index 67bcf04ae730..df9f321d854d 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/app/runtime/ProgramRuntimeService.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/app/runtime/ProgramRuntimeService.java
@@ -18,13 +18,19 @@
import com.google.common.util.concurrent.Service;
import io.cdap.cdap.app.program.ProgramDescriptor;
+import io.cdap.cdap.common.BadRequestException;
import io.cdap.cdap.proto.ProgramLiveInfo;
import io.cdap.cdap.proto.ProgramType;
import io.cdap.cdap.proto.id.ProgramId;
+import io.cdap.cdap.proto.security.StandardPermission;
+import io.cdap.cdap.security.spi.authorization.UnauthorizedException;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
import org.apache.twill.api.RunId;
+import org.apache.twill.api.logging.LogEntry;
/**
* Service for interacting with the runtime system.
@@ -47,7 +53,9 @@ interface RuntimeInfo {
}
/**
- * Starts the given program and return a {@link RuntimeInfo} about the running program.
+ * Runs the given program and return a {@link RuntimeInfo} about the running program. The program
+ * is run if it is not already running, otherwise the {@link RuntimeInfo} of the already running
+ * program is returned.
*
* @param programDescriptor describing the program to run
* @param options {@link ProgramOptions} that are needed by the program.
@@ -97,4 +105,60 @@ interface RuntimeInfo {
* @param types Types of program to check returns List of info about running programs.
*/
List listAll(ProgramType... types);
+
+ /**
+ * Reset log levels for the given program. Only supported program types for this action are {@link
+ * ProgramType#SERVICE} and {@link ProgramType#WORKER}.
+ *
+ * @param programId the {@link ProgramId} of the program for which log levels are to be
+ * reset.
+ * @param loggerNames the {@link String} set of the logger names to be updated, empty means
+ * reset for all loggers.
+ * @param runId the run id of the program.
+ * @throws InterruptedException if there is an error while asynchronously resetting log
+ * levels.
+ * @throws ExecutionException if there is an error while asynchronously resetting log levels.
+ * @throws UnauthorizedException if the user does not have privileges to reset log levels for
+ * the specified program. To reset log levels for a program, a user needs {@link
+ * StandardPermission#UPDATE} on the program.
+ */
+ void resetProgramLogLevels(ProgramId programId, Set loggerNames, @Nullable String runId) throws Exception;
+
+ /**
+ * Update log levels for the given program. Only supported program types for this action are
+ * {@link ProgramType#SERVICE} and {@link ProgramType#WORKER}.
+ *
+ * @param programId the {@link ProgramId} of the program for which log levels are to be
+ * updated
+ * @param logLevels the {@link Map} of the log levels to be updated.
+ * @param runId the run id of the program.
+ * @throws InterruptedException if there is an error while asynchronously updating log
+ * levels.
+ * @throws ExecutionException if there is an error while asynchronously updating log levels.
+ * @throws BadRequestException if the log level is not valid or the program type is not
+ * supported.
+ * @throws UnauthorizedException if the user does not have privileges to update log levels for
+ * the specified program. To update log levels for a program, a user needs {@link
+ * StandardPermission#UPDATE} on the program.
+ */
+ void updateProgramLogLevels(ProgramId programId, Map logLevels, @Nullable String runId)
+ throws Exception;
+
+ /**
+ * Set instances for the given program. Only supported program types for this action are {@link
+ * ProgramType#SERVICE} and {@link ProgramType#WORKER}.
+ *
+ * @param programId the {@link ProgramId} of the program for which instances are to be
+ * updated
+ * @param instances the number of instances to be updated.
+ * @param instances the previous number of instances.
+ *
+ * @throws InterruptedException if there is an error while asynchronously updating instances
+ * @throws ExecutionException if there is an error while asynchronously updating instances
+ * @throws BadRequestException if the number of instances specified is less than 0
+ * @throws UnauthorizedException if the user does not have privileges to set instances for the
+ * specified program. To set instances for a program, a user needs {@link
+ * StandardPermission#UPDATE} on the program.
+ */
+ void setInstances(ProgramId programId, int instances, int oldInstances) throws Exception;
}
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/app/store/Store.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/app/store/Store.java
index 6463e243fdc9..c47e42880aaa 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/app/store/Store.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/app/store/Store.java
@@ -26,6 +26,7 @@
import io.cdap.cdap.app.program.Program;
import io.cdap.cdap.app.program.ProgramDescriptor;
import io.cdap.cdap.common.ApplicationNotFoundException;
+import io.cdap.cdap.common.BadRequestException;
import io.cdap.cdap.common.ConflictException;
import io.cdap.cdap.common.NotFoundException;
import io.cdap.cdap.common.ProgramNotFoundException;
@@ -464,6 +465,16 @@ void updateApplicationSourceControlMeta(Map up
@Nullable
ApplicationMeta getLatest(ApplicationReference appRef);
+ /**
+ * Gets the ApplicationId with the latest version for the given ApplicationReference.
+ *
+ * @param appRef ApplicationReference
+ * @return ApplicationId for the latest version
+ *
+ * @throws ApplicationNotFoundException if the app was not found for the given application reference.
+ */
+ ApplicationId getLatestApp(ApplicationReference appRef) throws ApplicationNotFoundException;
+
/**
* Scans for the latest applications across all namespaces.
*
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/ProgramLifecycleHttpHandler.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/ProgramLifecycleHttpHandler.java
index 888bad896edf..d20df72fb68d 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/ProgramLifecycleHttpHandler.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/ProgramLifecycleHttpHandler.java
@@ -19,7 +19,6 @@
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
-import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -31,38 +30,31 @@
import com.google.inject.Singleton;
import io.cdap.cdap.api.ProgramSpecification;
import io.cdap.cdap.api.app.ApplicationSpecification;
-import io.cdap.cdap.api.schedule.Trigger;
+import io.cdap.cdap.api.service.ServiceUnavailableException;
import io.cdap.cdap.app.mapreduce.MRJobInfoFetcher;
-import io.cdap.cdap.app.runtime.ProgramRuntimeService;
import io.cdap.cdap.app.store.Store;
-import io.cdap.cdap.common.ApplicationNotFoundException;
import io.cdap.cdap.common.BadRequestException;
import io.cdap.cdap.common.ConflictException;
-import io.cdap.cdap.common.NamespaceNotFoundException;
import io.cdap.cdap.common.NotFoundException;
import io.cdap.cdap.common.NotImplementedException;
-import io.cdap.cdap.api.service.ServiceUnavailableException;
import io.cdap.cdap.common.app.RunIds;
import io.cdap.cdap.common.conf.Constants;
import io.cdap.cdap.common.discovery.EndpointStrategy;
import io.cdap.cdap.common.discovery.RandomEndpointStrategy;
import io.cdap.cdap.common.id.Id;
-import io.cdap.cdap.common.io.CaseInsensitiveEnumTypeAdapterFactory;
import io.cdap.cdap.common.namespace.NamespaceQueryAdmin;
import io.cdap.cdap.common.security.AuditDetail;
import io.cdap.cdap.common.security.AuditPolicy;
import io.cdap.cdap.common.service.ServiceDiscoverable;
import io.cdap.cdap.gateway.handlers.util.AbstractAppFabricHttpHandler;
-import io.cdap.cdap.internal.app.ApplicationSpecificationAdapter;
+import io.cdap.cdap.gateway.handlers.util.NamespaceHelper;
+import io.cdap.cdap.gateway.handlers.util.ProgramHandlerUtil;
import io.cdap.cdap.internal.app.runtime.schedule.ProgramSchedule;
import io.cdap.cdap.internal.app.runtime.schedule.ProgramScheduleRecord;
import io.cdap.cdap.internal.app.runtime.schedule.ProgramScheduleStatus;
import io.cdap.cdap.internal.app.runtime.schedule.SchedulerException;
-import io.cdap.cdap.internal.app.runtime.schedule.constraint.ConstraintCodec;
import io.cdap.cdap.internal.app.runtime.schedule.store.Schedulers;
import io.cdap.cdap.internal.app.runtime.schedule.trigger.ProgramStatusTrigger;
-import io.cdap.cdap.internal.app.runtime.schedule.trigger.SatisfiableTrigger;
-import io.cdap.cdap.internal.app.runtime.schedule.trigger.TriggerCodec;
import io.cdap.cdap.internal.app.services.ProgramLifecycleService;
import io.cdap.cdap.internal.app.store.ApplicationMeta;
import io.cdap.cdap.internal.app.store.RunRecordDetail;
@@ -74,14 +66,8 @@
import io.cdap.cdap.proto.BatchProgramSchedule;
import io.cdap.cdap.proto.BatchProgramStart;
import io.cdap.cdap.proto.BatchProgramStatus;
-import io.cdap.cdap.proto.BatchRunnable;
-import io.cdap.cdap.proto.BatchRunnableInstances;
-import io.cdap.cdap.proto.Containers;
-import io.cdap.cdap.proto.Instances;
import io.cdap.cdap.proto.MRJobInfo;
-import io.cdap.cdap.proto.NotRunningProgramLiveInfo;
import io.cdap.cdap.proto.ProgramHistory;
-import io.cdap.cdap.proto.ProgramLiveInfo;
import io.cdap.cdap.proto.ProgramRunStatus;
import io.cdap.cdap.proto.ProgramStatus;
import io.cdap.cdap.proto.ProgramType;
@@ -90,7 +76,6 @@
import io.cdap.cdap.proto.RunRecord;
import io.cdap.cdap.proto.ScheduleDetail;
import io.cdap.cdap.proto.ScheduledRuntime;
-import io.cdap.cdap.proto.ServiceInstances;
import io.cdap.cdap.proto.id.ApplicationId;
import io.cdap.cdap.proto.id.ApplicationReference;
import io.cdap.cdap.proto.id.NamespaceId;
@@ -109,12 +94,10 @@
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -146,8 +129,6 @@ public class ProgramLifecycleHttpHandler extends AbstractAppFabricHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(ProgramLifecycleHttpHandler.class);
private static final Type BATCH_PROGRAMS_TYPE = new TypeToken>() {
}.getType();
- private static final Type BATCH_RUNNABLES_TYPE = new TypeToken>() {
- }.getType();
private static final Type BATCH_STARTS_TYPE = new TypeToken>() {
}.getType();
@@ -155,52 +136,21 @@ public class ProgramLifecycleHttpHandler extends AbstractAppFabricHttpHandler {
private static final String SCHEDULES = "schedules";
- /**
- * Json serializer/deserializer.
- */
- private static final Gson GSON = ApplicationSpecificationAdapter
- .addTypeAdapters(new GsonBuilder())
- .registerTypeAdapter(Trigger.class, new TriggerCodec())
- .registerTypeAdapter(SatisfiableTrigger.class, new TriggerCodec())
- .registerTypeAdapter(Constraint.class, new ConstraintCodec())
- .create();
-
- /**
- * Json serde for decoding request. It uses a case insensitive enum adapter.
- */
- private static final Gson DECODE_GSON = ApplicationSpecificationAdapter
- .addTypeAdapters(new GsonBuilder())
- .registerTypeAdapterFactory(new CaseInsensitiveEnumTypeAdapterFactory())
- .registerTypeAdapter(Trigger.class, new TriggerCodec())
- .registerTypeAdapter(SatisfiableTrigger.class, new TriggerCodec())
- .registerTypeAdapter(Constraint.class, new ConstraintCodec())
- .create();
-
private final ProgramScheduleService programScheduleService;
private final ProgramLifecycleService lifecycleService;
private final DiscoveryServiceClient discoveryServiceClient;
private final MRJobInfoFetcher mrJobInfoFetcher;
private final NamespaceQueryAdmin namespaceQueryAdmin;
-
- /**
- * Store manages non-runtime lifecycle.
- */
private final Store store;
- /**
- * Runtime program service for running and managing programs.
- */
- private final ProgramRuntimeService runtimeService;
-
@Inject
- ProgramLifecycleHttpHandler(Store store, ProgramRuntimeService runtimeService,
+ ProgramLifecycleHttpHandler(Store store,
DiscoveryServiceClient discoveryServiceClient,
ProgramLifecycleService lifecycleService,
MRJobInfoFetcher mrJobInfoFetcher,
NamespaceQueryAdmin namespaceQueryAdmin,
ProgramScheduleService programScheduleService) {
this.store = store;
- this.runtimeService = runtimeService;
this.discoveryServiceClient = discoveryServiceClient;
this.lifecycleService = lifecycleService;
this.mrJobInfoFetcher = mrJobInfoFetcher;
@@ -300,7 +250,7 @@ public void getStatusVersioned(HttpRequest request, HttpResponder responder,
return;
}
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
ProgramStatus programStatus;
if (ApplicationId.DEFAULT_VERSION.equals(versionId)) {
programStatus = lifecycleService.getProgramStatus(
@@ -311,7 +261,7 @@ public void getStatusVersioned(HttpRequest request, HttpResponder responder,
}
Map status = ImmutableMap.of("status", programStatus.name());
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(status));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(status));
}
/**
@@ -325,7 +275,7 @@ public void performRunLevelStop(HttpRequest request, HttpResponder responder,
@PathParam("program-type") String type,
@PathParam("program-id") String programId,
@PathParam("run-id") String runId) throws Exception {
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
ProgramReference program = new ApplicationReference(namespaceId, appId).program(programType,
programId);
@@ -408,7 +358,7 @@ private void doPerformAction(FullHttpRequest request, HttpResponder responder, S
return;
}
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
ProgramReference programReference = new ProgramReference(namespaceId, appId, programType,
programId);
ProgramId program = applicationId.program(programType, programId);
@@ -491,7 +441,7 @@ public void programHistory(HttpRequest request, HttpResponder responder,
@QueryParam("end") String endTs,
@QueryParam("limit") @DefaultValue("100") final int resultLimit)
throws Exception {
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
long start = parseAndValidate(startTs, 0L, "start");
long end = parseAndValidate(endTs, Long.MAX_VALUE, "end");
@@ -508,7 +458,7 @@ public void programHistory(HttpRequest request, HttpResponder responder,
List records = lifecycleService.getAllRunRecords(programReference, runStatus, start,
end, resultLimit,
record -> !isTetheredRunRecord(record));
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(records));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(records));
}
/**
@@ -528,7 +478,7 @@ public void programHistoryVersioned(HttpRequest request, HttpResponder responder
@QueryParam("end") String endTs,
@QueryParam("limit") @DefaultValue("100") final int resultLimit)
throws Exception {
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
long start = parseAndValidate(startTs, 0L, "start");
long end = parseAndValidate(endTs, Long.MAX_VALUE, "end");
@@ -553,7 +503,7 @@ public void programHistoryVersioned(HttpRequest request, HttpResponder responder
}
records = records.stream().filter(record -> !isTetheredRunRecord(record))
.collect(Collectors.toList());
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(records));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(records));
}
private long parseAndValidate(String strVal, long defaultVal, String paramName)
@@ -578,12 +528,19 @@ public void programRunRecord(HttpRequest request, HttpResponder responder,
@PathParam("app-name") String appName,
@PathParam("program-type") String type,
@PathParam("program-name") String programName,
- @PathParam("run-id") String runid) throws NotFoundException, BadRequestException {
- RunRecordDetail runRecordMeta = getRunRecordDetailFromId(namespaceId, appName, type,
- programName, runid);
+ @PathParam("run-id") String runId) throws NotFoundException, BadRequestException {
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
+ ProgramReference programRef = new ApplicationReference(namespaceId, appName).program(programType,
+ programName);
+ RunRecordDetail runRecordMeta = store.getRun(programRef, runId);
+ if (runRecordMeta == null) {
+ throw new NotFoundException(
+ String.format("No run record found for program %s and runID: %s", programRef, runId));
+ }
+
if (!isTetheredRunRecord(runRecordMeta)) {
RunRecord runRecord = RunRecord.builder(runRecordMeta).build();
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(runRecord));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(runRecord));
return;
}
throw new NotFoundException(runRecordMeta.getProgramRunId());
@@ -605,13 +562,13 @@ public void programRunRecordVersioned(HttpRequest request,
@PathParam("program-name") String programName,
@PathParam("run-id") String runid)
throws NotFoundException, BadRequestException {
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
ProgramId progId = new ApplicationId(namespaceId, appName, appVersion).program(programType,
programName);
RunRecordDetail runRecordMeta = store.getRun(progId.run(runid));
if (runRecordMeta != null && !isTetheredRunRecord(runRecordMeta)) {
RunRecord runRecord = RunRecord.builder(runRecordMeta).build();
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(runRecord));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(runRecord));
return;
}
throw new NotFoundException(progId.run(runid));
@@ -627,10 +584,9 @@ public void getProgramRuntimeArgs(HttpRequest request, HttpResponder responder,
@PathParam("app-name") String appName,
@PathParam("program-type") String type,
@PathParam("program-name") String programName) throws Exception {
- ProgramType programType = getProgramType(type);
- String appVersion = getLatestAppVersion(new NamespaceId(namespaceId), appName);
- ProgramId programId = new ApplicationId(namespaceId, appName, appVersion).program(programType,
- programName);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
+ ProgramId programId = store.getLatestApp(new ApplicationReference(namespaceId, appName))
+ .program(programType, programName);
getProgramIdRuntimeArgs(programId, responder);
}
@@ -645,9 +601,8 @@ public void saveProgramRuntimeArgs(FullHttpRequest request, HttpResponder respon
@PathParam("app-name") String appName,
@PathParam("program-type") String type,
@PathParam("program-name") String programName) throws Exception {
- ProgramType programType = getProgramType(type);
- String appVersion = getLatestAppVersion(new NamespaceId(namespaceId), appName);
- ProgramId programId = new ApplicationId(namespaceId, appName, appVersion).program(programType,
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
+ ProgramId programId = store.getLatestApp(new ApplicationReference(namespaceId, appName)).program(programType,
programName);
saveProgramIdRuntimeArgs(programId, request, responder);
}
@@ -666,7 +621,7 @@ public void getProgramRuntimeArgsVersioned(HttpRequest request, HttpResponder re
@PathParam("app-version") String appVersion,
@PathParam("program-type") String type,
@PathParam("program-name") String programName) throws Exception {
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
ProgramId programId = new ApplicationId(namespaceId, appName, appVersion).program(programType,
programName);
getProgramIdRuntimeArgs(programId, responder);
@@ -687,12 +642,12 @@ public void saveProgramRuntimeArgsVersioned(FullHttpRequest request, HttpRespond
@PathParam("app-version") String appVersion,
@PathParam("program-type") String type,
@PathParam("program-name") String programName) throws Exception {
- String latestVersion = getLatestAppVersion(new NamespaceId(namespaceId), appName);
+ String latestVersion = store.getLatestApp(new ApplicationReference(namespaceId, appName)).getVersion();
if (!appVersion.equals(latestVersion) && !ApplicationId.DEFAULT_VERSION.equals(appVersion)) {
throw new BadRequestException(
"Runtime arguments can only be changed on the latest program version");
}
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
ProgramId programId = new ApplicationId(namespaceId, appName, appVersion).program(programType,
programName);
saveProgramIdRuntimeArgs(programId, request, responder);
@@ -701,7 +656,7 @@ public void saveProgramRuntimeArgsVersioned(FullHttpRequest request, HttpRespond
private void getProgramIdRuntimeArgs(ProgramId programId, HttpResponder responder)
throws Exception {
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(lifecycleService.getRuntimeArgs(programId)));
+ ProgramHandlerUtil.toJson(lifecycleService.getRuntimeArgs(programId)));
}
private void saveProgramIdRuntimeArgs(ProgramId programId, FullHttpRequest request,
@@ -733,7 +688,7 @@ public void programSpecificationVersioned(HttpRequest request, HttpResponder res
@PathParam("app-version") String appVersion,
@PathParam("program-type") String type,
@PathParam("program-name") String programName) throws Exception {
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
ApplicationId application = new ApplicationId(namespaceId, appName, appVersion);
ProgramId programId = application.program(programType, programName);
ProgramSpecification specification;
@@ -747,7 +702,7 @@ public void programSpecificationVersioned(HttpRequest request, HttpResponder res
if (specification == null) {
throw new NotFoundException(programId);
}
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(specification));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(specification));
}
/**
@@ -812,7 +767,7 @@ public void getProgramStatusSchedules(HttpRequest request, HttpResponder respond
throw new BadRequestException("Must specify trigger-program-name as a query param");
}
- ProgramType programType = getProgramType(triggerProgramType);
+ ProgramType programType = ProgramType.valueOfCategoryName(triggerProgramType, BadRequestException::new);
ProgramScheduleStatus programScheduleStatus;
try {
programScheduleStatus =
@@ -853,7 +808,7 @@ public void getProgramStatusSchedules(HttpRequest request, HttpResponder respond
.map(ProgramScheduleRecord::toScheduleDetail)
.collect(Collectors.toList());
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(details, Schedulers.SCHEDULE_DETAILS_TYPE));
+ ProgramHandlerUtil.toJson(details, Schedulers.SCHEDULE_DETAILS_TYPE));
}
@GET
@@ -885,7 +840,7 @@ private void doGetSchedule(HttpResponder responder, String namespace, String app
ScheduleId scheduleId = new ApplicationId(namespace, app).schedule(scheduleName);
ProgramScheduleRecord record = programScheduleService.getRecord(scheduleId);
ScheduleDetail detail = record.toScheduleDetail();
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(detail, ScheduleDetail.class));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(detail, ScheduleDetail.class));
}
/**
@@ -963,7 +918,7 @@ public void getProgramSchedulesVersioned(HttpRequest request, HttpResponder resp
@PathParam("program-name") String program,
@QueryParam("trigger-type") String triggerType,
@QueryParam("schedule-status") String scheduleStatus) throws Exception {
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
if (programType.getSchedulableType() == null) {
throw new BadRequestException("Program type " + programType + " cannot have schedule");
}
@@ -1024,7 +979,7 @@ record -> record.getSchedule().getTrigger().getType().equals(triggerType));
.map(ProgramScheduleRecord::toScheduleDetail)
.collect(Collectors.toList());
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(details, Schedulers.SCHEDULE_DETAILS_TYPE));
+ ProgramHandlerUtil.toJson(details, Schedulers.SCHEDULE_DETAILS_TYPE));
}
/**
@@ -1034,14 +989,12 @@ record -> record.getSchedule().getTrigger().getType().equals(triggerType));
@Path("/apps/{app-name}/{program-type}/{program-name}/previousruntime")
public void getPreviousScheduledRunTime(HttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId,
- @PathParam("app-name") String appId,
+ @PathParam("app-name") String appName,
@PathParam("program-type") String type,
@PathParam("program-name") String program) throws Exception {
- ProgramType programType = getProgramType(type);
- String appVersion = getLatestAppVersion(new NamespaceId(namespaceId), appId);
- handleScheduleRunTime(responder,
- new NamespaceId(namespaceId).app(appId, appVersion).program(programType, program),
- true);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
+ ApplicationId appId = store.getLatestApp(new ApplicationReference(namespaceId, appName));
+ handleScheduleRunTime(responder, appId.program(programType, program), true);
}
/**
@@ -1051,14 +1004,12 @@ public void getPreviousScheduledRunTime(HttpRequest request, HttpResponder respo
@Path("/apps/{app-name}/{program-type}/{program-name}/nextruntime")
public void getNextScheduledRunTime(HttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId,
- @PathParam("app-name") String appId,
+ @PathParam("app-name") String appName,
@PathParam("program-type") String type,
@PathParam("program-name") String program) throws Exception {
- ProgramType programType = getProgramType(type);
- String appVersion = getLatestAppVersion(new NamespaceId(namespaceId), appId);
- handleScheduleRunTime(responder,
- new NamespaceId(namespaceId).app(appId, appVersion).program(programType, program),
- false);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
+ ApplicationId appId = store.getLatestApp(new ApplicationReference(namespaceId, appName));
+ handleScheduleRunTime(responder, appId.program(programType, program), false);
}
private void handleScheduleRunTime(HttpResponder responder, ProgramId programId,
@@ -1066,7 +1017,7 @@ private void handleScheduleRunTime(HttpResponder responder, ProgramId programId,
try {
lifecycleService.ensureProgramExists(programId);
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(getScheduledRunTimes(programId, previousRuntimeRequested)));
+ ProgramHandlerUtil.toJson(getScheduledRunTimes(programId, previousRuntimeRequested)));
} catch (SecurityException e) {
responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
}
@@ -1096,9 +1047,9 @@ private void handleScheduleRunTime(HttpResponder responder, ProgramId programId,
public void batchPreviousRunTimes(FullHttpRequest request,
HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
- List batchPrograms = validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
+ List batchPrograms = ProgramHandlerUtil.validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(batchRunTimes(namespaceId, batchPrograms, true)));
+ ProgramHandlerUtil.toJson(batchRunTimes(namespaceId, batchPrograms, true)));
}
/**
@@ -1125,9 +1076,9 @@ public void batchPreviousRunTimes(FullHttpRequest request,
public void batchNextRunTimes(FullHttpRequest request,
HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
- List batchPrograms = validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
+ List batchPrograms = ProgramHandlerUtil.validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(batchRunTimes(namespaceId, batchPrograms, false)));
+ ProgramHandlerUtil.toJson(batchRunTimes(namespaceId, batchPrograms, false)));
}
/**
@@ -1297,7 +1248,7 @@ private ScheduleDetail readScheduleDetailBody(FullHttpRequest request, String sc
try (Reader reader = new InputStreamReader(new ByteBufInputStream(request.content()),
Charsets.UTF_8)) {
// The schedule spec in the request body does not contain the program information
- json = DECODE_GSON.fromJson(reader, JsonElement.class);
+ json = ProgramHandlerUtil.fromJson(reader, JsonElement.class);
} catch (IOException e) {
throw new IOException("Error reading request body", e);
} catch (JsonSyntaxException e) {
@@ -1305,11 +1256,11 @@ private ScheduleDetail readScheduleDetailBody(FullHttpRequest request, String sc
}
if (!json.isJsonObject()) {
throw new BadRequestException(
- "Expected a json object in the request body but received " + GSON.toJson(json));
+ "Expected a json object in the request body but received " + ProgramHandlerUtil.toJson(json));
}
ScheduleDetail scheduleDetail;
try {
- scheduleDetail = DECODE_GSON.fromJson(json, ScheduleDetail.class);
+ scheduleDetail = ProgramHandlerUtil.fromJson(json, ScheduleDetail.class);
} catch (JsonSyntaxException e) {
throw new BadRequestException(
"Error parsing request body as a schedule specification: " + e.getMessage());
@@ -1354,107 +1305,6 @@ private void doDeleteSchedule(HttpResponder responder, String namespaceId, Strin
responder.sendStatus(HttpResponseStatus.OK);
}
- /**
- * Update the log level for a running program according to the request body. Currently supported
- * program types are {@link ProgramType#SERVICE} and {@link ProgramType#WORKER}. The request body
- * is expected to contain a map of log levels, where key is loggername, value is one of the valid
- * {@link org.apache.twill.api.logging.LogEntry.Level} or null.
- *
- */
- @PUT
- @Path("/apps/{app-name}/{program-type}/{program-name}/runs/{run-id}/loglevels")
- @AuditPolicy(AuditDetail.REQUEST_BODY)
- public void updateProgramLogLevels(FullHttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespace,
- @PathParam("app-name") String appName,
- @PathParam("program-type") String type,
- @PathParam("program-name") String programName,
- @PathParam("run-id") String runId) throws Exception {
- RunRecordDetail run = getRunRecordDetailFromId(namespace, appName, type, programName, runId);
- updateLogLevels(request, responder, namespace, appName, run.getVersion(), type, programName,
- runId);
- }
-
- /**
- * Update the log level for a running program according to the request body.
- * Deprecated : Run-id is sufficient to identify a program run.
- */
- @Deprecated
- @PUT
- @Path("/apps/{app-name}/versions/{app-version}/{program-type}/{program-name}/runs/{run-id}/loglevels")
- @AuditPolicy(AuditDetail.REQUEST_BODY)
- public void updateProgramLogLevelsVersioned(FullHttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespace,
- @PathParam("app-name") String appName,
- @PathParam("app-version") String appVersion,
- @PathParam("program-type") String type,
- @PathParam("program-name") String programName,
- @PathParam("run-id") String runId) throws Exception {
- updateLogLevels(request, responder, namespace, appName, appVersion, type, programName, runId);
- }
-
- /**
- * Reset the log level for a running program back to where it starts. Currently supported program
- * types are {@link ProgramType#SERVICE} and {@link ProgramType#WORKER}. The request body can
- * either be empty, which will reset all loggers for the program, or contain a list of logger
- * names, which will reset for these particular logger names for the program.
- */
- @POST
- @Path("/apps/{app-name}/{program-type}/{program-name}/runs/{run-id}/resetloglevels")
- @AuditPolicy(AuditDetail.REQUEST_BODY)
- public void resetProgramLogLevels(FullHttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespace,
- @PathParam("app-name") String appName,
- @PathParam("program-type") String type,
- @PathParam("program-name") String programName,
- @PathParam("run-id") String runId) throws Exception {
- RunRecordDetail run = getRunRecordDetailFromId(namespace, appName, type, programName, runId);
- resetLogLevels(request, responder, namespace, appName, run.getVersion(), type, programName,
- runId);
- }
-
- /**
- * Reset the log level for a running program back to where it starts.
- *
- * Deprecated : Run-id is sufficient to identify a program run.
- */
- @POST
- @Path("/apps/{app-name}/versions/{app-version}/{program-type}/{program-name}/runs/{run-id}/resetloglevels")
- @AuditPolicy(AuditDetail.REQUEST_BODY)
- public void resetProgramLogLevelsVersioned(FullHttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespace,
- @PathParam("app-name") String appName,
- @PathParam("app-version") String appVersion,
- @PathParam("program-type") String type,
- @PathParam("program-name") String programName,
- @PathParam("run-id") String runId) throws Exception {
- resetLogLevels(request, responder, namespace, appName, appVersion, type, programName, runId);
- }
-
- /**
- * Fetches the run record for particular run of a program without version.
- *
- * @param namespace namespace id
- * @param appName application name
- * @param type program type
- * @param programName program name
- * @param runId the run id
- * @return run record for the specified program and runRef, null if not found
- */
- private RunRecordDetail getRunRecordDetailFromId(String namespace, String appName,
- String type, String programName, String runId)
- throws BadRequestException, NotFoundException {
- ProgramType programType = getProgramType(type);
- ProgramReference programRef = new ApplicationReference(namespace, appName).program(programType,
- programName);
- RunRecordDetail runRecordMeta = store.getRun(programRef, runId);
- if (runRecordMeta == null) {
- throw new NotFoundException(
- String.format("No run record found for program %s and runID: %s", programRef, runId));
- }
- return runRecordMeta;
- }
-
/**
* Returns the status for all programs that are passed into the data. The data is an array of JSON
* objects where each object must contain the following three elements: appId, programType, and
@@ -1488,7 +1338,7 @@ private RunRecordDetail getRunRecordDetailFromId(String namespace, String appNam
@AuditPolicy(AuditDetail.REQUEST_BODY)
public void getStatuses(FullHttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
- List batchPrograms = validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
+ List batchPrograms = ProgramHandlerUtil.validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
List programs = batchPrograms.stream()
.map(p -> new ProgramReference(namespaceId, p.getAppId(), p.getProgramType(),
p.getProgramId()))
@@ -1512,7 +1362,7 @@ public void getStatuses(FullHttpRequest request, HttpResponder responder,
null, statuses.get(programId).name()));
}
}
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(result));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(result));
}
/**
@@ -1548,13 +1398,11 @@ public void getStatuses(FullHttpRequest request, HttpResponder responder,
@AuditPolicy({AuditDetail.REQUEST_BODY, AuditDetail.RESPONSE_BODY})
public void stopPrograms(FullHttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
- List programs = validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
+ List programs = ProgramHandlerUtil.validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
List issuedStops = new ArrayList<>(programs.size());
for (final BatchProgram program : programs) {
- ApplicationId applicationId = new ApplicationId(namespaceId, program.getAppId(),
- getLatestAppVersion(new NamespaceId(namespaceId),
- program.getAppId()));
+ ApplicationId applicationId = store.getLatestApp(new ApplicationReference(namespaceId, program.getAppId()));
ProgramId programId = new ProgramId(applicationId, program.getProgramType(),
program.getProgramId());
try {
@@ -1572,7 +1420,7 @@ public void stopPrograms(FullHttpRequest request, HttpResponder responder,
}
}
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(issuedStops));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(issuedStops));
}
/**
@@ -1611,13 +1459,11 @@ public void stopPrograms(FullHttpRequest request, HttpResponder responder,
public void startPrograms(FullHttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
- List programs = validateAndGetBatchInput(request, BATCH_STARTS_TYPE);
+ List programs = ProgramHandlerUtil.validateAndGetBatchInput(request, BATCH_STARTS_TYPE);
List output = new ArrayList<>(programs.size());
for (BatchProgramStart program : programs) {
- ApplicationId applicationId = new ApplicationId(namespaceId, program.getAppId(),
- getLatestAppVersion(new NamespaceId(namespaceId),
- program.getAppId()));
+ ApplicationId applicationId = store.getLatestApp(new ApplicationReference(namespaceId, program.getAppId()));
ProgramId programId = new ProgramId(applicationId, program.getProgramType(),
program.getProgramId());
try {
@@ -1634,85 +1480,7 @@ public void startPrograms(FullHttpRequest request, HttpResponder responder,
new BatchProgramResult(program, HttpResponseStatus.CONFLICT.code(), e.getMessage()));
}
}
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(output));
- }
-
- /**
- * Returns the number of instances for all program runnables that are passed into the data. The
- * data is an array of Json objects where each object must contain the following three elements:
- * appId, programType, and programId (flow name, service name). Retrieving instances only applies
- * to flows, and user services. For flows, another parameter, "runnableId", must be provided. This
- * corresponds to the flowlet/runnable for which to retrieve the instances.
- *
- * Example input:
- *
- * [{"appId": "App1", "programType": "Service", "programId": "Service1", "runnableId": "Runnable1"},
- * {"appId": "App1", "programType": "Mapreduce", "programId": "Mapreduce2"}]
- *
- *
- * The response will be an array of JsonObjects each of which will contain the three input
- * parameters as well as 3 fields:
- *
- * - "provisioned" which maps to the number of instances actually provided for the input runnable;
- * - "requested" which maps to the number of instances the user has requested for the input runnable; and
- * - "statusCode" which maps to the http status code for the data in that JsonObjects (200, 400, 404).
- *
- *
- * If an error occurs in the input (for the example above, Flowlet1 does not exist), then all JsonObjects for
- * which the parameters have a valid instances will have the provisioned and requested fields status code fields
- * but all JsonObjects for which the parameters are not valid will have an error message and statusCode.
- *
- * For example, if there is no Flowlet1 in the above data, then the response could be 200 OK with the following data:
- *
- *
- * [{"appId": "App1", "programType": "Service", "programId": "Service1", "runnableId": "Runnable1",
- * "statusCode": 200, "provisioned": 2, "requested": 2},
- * {"appId": "App1", "programType": "Mapreduce", "programId": "Mapreduce2", "statusCode": 400,
- * "error": "Program type 'Mapreduce' is not a valid program type to get instances"}]
- *
- */
- @POST
- @Path("/instances")
- @AuditPolicy(AuditDetail.REQUEST_BODY)
- public void getInstances(FullHttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespaceId) throws IOException, BadRequestException {
-
- List runnables = validateAndGetBatchInput(request, BATCH_RUNNABLES_TYPE);
-
- // cache app specs to perform fewer store lookups
- Map appSpecs = new HashMap<>();
-
- List output = new ArrayList<>(runnables.size());
- for (BatchRunnable runnable : runnables) {
- // cant get instances for things that are not flows, services, or workers
- if (!canHaveInstances(runnable.getProgramType())) {
- output.add(
- new BatchRunnableInstances(runnable, HttpResponseStatus.BAD_REQUEST.code(),
- String.format("Program type '%s' is not a valid program type to get instances",
- runnable.getProgramType().getPrettyName())));
- continue;
- }
-
- ApplicationId appId = new ApplicationId(namespaceId, runnable.getAppId());
- try {
- appId = new ApplicationId(namespaceId, runnable.getAppId(),
- getLatestAppVersion(new NamespaceId(namespaceId), runnable.getAppId()));
- } catch (ApplicationNotFoundException e) {
- output.add(new BatchRunnableInstances(runnable, HttpResponseStatus.NOT_FOUND.code(),
- String.format("App: %s not found", appId)));
- continue;
- }
-
- // populate spec cache if this is the first time we've seen the appid.
- if (!appSpecs.containsKey(appId)) {
- appSpecs.put(appId, store.getApplication(appId));
- }
-
- ApplicationSpecification spec = appSpecs.get(appId);
- ProgramId programId = appId.program(runnable.getProgramType(), runnable.getProgramId());
- output.add(getProgramInstances(runnable, spec, programId));
- }
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(output));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(output));
}
/**
@@ -1753,7 +1521,7 @@ public void getInstances(FullHttpRequest request, HttpResponder responder,
@Path("/runcount")
public void getRunCounts(FullHttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
- List programs = validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
+ List programs = ProgramHandlerUtil.validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
if (programs.size() > 100) {
throw new BadRequestException(
String.format("%d programs found in the request, the maximum number "
@@ -1784,7 +1552,7 @@ public void getRunCounts(FullHttpRequest request, HttpResponder responder,
exception.getMessage(), null));
}
}
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(counts));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(counts));
}
/**
@@ -1825,7 +1593,7 @@ public void getRunCounts(FullHttpRequest request, HttpResponder responder,
@Path("/runs")
public void getLatestRuns(FullHttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
- List programs = validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
+ List programs = ProgramHandlerUtil.validateAndGetBatchInput(request, BATCH_PROGRAMS_TYPE);
List programRefs =
programs.stream().map(
p -> new ProgramReference(namespaceId, p.getAppId(), p.getProgramType(),
@@ -1856,7 +1624,7 @@ public void getLatestRuns(FullHttpRequest request, HttpResponder responder,
exception.getMessage(), Collections.emptyList()));
}
}
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(response));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(response));
}
/**
@@ -1869,11 +1637,11 @@ public void getProgramRunCount(HttpRequest request, HttpResponder responder,
@PathParam("app-name") String appName,
@PathParam("program-type") String type,
@PathParam("program-name") String programName) throws Exception {
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
ProgramReference programReference = new ProgramReference(namespaceId, appName, programType,
programName);
long runCount = lifecycleService.getProgramTotalRunCount(programReference);
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(runCount));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(runCount));
}
/**
@@ -1887,7 +1655,7 @@ public void getProgramRunCountVersioned(HttpRequest request, HttpResponder respo
@PathParam("app-version") String appVersion,
@PathParam("program-type") String type,
@PathParam("program-name") String programName) throws Exception {
- ProgramType programType = getProgramType(type);
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
long runCount;
if (ApplicationId.DEFAULT_VERSION.equals(appVersion)) {
ProgramReference programReference = new ProgramReference(namespaceId, appName, programType,
@@ -1898,7 +1666,7 @@ public void getProgramRunCountVersioned(HttpRequest request, HttpResponder respo
programName);
runCount = lifecycleService.getProgramRunCount(programId);
}
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(runCount));
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(runCount));
}
/*
@@ -1913,8 +1681,9 @@ public void getProgramRunCountVersioned(HttpRequest request, HttpResponder respo
public void getAllMapReduce(HttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(
- lifecycleService.list(validateAndGetNamespace(namespaceId), ProgramType.MAPREDUCE)));
+ ProgramHandlerUtil.toJson(
+ lifecycleService.list(NamespaceHelper.validateNamespace(namespaceQueryAdmin,namespaceId),
+ ProgramType.MAPREDUCE)));
}
/**
@@ -1925,8 +1694,9 @@ public void getAllMapReduce(HttpRequest request, HttpResponder responder,
public void getAllSpark(HttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(
- lifecycleService.list(validateAndGetNamespace(namespaceId), ProgramType.SPARK)));
+ ProgramHandlerUtil.toJson(
+ lifecycleService.list(NamespaceHelper.validateNamespace(namespaceQueryAdmin,namespaceId),
+ ProgramType.SPARK)));
}
/**
@@ -1937,8 +1707,9 @@ public void getAllSpark(HttpRequest request, HttpResponder responder,
public void getAllWorkflows(HttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(
- lifecycleService.list(validateAndGetNamespace(namespaceId), ProgramType.WORKFLOW)));
+ ProgramHandlerUtil.toJson(
+ lifecycleService.list(NamespaceHelper.validateNamespace(namespaceQueryAdmin,namespaceId),
+ ProgramType.WORKFLOW)));
}
/**
@@ -1949,8 +1720,9 @@ public void getAllWorkflows(HttpRequest request, HttpResponder responder,
public void getAllServices(HttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(
- lifecycleService.list(validateAndGetNamespace(namespaceId), ProgramType.SERVICE)));
+ ProgramHandlerUtil.toJson(
+ lifecycleService.list(NamespaceHelper.validateNamespace(namespaceQueryAdmin,namespaceId),
+ ProgramType.SERVICE)));
}
@GET
@@ -1958,107 +1730,9 @@ public void getAllServices(HttpRequest request, HttpResponder responder,
public void getAllWorkers(HttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespaceId) throws Exception {
responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(
- lifecycleService.list(validateAndGetNamespace(namespaceId), ProgramType.WORKER)));
- }
-
- /**
- * Returns number of instances of a worker.
- */
- @GET
- @Path("/apps/{app-id}/workers/{worker-id}/instances")
- public void getWorkerInstances(HttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespaceId,
- @PathParam("app-id") String appId,
- @PathParam("worker-id") String workerId) throws Exception {
- try {
- ProgramId programId = validateAndGetNamespace(namespaceId)
- .app(appId, getLatestAppVersion(new NamespaceId(namespaceId), appId))
- .worker(workerId);
- lifecycleService.ensureProgramExists(programId);
- int count = store.getWorkerInstances(programId);
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(new Instances(count)));
- } catch (SecurityException e) {
- responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
- } catch (Throwable e) {
- if (respondIfElementNotFound(e, responder)) {
- return;
- }
- throw e;
- }
- }
-
- /**
- * Sets the number of instances of a worker.
- */
- @PUT
- @Path("/apps/{app-id}/workers/{worker-id}/instances")
- @AuditPolicy(AuditDetail.REQUEST_BODY)
- public void setWorkerInstances(FullHttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespaceId,
- @PathParam("app-id") String appId,
- @PathParam("worker-id") String workerId) throws Exception {
- int instances = getInstances(request);
- try {
- lifecycleService.setInstances(new ApplicationId(namespaceId, appId,
- getLatestAppVersion(new NamespaceId(namespaceId), appId))
- .program(ProgramType.WORKER, workerId), instances);
- responder.sendStatus(HttpResponseStatus.OK);
- } catch (SecurityException e) {
- responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
- } catch (Throwable e) {
- if (respondIfElementNotFound(e, responder)) {
- return;
- }
- throw e;
- }
- }
-
- @GET
- @Path("/apps/{app-id}/{program-category}/{program-id}/live-info")
- @SuppressWarnings("unused")
- public void liveInfo(HttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespaceId,
- @PathParam("app-id") String appId, @PathParam("program-category") String programCategory,
- @PathParam("program-id") String programId)
- throws BadRequestException, ApplicationNotFoundException {
- ProgramType type = getProgramType(programCategory);
- ProgramId program = new ApplicationId(namespaceId, appId,
- getLatestAppVersion(new NamespaceId(namespaceId), appId))
- .program(type, programId);
- getLiveInfo(responder, program, runtimeService);
- }
-
-
- private void getLiveInfo(HttpResponder responder, ProgramId programId,
- ProgramRuntimeService runtimeService) {
- try {
- responder.sendJson(HttpResponseStatus.OK, GSON.toJson(runtimeService.getLiveInfo(programId)));
- } catch (SecurityException e) {
- responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
- }
- }
-
- /**
- * Return the number of instances of a service.
- */
- @GET
- @Path("/apps/{app-id}/services/{service-id}/instances")
- public void getServiceInstances(HttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespaceId,
- @PathParam("app-id") String appId,
- @PathParam("service-id") String serviceId) throws Exception {
- try {
- ProgramId programId = validateAndGetNamespace(namespaceId)
- .app(appId, getLatestAppVersion(new NamespaceId(namespaceId), appId))
- .service(serviceId);
- lifecycleService.ensureProgramExists(programId);
- int instances = store.getServiceInstances(programId);
- responder.sendJson(HttpResponseStatus.OK,
- GSON.toJson(new ServiceInstances(instances, getInstanceCount(programId, serviceId))));
- } catch (SecurityException e) {
- responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
- }
+ ProgramHandlerUtil.toJson(
+ lifecycleService.list(NamespaceHelper.validateNamespace(namespaceQueryAdmin,namespaceId),
+ ProgramType.WORKER)));
}
/**
@@ -2091,7 +1765,7 @@ public void getServiceAvailabilityVersioned(HttpRequest request, HttpResponder r
@PathParam("service-type") String serviceType,
@PathParam("program-name") String programName) throws Exception {
// Currently, we only support services and sparks as the service-type
- ProgramType programType = getProgramType(serviceType);
+ ProgramType programType = ProgramType.valueOfCategoryName(serviceType, BadRequestException::new);
if (!ServiceDiscoverable.getUserServiceTypes().contains(programType)) {
throw new BadRequestException(
"Only service or spark is support for service availability check");
@@ -2128,230 +1802,14 @@ public void getServiceAvailabilityVersioned(HttpRequest request, HttpResponder r
responder.sendString(HttpResponseStatus.OK, "Service is available to accept requests.");
}
- /**
- * Set instances of a service.
- */
- @PUT
- @Path("/apps/{app-id}/services/{service-id}/instances")
- @AuditPolicy(AuditDetail.REQUEST_BODY)
- public void setServiceInstances(FullHttpRequest request, HttpResponder responder,
- @PathParam("namespace-id") String namespaceId,
- @PathParam("app-id") String appId,
- @PathParam("service-id") String serviceId) throws Exception {
- try {
- ProgramId programId = new ApplicationId(namespaceId, appId,
- getLatestAppVersion(new NamespaceId(namespaceId), appId))
- .program(ProgramType.SERVICE, serviceId);
- Store.ensureProgramExists(programId, store.getApplication(programId.getParent()));
-
- int instances = getInstances(request);
- lifecycleService.setInstances(programId, instances);
- responder.sendStatus(HttpResponseStatus.OK);
- } catch (SecurityException e) {
- responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
- } catch (Throwable throwable) {
- if (respondIfElementNotFound(throwable, responder)) {
- return;
- }
- throw throwable;
- }
- }
-
- /**
- * Get requested and provisioned instances for a program type. The program type passed here should
- * be one that can have instances (flows, services, ...) Requires caller to do this validation.
- */
- private BatchRunnableInstances getProgramInstances(BatchRunnable runnable,
- ApplicationSpecification spec,
- ProgramId programId) {
- int requested;
- String programName = programId.getProgram();
- ProgramType programType = programId.getType();
- if (programType == ProgramType.WORKER) {
- if (!spec.getWorkers().containsKey(programName)) {
- return new BatchRunnableInstances(runnable, HttpResponseStatus.NOT_FOUND.code(),
- "Worker: " + programName + " not found");
- }
- requested = spec.getWorkers().get(programName).getInstances();
-
- } else if (programType == ProgramType.SERVICE) {
- if (!spec.getServices().containsKey(programName)) {
- return new BatchRunnableInstances(runnable, HttpResponseStatus.NOT_FOUND.code(),
- "Service: " + programName + " not found");
- }
- requested = spec.getServices().get(programName).getInstances();
-
- } else {
- return new BatchRunnableInstances(runnable, HttpResponseStatus.BAD_REQUEST.code(),
- "Instances not supported for program type + " + programType);
- }
- int provisioned = getInstanceCount(programId, programName);
- // use the pretty name of program types to be consistent
- return new BatchRunnableInstances(runnable, HttpResponseStatus.OK.code(), provisioned,
- requested);
- }
-
- /**
- * Returns the number of instances currently running for different runnables for different
- * programs
- */
- private int getInstanceCount(ProgramId programId, String runnableId) {
- ProgramLiveInfo info = runtimeService.getLiveInfo(programId);
- int count = 0;
- if (info instanceof NotRunningProgramLiveInfo) {
- return count;
- }
- if (info instanceof Containers) {
- Containers containers = (Containers) info;
- for (Containers.ContainerInfo container : containers.getContainers()) {
- if (container.getName().equals(runnableId)) {
- count++;
- }
- }
- return count;
- }
- // TODO: CDAP-1091: For standalone mode, returning the requested instances instead of provisioned only for services.
- // Doing this only for services to keep it consistent with the existing contract for flowlets right now.
- // The get instances contract for both flowlets and services should be re-thought and fixed as part of CDAP-1091
- if (programId.getType() == ProgramType.SERVICE) {
- return getRequestedServiceInstances(programId);
- }
-
- // Not running on YARN default 1
- return 1;
- }
-
- private int getRequestedServiceInstances(ProgramId serviceId) {
- // Not running on YARN, get it from store
- return store.getServiceInstances(serviceId);
- }
-
private boolean isDebugAllowed(ProgramType programType) {
return EnumSet.of(ProgramType.SERVICE, ProgramType.WORKER).contains(programType);
}
- private boolean canHaveInstances(ProgramType programType) {
- return EnumSet.of(ProgramType.SERVICE, ProgramType.WORKER).contains(programType);
- }
-
- private List validateAndGetBatchInput(FullHttpRequest request,
- Type type)
- throws BadRequestException, IOException {
-
- List programs;
- try (Reader reader = new InputStreamReader(new ByteBufInputStream(request.content()),
- StandardCharsets.UTF_8)) {
- try {
- programs = DECODE_GSON.fromJson(reader, type);
- if (programs == null) {
- throw new BadRequestException(
- "Request body is invalid json, please check that it is a json array.");
- }
- } catch (JsonSyntaxException e) {
- throw new BadRequestException("Request body is invalid json: " + e.getMessage());
- }
- }
-
- // validate input
- for (BatchProgram program : programs) {
- try {
- program.validate();
- } catch (IllegalArgumentException e) {
- throw new BadRequestException(
- "Must provide valid appId, programType, and programId for each object: "
- + e.getMessage());
- }
- }
- return programs;
- }
-
- private void updateLogLevels(FullHttpRequest request, HttpResponder responder, String namespace,
- String appName,
- String appVersion, String type, String programName,
- String runId) throws Exception {
- ProgramType programType = getProgramType(type);
- try {
- // we are decoding the body to Map instead of Map here since Gson will
- // serialize invalid enum values to null, which is allowed for log level, instead of throw an Exception.
- lifecycleService.updateProgramLogLevels(
- new ApplicationId(namespace, appName, appVersion).program(programType, programName),
- transformLogLevelsMap(decodeArguments(request)), runId);
- responder.sendStatus(HttpResponseStatus.OK);
- } catch (JsonSyntaxException e) {
- throw new BadRequestException("Invalid JSON in body");
- } catch (IllegalArgumentException e) {
- throw new BadRequestException(e.getMessage());
- } catch (SecurityException e) {
- throw new UnauthorizedException("Unauthorized to update the log levels");
- }
- }
-
- private void resetLogLevels(FullHttpRequest request, HttpResponder responder, String namespace,
- String appName,
- String appVersion, String type, String programName,
- String runId) throws Exception {
- ProgramType programType = getProgramType(type);
- try {
- Set loggerNames = parseBody(request, SET_STRING_TYPE);
- lifecycleService.resetProgramLogLevels(
- new ApplicationId(namespace, appName, appVersion).program(programType, programName),
- loggerNames == null ? Collections.emptySet() : loggerNames, runId);
- responder.sendStatus(HttpResponseStatus.OK);
- } catch (JsonSyntaxException e) {
- throw new BadRequestException("Invalid JSON in body");
- } catch (SecurityException e) {
- throw new UnauthorizedException("Unauthorized to reset the log levels");
- }
- }
-
- private NamespaceId validateAndGetNamespace(String namespace) throws NamespaceNotFoundException {
- NamespaceId namespaceId = new NamespaceId(namespace);
- try {
- namespaceQueryAdmin.get(namespaceId);
- } catch (NamespaceNotFoundException e) {
- throw e;
- } catch (Exception e) {
- // This can only happen when NamespaceAdmin uses HTTP to interact with namespaces.
- // Within AppFabric, NamespaceAdmin is bound to DefaultNamespaceAdmin which directly interacts with MDS.
- // Hence, this should never happen.
- throw Throwables.propagate(e);
- }
- return namespaceId;
- }
-
- /**
- * Parses the give program type into {@link ProgramType} object.
- *
- * @param programType the program type to parse.
- * @throws BadRequestException if the given program type is not a valid {@link ProgramType}.
- */
- private ProgramType getProgramType(String programType) throws BadRequestException {
- try {
- return ProgramType.valueOfCategoryName(programType);
- } catch (Exception e) {
- throw new BadRequestException(String.format("Invalid program type '%s'", programType), e);
- }
- }
-
/**
* Used to filter out RunRecords initiated by a tethered instance
*/
private boolean isTetheredRunRecord(RunRecord runRecord) {
return runRecord.getPeerName() != null;
}
-
- /**
- * @param namespaceId namespace Id
- * @param appId app Id
- * @return latest app version
- */
- private String getLatestAppVersion(NamespaceId namespaceId, String appId)
- throws ApplicationNotFoundException {
- ApplicationMeta latestApplicationMeta = store.getLatest(namespaceId.appReference(appId));
- if (latestApplicationMeta == null) {
- throw new ApplicationNotFoundException(
- new ApplicationReference(namespaceId.getNamespace(), appId));
- }
- return latestApplicationMeta.getSpec().getAppVersion();
- }
}
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/ProgramRuntimeHttpHandler.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/ProgramRuntimeHttpHandler.java
new file mode 100644
index 000000000000..a6cc26a7d0ce
--- /dev/null
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/ProgramRuntimeHttpHandler.java
@@ -0,0 +1,566 @@
+/*
+ * Copyright © 2025 Cask Data, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.cdap.cdap.gateway.handlers;
+
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import io.cdap.cdap.api.app.ApplicationSpecification;
+import io.cdap.cdap.app.runtime.ProgramRuntimeService;
+import io.cdap.cdap.app.store.Store;
+import io.cdap.cdap.common.ApplicationNotFoundException;
+import io.cdap.cdap.common.BadRequestException;
+import io.cdap.cdap.common.NotFoundException;
+import io.cdap.cdap.common.conf.Constants;
+import io.cdap.cdap.common.namespace.NamespaceQueryAdmin;
+import io.cdap.cdap.common.security.AuditDetail;
+import io.cdap.cdap.common.security.AuditPolicy;
+import io.cdap.cdap.gateway.handlers.util.AbstractAppFabricHttpHandler;
+import io.cdap.cdap.gateway.handlers.util.NamespaceHelper;
+import io.cdap.cdap.gateway.handlers.util.ProgramHandlerUtil;
+import io.cdap.cdap.internal.app.services.ProgramLifecycleService;
+import io.cdap.cdap.internal.app.store.RunRecordDetail;
+import io.cdap.cdap.proto.BatchRunnable;
+import io.cdap.cdap.proto.BatchRunnableInstances;
+import io.cdap.cdap.proto.Containers;
+import io.cdap.cdap.proto.Instances;
+import io.cdap.cdap.proto.NotRunningProgramLiveInfo;
+import io.cdap.cdap.proto.ProgramLiveInfo;
+import io.cdap.cdap.proto.ProgramType;
+import io.cdap.cdap.proto.ServiceInstances;
+import io.cdap.cdap.proto.id.ApplicationId;
+import io.cdap.cdap.proto.id.ApplicationReference;
+import io.cdap.cdap.proto.id.ProgramId;
+import io.cdap.cdap.proto.id.ProgramReference;
+import io.cdap.cdap.proto.security.StandardPermission;
+import io.cdap.cdap.security.spi.authentication.AuthenticationContext;
+import io.cdap.cdap.security.spi.authorization.AccessEnforcer;
+import io.cdap.cdap.security.spi.authorization.UnauthorizedException;
+import io.cdap.http.HttpResponder;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+/**
+ * {@link io.cdap.http.HttpHandler} to manage runtime of Programs for v3 REST APIs
+ *
+ * Only supported program types for this handler are {@link ProgramType#SERVICE} and {@link ProgramType#WORKER}.
+ */
+@Singleton
+@Path(Constants.Gateway.API_VERSION_3 + "/namespaces/{namespace-id}")
+public class ProgramRuntimeHttpHandler extends AbstractAppFabricHttpHandler {
+
+ private final ProgramLifecycleService lifecycleService;
+ private final ProgramRuntimeService runtimeService;
+ private final Store store;
+ private final NamespaceQueryAdmin namespaceQueryAdmin;
+ private final AccessEnforcer accessEnforcer;
+ private final AuthenticationContext authenticationContext;
+
+ @Inject
+ public ProgramRuntimeHttpHandler(ProgramLifecycleService lifecycleService, Store store,
+ ProgramRuntimeService runtimeService, NamespaceQueryAdmin namespaceQueryAdmin, AccessEnforcer accessEnforcer,
+ AuthenticationContext authenticationContext) {
+ this.lifecycleService = lifecycleService;
+ this.runtimeService = runtimeService;
+ this.store = store;
+ this.namespaceQueryAdmin = namespaceQueryAdmin;
+ this.accessEnforcer = accessEnforcer;
+ this.authenticationContext = authenticationContext;
+ }
+
+ private static final Type BATCH_RUNNABLES_TYPE = new TypeToken>() {
+ }.getType();
+
+ /**
+ * Returns the number of instances for all program runnables that are passed into the data. The
+ * data is an array of Json objects where each object must contain the following three elements:
+ * appId, programType, and programId (flow name, service name). Retrieving instances only applies
+ * to flows, and user services. For flows, another parameter, "runnableId", must be provided. This
+ * corresponds to the flowlet/runnable for which to retrieve the instances.
+ *
+ * Example input:
+ *
+ * [{"appId": "App1", "programType": "Service", "programId": "Service1", "runnableId": "Runnable1"},
+ * {"appId": "App1", "programType": "Mapreduce", "programId": "Mapreduce2"}]
+ *
+ *
+ * The response will be an array of JsonObjects each of which will contain the three input
+ * parameters as well as 3 fields:
+ *
+ * - "provisioned" which maps to the number of instances actually provided for the input runnable;
+ * - "requested" which maps to the number of instances the user has requested for the input runnable; and
+ * - "statusCode" which maps to the http status code for the data in that JsonObjects (200, 400, 404).
+ *
+ *
+ * If an error occurs in the input (for the example above, Flowlet1 does not exist), then all JsonObjects for
+ * which the parameters have a valid instances will have the provisioned and requested fields status code fields
+ * but all JsonObjects for which the parameters are not valid will have an error message and statusCode.
+ *
+ * For example, if there is no Flowlet1 in the above data, then the response could be 200 OK with the following data:
+ *
+ *
+ * [{"appId": "App1", "programType": "Service", "programId": "Service1", "runnableId": "Runnable1",
+ * "statusCode": 200, "provisioned": 2, "requested": 2},
+ * {"appId": "App1", "programType": "Mapreduce", "programId": "Mapreduce2", "statusCode": 400,
+ * "error": "Program type 'Mapreduce' is not a valid program type to get instances"}]
+ *
+ */
+ @POST
+ @Path("/instances")
+ @AuditPolicy(AuditDetail.REQUEST_BODY)
+ public void getInstances(FullHttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespaceId) throws IOException, BadRequestException {
+
+ List runnables = ProgramHandlerUtil.validateAndGetBatchInput(request, BATCH_RUNNABLES_TYPE);
+
+ // cache app specs to perform fewer store lookups
+ Map appSpecs = new HashMap<>();
+
+ List output = new ArrayList<>(runnables.size());
+ for (BatchRunnable runnable : runnables) {
+ // cant get instances for things that are not services, or workers
+ if (!canHaveInstances(runnable.getProgramType())) {
+ output.add(
+ new BatchRunnableInstances(runnable, HttpResponseStatus.BAD_REQUEST.code(),
+ String.format("Program type '%s' is not a valid program type to get instances",
+ runnable.getProgramType().getPrettyName())));
+ continue;
+ }
+
+ ApplicationId appId = new ApplicationId(namespaceId, runnable.getAppId());
+ try {
+ appId = store.getLatestApp(new ApplicationReference(namespaceId, runnable.getAppId()));
+ } catch (ApplicationNotFoundException e) {
+ output.add(new BatchRunnableInstances(runnable, HttpResponseStatus.NOT_FOUND.code(),
+ String.format("App: %s not found", appId)));
+ continue;
+ }
+
+ // populate spec cache if this is the first time we've seen the appid.
+ if (!appSpecs.containsKey(appId)) {
+ appSpecs.put(appId, store.getApplication(appId));
+ }
+
+ ApplicationSpecification spec = appSpecs.get(appId);
+ ProgramId programId = appId.program(runnable.getProgramType(), runnable.getProgramId());
+ output.add(getProgramInstances(runnable, spec, programId));
+ }
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(output));
+ }
+
+ /**
+ * Return the number of instances of a service.
+ */
+ @GET
+ @Path("/apps/{app-id}/services/{service-id}/instances")
+ public void getServiceInstances(HttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespaceId,
+ @PathParam("app-id") String appId,
+ @PathParam("service-id") String serviceId) throws Exception {
+ try {
+ NamespaceHelper.validateNamespace(namespaceQueryAdmin, namespaceId);
+ ProgramId programId = store.getLatestApp(new ApplicationReference(namespaceId, appId)).service(serviceId);
+ lifecycleService.ensureProgramExists(programId);
+ int instances = store.getServiceInstances(programId);
+ responder.sendJson(HttpResponseStatus.OK,
+ ProgramHandlerUtil.toJson(new ServiceInstances(instances, getInstanceCount(programId, serviceId))));
+ } catch (SecurityException e) {
+ responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
+ }
+ }
+
+ /**
+ * Set instances of a service.
+ */
+ @PUT
+ @Path("/apps/{app-id}/services/{service-id}/instances")
+ @AuditPolicy(AuditDetail.REQUEST_BODY)
+ public void setServiceInstances(FullHttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespaceId,
+ @PathParam("app-id") String appId,
+ @PathParam("service-id") String serviceId) throws Exception {
+ try {
+ ProgramId programId = store.getLatestApp(new ApplicationReference(namespaceId, appId))
+ .program(ProgramType.SERVICE, serviceId);
+ Store.ensureProgramExists(programId, store.getApplication(programId.getParent()));
+ int instances = getInstances(request);
+ accessEnforcer.enforce(programId, authenticationContext.getPrincipal(), StandardPermission.UPDATE);
+ setInstances(programId, instances);
+ responder.sendStatus(HttpResponseStatus.OK);
+ } catch (SecurityException e) {
+ responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
+ } catch (Throwable throwable) {
+ if (respondIfElementNotFound(throwable, responder)) {
+ return;
+ }
+ throw throwable;
+ }
+ }
+
+ /**
+ * Returns number of instances of a worker.
+ */
+ @GET
+ @Path("/apps/{app-id}/workers/{worker-id}/instances")
+ public void getWorkerInstances(HttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespaceId,
+ @PathParam("app-id") String appId,
+ @PathParam("worker-id") String workerId) throws Exception {
+ try {
+ NamespaceHelper.validateNamespace(namespaceQueryAdmin, namespaceId);
+ ProgramId programId = store.getLatestApp(new ApplicationReference(namespaceId, appId)).worker(workerId);
+ lifecycleService.ensureProgramExists(programId);
+ int count = store.getWorkerInstances(programId);
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(new Instances(count)));
+ } catch (SecurityException e) {
+ responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
+ } catch (Throwable e) {
+ if (respondIfElementNotFound(e, responder)) {
+ return;
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Sets the number of instances of a worker.
+ */
+ @PUT
+ @Path("/apps/{app-id}/workers/{worker-id}/instances")
+ @AuditPolicy(AuditDetail.REQUEST_BODY)
+ public void setWorkerInstances(FullHttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespaceId,
+ @PathParam("app-id") String appId,
+ @PathParam("worker-id") String workerId) throws Exception {
+ int instances = getInstances(request);
+ try {
+ ProgramId programId = store.getLatestApp(new ApplicationReference(namespaceId, appId))
+ .program(ProgramType.WORKER, workerId);
+ Store.ensureProgramExists(programId, store.getApplication(programId.getParent()));
+ accessEnforcer.enforce(programId, authenticationContext.getPrincipal(), StandardPermission.UPDATE);
+ setInstances(programId, instances);
+ responder.sendStatus(HttpResponseStatus.OK);
+ } catch (SecurityException e) {
+ responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
+ } catch (Throwable e) {
+ if (respondIfElementNotFound(e, responder)) {
+ return;
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Gets runtime information about a running program.
+ *
+ * @param request the HTTP request
+ * @param responder the HTTP responder
+ * @param namespaceId namespaceId for the program
+ * @param appId appId for the program
+ * @param programCategory program type
+ * @param programId the program Id
+ *
+ * @throws BadRequestException
+ * @throws ApplicationNotFoundException
+ */
+ @GET
+ @Path("/apps/{app-id}/{program-category}/{program-id}/live-info")
+ @SuppressWarnings("unused")
+ public void liveInfo(HttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespaceId,
+ @PathParam("app-id") String appId, @PathParam("program-category") String programCategory,
+ @PathParam("program-id") String programId)
+ throws BadRequestException, ApplicationNotFoundException {
+ ProgramType type = ProgramType.valueOfCategoryName(programCategory, BadRequestException::new);
+ ProgramId program = store.getLatestApp(new ApplicationReference(namespaceId, appId))
+ .program(type, programId);
+ getLiveInfo(responder, program, runtimeService);
+ }
+
+ /**
+ * Update the log level for a running program according to the request body. Currently supported
+ * program types are {@link ProgramType#SERVICE} and {@link ProgramType#WORKER}. The request body
+ * is expected to contain a map of log levels, where key is loggername, value is one of the valid
+ * {@link org.apache.twill.api.logging.LogEntry.Level} or null.
+ *
+ */
+ @PUT
+ @Path("/apps/{app-name}/{program-type}/{program-name}/runs/{run-id}/loglevels")
+ @AuditPolicy(AuditDetail.REQUEST_BODY)
+ public void updateProgramLogLevels(FullHttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespace,
+ @PathParam("app-name") String appName,
+ @PathParam("program-type") String type,
+ @PathParam("program-name") String programName,
+ @PathParam("run-id") String runId) throws Exception {
+ RunRecordDetail run = getRunRecordDetailFromId(namespace, appName, type, programName, runId);
+ updateLogLevels(request, responder, namespace, appName, run.getVersion(), type, programName,
+ runId);
+ }
+
+ /**
+ * Update the log level for a running program according to the request body.
+ * Deprecated : Run-id is sufficient to identify a program run.
+ */
+ @Deprecated
+ @PUT
+ @Path("/apps/{app-name}/versions/{app-version}/{program-type}/{program-name}/runs/{run-id}/loglevels")
+ @AuditPolicy(AuditDetail.REQUEST_BODY)
+ public void updateProgramLogLevelsVersioned(FullHttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespace,
+ @PathParam("app-name") String appName,
+ @PathParam("app-version") String appVersion,
+ @PathParam("program-type") String type,
+ @PathParam("program-name") String programName,
+ @PathParam("run-id") String runId) throws Exception {
+ updateLogLevels(request, responder, namespace, appName, appVersion, type, programName, runId);
+ }
+
+ /**
+ * Reset the log level for a running program back to where it starts. Currently supported program
+ * types are {@link ProgramType#SERVICE} and {@link ProgramType#WORKER}. The request body can
+ * either be empty, which will reset all loggers for the program, or contain a list of logger
+ * names, which will reset for these particular logger names for the program.
+ */
+ @POST
+ @Path("/apps/{app-name}/{program-type}/{program-name}/runs/{run-id}/resetloglevels")
+ @AuditPolicy(AuditDetail.REQUEST_BODY)
+ public void resetProgramLogLevels(FullHttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespace,
+ @PathParam("app-name") String appName,
+ @PathParam("program-type") String type,
+ @PathParam("program-name") String programName,
+ @PathParam("run-id") String runId) throws Exception {
+ RunRecordDetail run = getRunRecordDetailFromId(namespace, appName, type, programName, runId);
+ resetLogLevels(request, responder, namespace, appName, run.getVersion(), type, programName,
+ runId);
+ }
+
+ /**
+ * Reset the log level for a running program back to where it starts.
+ *
+ * Deprecated : Run-id is sufficient to identify a program run.
+ */
+ @POST
+ @Path("/apps/{app-name}/versions/{app-version}/{program-type}/{program-name}/runs/{run-id}/resetloglevels")
+ @AuditPolicy(AuditDetail.REQUEST_BODY)
+ public void resetProgramLogLevelsVersioned(FullHttpRequest request, HttpResponder responder,
+ @PathParam("namespace-id") String namespace,
+ @PathParam("app-name") String appName,
+ @PathParam("app-version") String appVersion,
+ @PathParam("program-type") String type,
+ @PathParam("program-name") String programName,
+ @PathParam("run-id") String runId) throws Exception {
+ resetLogLevels(request, responder, namespace, appName, appVersion, type, programName, runId);
+ }
+
+ private void getLiveInfo(HttpResponder responder, ProgramId programId,
+ ProgramRuntimeService runtimeService) {
+ try {
+ responder.sendJson(HttpResponseStatus.OK, ProgramHandlerUtil.toJson(runtimeService.getLiveInfo(programId)));
+ } catch (SecurityException e) {
+ responder.sendStatus(HttpResponseStatus.UNAUTHORIZED);
+ }
+ }
+
+ /**
+ * Returns the number of instances currently running for different runnables for different
+ * programs
+ */
+ private int getInstanceCount(ProgramId programId, String runnableId) {
+ ProgramLiveInfo info = runtimeService.getLiveInfo(programId);
+ int count = 0;
+ if (info instanceof NotRunningProgramLiveInfo) {
+ return count;
+ }
+ if (info instanceof Containers) {
+ Containers containers = (Containers) info;
+ for (Containers.ContainerInfo container : containers.getContainers()) {
+ if (container.getName().equals(runnableId)) {
+ count++;
+ }
+ }
+ return count;
+ }
+ // TODO: CDAP-1091: For standalone mode, returning the requested instances instead of provisioned only for services.
+ // Doing this only for services to keep it consistent with the existing contract for flowlets right now.
+ // The get instances contract for both flowlets and services should be re-thought and fixed as part of CDAP-1091
+ if (programId.getType() == ProgramType.SERVICE) {
+ return getRequestedServiceInstances(programId);
+ }
+
+ // Not running on YARN default 1
+ return 1;
+ }
+
+ /**
+ * Get requested and provisioned instances for a program type. The program type passed here should
+ * be one that can have instances (flows, services, ...) Requires caller to do this validation.
+ */
+ private BatchRunnableInstances getProgramInstances(BatchRunnable runnable,
+ ApplicationSpecification spec,
+ ProgramId programId) {
+ int requested;
+ String programName = programId.getProgram();
+ ProgramType programType = programId.getType();
+ if (programType == ProgramType.WORKER) {
+ if (!spec.getWorkers().containsKey(programName)) {
+ return new BatchRunnableInstances(runnable, HttpResponseStatus.NOT_FOUND.code(),
+ "Worker: " + programName + " not found");
+ }
+ requested = spec.getWorkers().get(programName).getInstances();
+
+ } else if (programType == ProgramType.SERVICE) {
+ if (!spec.getServices().containsKey(programName)) {
+ return new BatchRunnableInstances(runnable, HttpResponseStatus.NOT_FOUND.code(),
+ "Service: " + programName + " not found");
+ }
+ requested = spec.getServices().get(programName).getInstances();
+
+ } else {
+ return new BatchRunnableInstances(runnable, HttpResponseStatus.BAD_REQUEST.code(),
+ "Instances not supported for program type + " + programType);
+ }
+ int provisioned = getInstanceCount(programId, programName);
+ // use the pretty name of program types to be consistent
+ return new BatchRunnableInstances(runnable, HttpResponseStatus.OK.code(), provisioned,
+ requested);
+ }
+
+ private int getRequestedServiceInstances(ProgramId serviceId) {
+ // Not running on YARN, get it from store
+ return store.getServiceInstances(serviceId);
+ }
+
+ private boolean canHaveInstances(ProgramType programType) {
+ return EnumSet.of(ProgramType.SERVICE, ProgramType.WORKER).contains(programType);
+ }
+
+ private void resetLogLevels(FullHttpRequest request, HttpResponder responder, String namespace,
+ String appName,
+ String appVersion, String type, String programName,
+ String runId) throws Exception {
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
+ try {
+ Set loggerNames = parseBody(request, SET_STRING_TYPE);
+ ProgramId programId = new ApplicationId(namespace, appName, appVersion).program(programType, programName);
+ accessEnforcer.enforce(programId, authenticationContext.getPrincipal(), StandardPermission.UPDATE);
+ runtimeService.resetProgramLogLevels(
+ new ApplicationId(namespace, appName, appVersion).program(programType, programName),
+ loggerNames == null ? Collections.emptySet() : loggerNames, runId);
+ responder.sendStatus(HttpResponseStatus.OK);
+ } catch (JsonSyntaxException e) {
+ throw new BadRequestException("Invalid JSON in body");
+ } catch (SecurityException e) {
+ throw new UnauthorizedException("Unauthorized to reset the log levels");
+ }
+ }
+
+ private void updateLogLevels(FullHttpRequest request, HttpResponder responder, String namespace,
+ String appName,
+ String appVersion, String type, String programName,
+ String runId) throws Exception {
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
+ try {
+ // we are decoding the body to Map instead of Map here since Gson will
+ // serialize invalid enum values to null, which is allowed for log level, instead of throw an Exception.
+ ProgramId programId = new ApplicationId(namespace, appName, appVersion).program(programType, programName);
+ accessEnforcer.enforce(programId, authenticationContext.getPrincipal(), StandardPermission.UPDATE);
+ runtimeService.updateProgramLogLevels(
+ new ApplicationId(namespace, appName, appVersion).program(programType, programName),
+ transformLogLevelsMap(decodeArguments(request)), runId);
+ responder.sendStatus(HttpResponseStatus.OK);
+ } catch (JsonSyntaxException e) {
+ throw new BadRequestException("Invalid JSON in body");
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException(e.getMessage());
+ } catch (SecurityException e) {
+ throw new UnauthorizedException("Unauthorized to update the log levels");
+ }
+ }
+
+ /**
+ * Set instances for the given program. Only supported program types for this action are {@link
+ * ProgramType#SERVICE} and {@link ProgramType#WORKER}.
+ *
+ * @param programId the {@link ProgramId} of the program for which instances are to be
+ * updated
+ * @param instances the number of instances to be updated.
+ * @throws InterruptedException if there is an error while asynchronously updating instances
+ * @throws ExecutionException if there is an error while asynchronously updating instances
+ * @throws BadRequestException if the number of instances specified is less than 0
+ * @throws UnauthorizedException if the user does not have privileges to set instances for the
+ * specified program. To set instances for a program, a user needs {@link
+ * StandardPermission#UPDATE} on the program.
+ */
+ private void setInstances(ProgramId programId, int instances) throws Exception {
+ if (instances < 1) {
+ throw new BadRequestException(
+ String.format("Instance count should be greater than 0. Got %s.", instances));
+ }
+ switch (programId.getType()) {
+ case SERVICE:
+ int oldInstances = store.getServiceInstances(programId);
+ if (oldInstances != instances) {
+ store.setServiceInstances(programId, instances);
+ runtimeService.setInstances(programId, instances, instances);
+ }
+ break;
+ case WORKER:
+ oldInstances = store.getWorkerInstances(programId);
+ if (oldInstances != instances) {
+ store.setWorkerInstances(programId, instances);
+ runtimeService.setInstances(programId, instances, instances);
+ }
+ break;
+ default:
+ throw new BadRequestException(
+ String.format("Setting instances for program type %s is not supported",
+ programId.getType().getPrettyName()));
+ }
+ }
+
+ private RunRecordDetail getRunRecordDetailFromId(String namespaceId, String appName, String type,
+ String programName, String runId) throws NotFoundException, BadRequestException {
+ ProgramType programType = ProgramType.valueOfCategoryName(type, BadRequestException::new);
+ ProgramReference programRef = new ApplicationReference(namespaceId, appName).program(programType,
+ programName);
+ RunRecordDetail runRecordMeta = store.getRun(programRef, runId);
+ if (runRecordMeta == null) {
+ throw new NotFoundException(
+ String.format("No run record found for program %s and runID: %s", programRef, runId));
+ }
+ return runRecordMeta;
+ }
+}
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/util/NamespaceHelper.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/util/NamespaceHelper.java
new file mode 100644
index 000000000000..63224673bef9
--- /dev/null
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/util/NamespaceHelper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2025 Cask Data, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.cdap.cdap.gateway.handlers.util;
+
+import com.google.common.base.Throwables;
+import io.cdap.cdap.common.NamespaceNotFoundException;
+import io.cdap.cdap.common.namespace.NamespaceQueryAdmin;
+import io.cdap.cdap.proto.id.NamespaceId;
+
+/**
+ * Helper class for Namespace operations.
+ */
+public class NamespaceHelper {
+
+ private NamespaceHelper() {
+ }
+
+ /**
+ * Validates that the namespace exists and gets the NamespaceId
+ *
+ * @param namespaceQueryAdmin query admin for namespace operations
+ * @param namespace the namespace to validate
+ * @return NamespaceId
+ *
+ * @throws NamespaceNotFoundException if namespace is not found
+ */
+ public static NamespaceId validateNamespace(NamespaceQueryAdmin namespaceQueryAdmin, String namespace)
+ throws NamespaceNotFoundException {
+ NamespaceId namespaceId = new NamespaceId(namespace);
+ try {
+ namespaceQueryAdmin.get(namespaceId);
+ } catch (NamespaceNotFoundException e) {
+ throw e;
+ } catch (Exception e) {
+ // This can only happen when NamespaceAdmin uses HTTP to interact with namespaces.
+ // Within AppFabric, NamespaceAdmin is bound to DefaultNamespaceAdmin which directly interacts with MDS.
+ // Hence, this should never happen.
+ throw Throwables.propagate(e);
+ }
+ return namespaceId;
+ }
+}
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/util/ProgramHandlerUtil.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/util/ProgramHandlerUtil.java
new file mode 100644
index 000000000000..24055e70eeea
--- /dev/null
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/gateway/handlers/util/ProgramHandlerUtil.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright © 2025 Cask Data, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.cdap.cdap.gateway.handlers.util;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonSyntaxException;
+import com.google.inject.Inject;
+import io.cdap.cdap.api.schedule.Trigger;
+import io.cdap.cdap.app.store.Store;
+import io.cdap.cdap.common.BadRequestException;
+import io.cdap.cdap.common.io.CaseInsensitiveEnumTypeAdapterFactory;
+import io.cdap.cdap.internal.app.ApplicationSpecificationAdapter;
+import io.cdap.cdap.internal.app.runtime.schedule.constraint.ConstraintCodec;
+import io.cdap.cdap.internal.app.runtime.schedule.trigger.SatisfiableTrigger;
+import io.cdap.cdap.internal.app.runtime.schedule.trigger.TriggerCodec;
+import io.cdap.cdap.internal.schedule.constraint.Constraint;
+import io.cdap.cdap.proto.BatchProgram;
+import io.cdap.cdap.security.spi.authentication.AuthenticationContext;
+import io.cdap.cdap.security.spi.authorization.AccessEnforcer;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.handler.codec.http.FullHttpRequest;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.validation.constraints.NotNull;
+
+public class ProgramHandlerUtil {
+
+ private ProgramHandlerUtil() {
+ }
+
+ /**
+ * Json serializer/deserializer.
+ */
+ private static final Gson GSON = ApplicationSpecificationAdapter
+ .addTypeAdapters(new GsonBuilder())
+ .registerTypeAdapter(Trigger.class, new TriggerCodec())
+ .registerTypeAdapter(SatisfiableTrigger.class, new TriggerCodec())
+ .registerTypeAdapter(Constraint.class, new ConstraintCodec())
+ .create();
+
+ /**
+ * Json serde for decoding request. It uses a case insensitive enum adapter.
+ */
+ private static final Gson DECODE_GSON = ApplicationSpecificationAdapter
+ .addTypeAdapters(new GsonBuilder())
+ .registerTypeAdapterFactory(new CaseInsensitiveEnumTypeAdapterFactory())
+ .registerTypeAdapter(Trigger.class, new TriggerCodec())
+ .registerTypeAdapter(SatisfiableTrigger.class, new TriggerCodec())
+ .registerTypeAdapter(Constraint.class, new ConstraintCodec())
+ .create();
+
+ public static String toJson(Object object) {
+ return GSON.toJson(object);
+ }
+
+ public static String toJson(Object object, @NotNull Type type) {
+ return GSON.toJson(object, type);
+ }
+
+ public static T fromJson(@NotNull Reader reader, Class type) {
+ return DECODE_GSON.fromJson(reader, type);
+ }
+
+ public static T fromJson(@Nullable JsonElement json, Class type) {
+ return DECODE_GSON.fromJson(json, type);
+ }
+
+ public static List validateAndGetBatchInput(FullHttpRequest request,
+ Type type)
+ throws BadRequestException, IOException {
+
+ List programs;
+ try (Reader reader = new InputStreamReader(new ByteBufInputStream(request.content()),
+ StandardCharsets.UTF_8)) {
+ try {
+ programs = DECODE_GSON.fromJson(reader, type);
+ if (programs == null) {
+ throw new BadRequestException(
+ "Request body is invalid json, please check that it is a json array.");
+ }
+ } catch (JsonSyntaxException e) {
+ throw new BadRequestException("Request body is invalid json: " + e.getMessage());
+ }
+ }
+
+ // validate input
+ for (BatchProgram program : programs) {
+ try {
+ program.validate();
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException(
+ "Must provide valid appId, programType, and programId for each object: "
+ + e.getMessage());
+ }
+ }
+ return programs;
+ }
+}
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/preview/DefaultPreviewRunner.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/preview/DefaultPreviewRunner.java
index 3d650c7acfc8..85549cb62c1e 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/preview/DefaultPreviewRunner.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/preview/DefaultPreviewRunner.java
@@ -46,6 +46,7 @@
import io.cdap.cdap.data2.dataset2.lib.table.leveldb.LevelDBTableService;
import io.cdap.cdap.internal.app.deploy.ProgramTerminator;
import io.cdap.cdap.internal.app.runtime.AbstractListener;
+import io.cdap.cdap.internal.app.runtime.ProgramStartRequest;
import io.cdap.cdap.internal.app.services.ApplicationLifecycleService;
import io.cdap.cdap.internal.app.services.ProgramLifecycleService;
import io.cdap.cdap.internal.app.services.ProgramNotificationSubscriberService;
@@ -208,7 +209,10 @@ public Future startPreview(PreviewRequest previewRequest) throws
}
LOG.debug("Starting preview for {}", programId);
- ProgramController controller = programLifecycleService.start(programId, userProps, false, true);
+ ProgramStartRequest startRequest = programLifecycleService.prepareStart(programId, userProps, false, true);
+ ProgramController controller = programRuntimeService.run(
+ startRequest.getProgramDescriptor(), startRequest.getProgramOptions(), startRequest.getRunId())
+ .getController();
long startTimeMillis = System.currentTimeMillis();
AtomicBoolean timeout = new AtomicBoolean();
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/ProgramStartRequest.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/ProgramStartRequest.java
new file mode 100644
index 000000000000..84814d5a8f10
--- /dev/null
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/ProgramStartRequest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright © 2025 Cask Data, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.cdap.cdap.internal.app.runtime;
+
+import io.cdap.cdap.app.program.ProgramDescriptor;
+import io.cdap.cdap.app.runtime.ProgramOptions;
+import io.cdap.cdap.common.app.RunIds;
+import io.cdap.cdap.proto.id.ProgramRunId;
+import org.apache.twill.api.RunId;
+
+/**
+ * Request object for starting a new Program run.
+ */
+public class ProgramStartRequest {
+
+ private final ProgramOptions programOptions;
+ private final ProgramDescriptor programDescriptor;
+ private final RunId runId;
+
+ public ProgramStartRequest(ProgramOptions programOptions,
+ ProgramDescriptor programDescriptor,
+ ProgramRunId programRunId) {
+ this.programOptions = programOptions;
+ this.programDescriptor = programDescriptor;
+ this.runId = RunIds.fromString(programRunId.getRun());
+ }
+
+ public ProgramOptions getProgramOptions() {
+ return programOptions;
+ }
+
+ public ProgramDescriptor getProgramDescriptor() {
+ return programDescriptor;
+ }
+
+ public RunId getRunId() {
+ return runId;
+ }
+}
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/service/InMemoryProgramRuntimeService.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/service/InMemoryProgramRuntimeService.java
index 38a1019ce455..eef7abc6eed7 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/service/InMemoryProgramRuntimeService.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/runtime/service/InMemoryProgramRuntimeService.java
@@ -47,9 +47,8 @@ public final class InMemoryProgramRuntimeService extends AbstractProgramRuntimeS
@Inject
InMemoryProgramRuntimeService(CConfiguration cConf, ProgramRunnerFactory programRunnerFactory,
- ProgramStateWriter programStateWriter,
- ProgramRunDispatcherFactory programRunDispatcherFactory) {
- super(cConf, programRunnerFactory, programStateWriter, programRunDispatcherFactory);
+ ProgramStateWriter programStateWriter, ProgramRunDispatcherFactory programRunDispatcherFactory) {
+ super(cConf, programRunnerFactory, programStateWriter, programRunDispatcherFactory );
}
@Override
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ProgramLifecycleService.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ProgramLifecycleService.java
index c8bdd8b0c19f..8ee093574b07 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ProgramLifecycleService.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ProgramLifecycleService.java
@@ -17,7 +17,6 @@
package io.cdap.cdap.internal.app.services;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -29,11 +28,8 @@
import io.cdap.cdap.api.plugin.Plugin;
import io.cdap.cdap.app.guice.ClusterMode;
import io.cdap.cdap.app.program.ProgramDescriptor;
-import io.cdap.cdap.app.runtime.LogLevelUpdater;
-import io.cdap.cdap.app.runtime.ProgramController;
import io.cdap.cdap.app.runtime.ProgramOptions;
import io.cdap.cdap.app.runtime.ProgramRuntimeService;
-import io.cdap.cdap.app.runtime.ProgramRuntimeService.RuntimeInfo;
import io.cdap.cdap.app.runtime.ProgramStateWriter;
import io.cdap.cdap.app.store.ScanApplicationsRequest;
import io.cdap.cdap.app.store.Store;
@@ -54,6 +50,7 @@
import io.cdap.cdap.internal.app.ApplicationSpecificationAdapter;
import io.cdap.cdap.internal.app.runtime.BasicArguments;
import io.cdap.cdap.internal.app.runtime.ProgramOptionConstants;
+import io.cdap.cdap.internal.app.runtime.ProgramStartRequest;
import io.cdap.cdap.internal.app.runtime.SimpleProgramOptions;
import io.cdap.cdap.internal.app.runtime.SystemArguments;
import io.cdap.cdap.internal.app.runtime.artifact.ArtifactRepository;
@@ -67,7 +64,6 @@
import io.cdap.cdap.internal.provision.ProvisioningService;
import io.cdap.cdap.proto.ProgramHistory;
import io.cdap.cdap.proto.ProgramRecord;
-import io.cdap.cdap.proto.ProgramRunClusterStatus;
import io.cdap.cdap.proto.ProgramRunStatus;
import io.cdap.cdap.proto.ProgramStatus;
import io.cdap.cdap.proto.ProgramType;
@@ -112,7 +108,6 @@
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.twill.api.RunId;
-import org.apache.twill.api.logging.LogEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -134,27 +129,27 @@ public class ProgramLifecycleService {
ProgramRunStatus.SUSPENDED);
private final Store store;
- private final ProfileService profileService;
- private final ProgramRuntimeService runtimeService;
- private final PropertiesResolver propertiesResolver;
- private final PreferencesService preferencesService;
+ private final ProgramStateWriter programStateWriter;
private final AccessEnforcer accessEnforcer;
private final AuthenticationContext authenticationContext;
+ private final PropertiesResolver propertiesResolver;
+ private final CapabilityReader capabilityReader;
+ private final ArtifactRepository artifactRepository;
+ private final ProfileService profileService;
+ private final PreferencesService preferencesService;
private final ProvisionerNotifier provisionerNotifier;
private final ProvisioningService provisioningService;
- private final ProgramStateWriter programStateWriter;
- private final CapabilityReader capabilityReader;
+ private final FlowControlService flowControlService;
private final int maxConcurrentRuns;
private final int maxConcurrentLaunching;
private final int defaultStopTimeoutSecs;
private final int batchSize;
- private final ArtifactRepository artifactRepository;
- private final FlowControlService flowControlService;
+
private final boolean userProgramLaunchDisabled;
@Inject
ProgramLifecycleService(CConfiguration cConf,
- Store store, ProfileService profileService, ProgramRuntimeService runtimeService,
+ Store store, ProfileService profileService,
PropertiesResolver propertiesResolver,
PreferencesService preferencesService, AccessEnforcer accessEnforcer,
AuthenticationContext authenticationContext,
@@ -162,45 +157,26 @@ public class ProgramLifecycleService {
ProgramStateWriter programStateWriter, CapabilityReader capabilityReader,
ArtifactRepository artifactRepository,
FlowControlService flowControlService) {
+ this.store = store;
+ this.programStateWriter = programStateWriter;
+ this.accessEnforcer = accessEnforcer;
+ this.authenticationContext = authenticationContext;
+ this.propertiesResolver = propertiesResolver;
+ this.capabilityReader = capabilityReader;
+ this.artifactRepository = artifactRepository;
this.maxConcurrentRuns = cConf.getInt(Constants.AppFabric.MAX_CONCURRENT_RUNS);
this.maxConcurrentLaunching = cConf.getInt(Constants.AppFabric.MAX_CONCURRENT_LAUNCHING);
this.defaultStopTimeoutSecs = cConf.getInt(Constants.AppFabric.PROGRAM_MAX_STOP_SECONDS);
this.userProgramLaunchDisabled = cConf.getBoolean(
Constants.AppFabric.USER_PROGRAM_LAUNCH_DISABLED, false);
this.batchSize = cConf.getInt(Constants.AppFabric.STREAMING_BATCH_SIZE);
- this.store = store;
this.profileService = profileService;
- this.runtimeService = runtimeService;
- this.propertiesResolver = propertiesResolver;
this.preferencesService = preferencesService;
- this.accessEnforcer = accessEnforcer;
- this.authenticationContext = authenticationContext;
this.provisionerNotifier = provisionerNotifier;
this.provisioningService = provisioningService;
- this.programStateWriter = programStateWriter;
- this.capabilityReader = capabilityReader;
- this.artifactRepository = artifactRepository;
this.flowControlService = flowControlService;
}
- /**
- * Returns the program status.
- *
- * @param programId the id of the program for which the status call is made
- * @return the status of the program
- * @throws NotFoundException if the application to which this program belongs was not found
- */
- public ProgramStatus getProgramStatus(ProgramId programId) throws Exception {
- // check that app exists
- ApplicationId appId = programId.getParent();
- ApplicationSpecification appSpec = store.getApplication(appId);
- if (appSpec == null) {
- throw new NotFoundException(appId);
- }
-
- return getExistingAppProgramStatus(appSpec, programId);
- }
-
/**
* Returns the status of the latest version program.
*
@@ -214,26 +190,21 @@ public ProgramStatus getProgramStatus(ProgramReference programReference) throws
}
/**
- * Returns the program status based on the active run records of a program. A program is RUNNING
- * if there are any RUNNING, STOPPING, or SUSPENDED run records. A program is starting if there
- * are any PENDING or STARTING run records and no RUNNING run records. Otherwise, it is STOPPED.
+ * Returns the program status.
*
- * @param runRecords run records for the program
- * @return the program status
+ * @param programId the id of the program for which the status call is made
+ * @return the status of the program
+ * @throws NotFoundException if the application to which this program belongs was not found
*/
- @VisibleForTesting
- static ProgramStatus getProgramStatus(Collection runRecords) {
- boolean hasStarting = false;
- for (RunRecordDetail runRecord : runRecords) {
- ProgramRunStatus runStatus = runRecord.getStatus();
- if (runStatus == ProgramRunStatus.RUNNING || runStatus == ProgramRunStatus.SUSPENDED
- || runStatus == ProgramRunStatus.STOPPING) {
- return ProgramStatus.RUNNING;
- }
- hasStarting = hasStarting || runStatus == ProgramRunStatus.STARTING
- || runStatus == ProgramRunStatus.PENDING;
+ public ProgramStatus getProgramStatus(ProgramId programId) throws Exception {
+ // check that app exists
+ ApplicationId appId = programId.getParent();
+ ApplicationSpecification appSpec = store.getApplication(appId);
+ if (appSpec == null) {
+ throw new NotFoundException(appId);
}
- return hasStarting ? ProgramStatus.STARTING : ProgramStatus.STOPPED;
+
+ return getExistingAppProgramStatus(appSpec, programId);
}
/**
@@ -243,8 +214,7 @@ static ProgramStatus getProgramStatus(Collection runRecords) {
* @return a {@link Map} from the {@link ProgramId} to the corresponding status; there will be no
* entry for programs that do not exist.
*/
- public Map getProgramStatuses(Collection programRefs)
- throws Exception {
+ public Map getProgramStatuses(Collection programRefs) {
// filter the result
Set extends EntityId> visibleEntities = accessEnforcer.isVisible(
new LinkedHashSet<>(programRefs),
@@ -506,27 +476,6 @@ private void addProgramHistory(List histories, List overrides, boolean deb
}
authorizePipelineRuntimeImpersonation(userArgs);
-
return runInternal(programId, userArgs, sysArgs, debug);
}
@@ -728,7 +645,6 @@ public RunId runInternal(ProgramId programId, Map userArgs,
userId = userId == null ? "" : userId;
checkCapability(programDescriptor);
-
ProgramRunId programRunId = programId.run(runId);
FlowControlService.Counter counter = flowControlService.addRequestAndGetCounter(
programRunId, programOptions, programDescriptor);
@@ -822,7 +738,11 @@ ProgramOptions createProgramOptions(ProgramId programId, Map use
/**
* Starts a Program with the specified argument overrides, skipping cluster lifecycle steps in the
- * run. NOTE: This method should only be called from preview runner.
+ * run.
+ *
+ * NOTE: {@Link ProgramRuntimeService#run} needs be called to start the program run.
+ *
+ * NOTE: This method should only be called from preview runner.
*
* @param programId the {@link ProgramId} to start/stop
* @param overrides the arguments to override in the program's configured user arguments
@@ -831,7 +751,7 @@ ProgramOptions createProgramOptions(ProgramId programId, Map use
* otherwise
* @param isPreview true if the program is for preview run, for preview run, the app is
* already deployed with resolved properties, so no need to regenerate app spec again
- * @return {@link ProgramController}
+ *
* @throws ConflictException if the specified program is already running, and if concurrent
* runs are not allowed
* @throws NotFoundException if the specified program or the app it belongs to is not found in
@@ -842,7 +762,7 @@ ProgramOptions createProgramOptions(ProgramId programId, Map use
* @throws Exception if there were other exceptions checking if the current user is authorized
* to start the program
*/
- public ProgramController start(ProgramId programId, Map overrides, boolean debug,
+ public ProgramStartRequest prepareStart(ProgramId programId, Map overrides, boolean debug,
boolean isPreview) throws Exception {
accessEnforcer.enforce(programId, authenticationContext.getPrincipal(),
ApplicationPermission.EXECUTE);
@@ -859,62 +779,16 @@ public ProgramController start(ProgramId programId, Map override
}
authorizePipelineRuntimeImpersonation(userArgs);
-
BasicArguments systemArguments = new BasicArguments(sysArgs);
BasicArguments userArguments = new BasicArguments(userArgs);
- ProgramOptions options = new SimpleProgramOptions(programId, systemArguments, userArguments,
- debug);
- ProgramDescriptor programDescriptor = store.loadProgram(programId);
- ProgramRunId programRunId = programId.run(RunIds.generate());
+ ProgramOptions programOptions = new SimpleProgramOptions(programId, systemArguments, userArguments, debug);
+ ProgramDescriptor programDescriptor = store.loadProgram(programId);
checkCapability(programDescriptor);
+ ProgramRunId programRunId = programId.run(RunIds.generate());
- programStateWriter.start(programRunId, options, null, programDescriptor);
- return startInternal(programDescriptor, options, programRunId);
- }
-
- private void checkCapability(ProgramDescriptor programDescriptor) throws Exception {
- //check for capability at application class level
- Set applicationClasses = artifactRepository
- .getArtifact(Id.Artifact.fromEntityId(programDescriptor.getArtifactId())).getMeta()
- .getClasses()
- .getApps();
- for (ApplicationClass applicationClass : applicationClasses) {
- Set capabilities = applicationClass.getRequirements().getCapabilities();
- capabilityReader.checkAllEnabled(capabilities);
- }
- for (Map.Entry pluginEntry : programDescriptor.getApplicationSpecification()
- .getPlugins()
- .entrySet()) {
- Set capabilities = pluginEntry.getValue().getPluginClass().getRequirements()
- .getCapabilities();
- capabilityReader.checkAllEnabled(capabilities);
- }
- }
-
- /**
- * Starts a Program run with the given arguments. This method skips cluster lifecycle steps and
- * does not perform authorization checks. If the program is already started, returns the
- * controller for the program. NOTE: This method should only be used from this service and the
- * {@link ProgramNotificationSubscriberService} upon receiving a {@link
- * ProgramRunClusterStatus#PROVISIONED} state.
- *
- * @param programDescriptor descriptor of the program to run
- * @param programOptions options for the program run
- * @param programRunId program run id
- * @return controller for the program
- */
- ProgramController startInternal(ProgramDescriptor programDescriptor,
- ProgramOptions programOptions, ProgramRunId programRunId) {
- RunId runId = RunIds.fromString(programRunId.getRun());
-
- synchronized (this) {
- RuntimeInfo runtimeInfo = runtimeService.lookup(programRunId.getParent(), runId);
- if (runtimeInfo != null) {
- return runtimeInfo.getController();
- }
- return runtimeService.run(programDescriptor, programOptions, runId).getController();
- }
+ programStateWriter.start(programRunId, programOptions, null, programDescriptor);
+ return new ProgramStartRequest(programOptions, programDescriptor, programRunId);
}
/**
@@ -1137,63 +1011,6 @@ public Map getRuntimeArgs(@Name("programId") ProgramId programId
return preferencesService.getProperties(programId);
}
- /**
- * Update log levels for the given program. Only supported program types for this action are
- * {@link ProgramType#SERVICE} and {@link ProgramType#WORKER}.
- *
- * @param programId the {@link ProgramId} of the program for which log levels are to be
- * updated
- * @param logLevels the {@link Map} of the log levels to be updated.
- * @param runId the run id of the program.
- * @throws InterruptedException if there is an error while asynchronously updating log
- * levels.
- * @throws ExecutionException if there is an error while asynchronously updating log levels.
- * @throws BadRequestException if the log level is not valid or the program type is not
- * supported.
- * @throws UnauthorizedException if the user does not have privileges to update log levels for
- * the specified program. To update log levels for a program, a user needs {@link
- * StandardPermission#UPDATE} on the program.
- */
- public void updateProgramLogLevels(ProgramId programId, Map logLevels,
- @Nullable String runId) throws Exception {
- accessEnforcer.enforce(programId, authenticationContext.getPrincipal(),
- StandardPermission.UPDATE);
- if (!EnumSet.of(ProgramType.SERVICE, ProgramType.WORKER).contains(programId.getType())) {
- throw new BadRequestException(
- String.format("Updating log levels for program type %s is not supported",
- programId.getType().getPrettyName()));
- }
- updateLogLevels(programId, logLevels, runId);
- }
-
- /**
- * Reset log levels for the given program. Only supported program types for this action are {@link
- * ProgramType#SERVICE} and {@link ProgramType#WORKER}.
- *
- * @param programId the {@link ProgramId} of the program for which log levels are to be
- * reset.
- * @param loggerNames the {@link String} set of the logger names to be updated, empty means
- * reset for all loggers.
- * @param runId the run id of the program.
- * @throws InterruptedException if there is an error while asynchronously resetting log
- * levels.
- * @throws ExecutionException if there is an error while asynchronously resetting log levels.
- * @throws UnauthorizedException if the user does not have privileges to reset log levels for
- * the specified program. To reset log levels for a program, a user needs {@link
- * StandardPermission#UPDATE} on the program.
- */
- public void resetProgramLogLevels(ProgramId programId, Set loggerNames,
- @Nullable String runId) throws Exception {
- accessEnforcer.enforce(programId, authenticationContext.getPrincipal(),
- StandardPermission.UPDATE);
- if (!EnumSet.of(ProgramType.SERVICE, ProgramType.WORKER).contains(programId.getType())) {
- throw new BadRequestException(
- String.format("Resetting log levels for program type %s is not supported",
- programId.getType().getPrettyName()));
- }
- resetLogLevels(programId, loggerNames, runId);
- }
-
/**
* Ensures the caller is authorized to check if the given program exists.
*/
@@ -1225,10 +1042,6 @@ public void ensureLatestProgramExists(ProgramReference programRef) throws Except
Store.ensureProgramExists(programRef.id(appSpec.getAppVersion()), appSpec);
}
- private boolean isStopped(ProgramId programId) throws Exception {
- return ProgramStatus.STOPPED == getProgramStatus(programId);
- }
-
/**
* Checks if the given program is running and is allowed for concurrent execution.
*
@@ -1237,9 +1050,9 @@ private boolean isStopped(ProgramId programId) throws Exception {
* @throws NotFoundException if the program is not found
* @throws Exception if failed to determine the state
*/
- private synchronized void checkConcurrentExecution(ProgramId programId) throws Exception {
+ public void checkConcurrentExecution(ProgramId programId) throws Exception {
+ Map runs = store.getActiveRuns(programId);
if (isConcurrentRunsInSameAppForbidden(programId.getType())) {
- Map runs = runtimeService.list(programId);
if (!runs.isEmpty() || !isStoppedInSameProgram(programId)) {
throw new ConflictException(
String.format(
@@ -1248,13 +1061,8 @@ private synchronized void checkConcurrentExecution(ProgramId programId) throws E
}
}
if (!isConcurrentRunsAllowed(programId.getType())) {
- List runIds = new ArrayList<>();
- for (Map.Entry entry : runtimeService.list(programId.getType())
- .entrySet()) {
- if (programId.equals(entry.getValue().getProgramId())) {
- runIds.add(entry.getKey());
- }
- }
+ List runIds = runs.keySet().stream().map(r -> programId.run(r.getRun()))
+ .collect(Collectors.toList());
if (!runIds.isEmpty() || !isStopped(programId)) {
throw new ConflictException(
String.format("Program %s is already running with run ids %s", programId, runIds));
@@ -1262,101 +1070,6 @@ private synchronized void checkConcurrentExecution(ProgramId programId) throws E
}
}
- /**
- * Returns whether the given program is stopped in all versions of the app.
- *
- * @param programId the id of the program for which the stopped status in all versions of the
- * app is found
- * @return whether the given program is stopped in all versions of the app
- * @throws NotFoundException if the application to which this program belongs was not found
- */
- private boolean isStoppedInSameProgram(ProgramId programId) throws Exception {
- // check that app exists
- Collection appIds = store.getAllAppVersionsAppIds(
- programId.getParent().getAppReference());
- if (appIds == null || appIds.isEmpty()) {
- throw new NotFoundException(
- Id.Application.from(programId.getNamespace(), programId.getApplication()));
- }
- ApplicationSpecification appSpec = store.getApplication(programId.getParent());
- for (ApplicationId appId : appIds) {
- ProgramId pId = appId.program(programId.getType(), programId.getProgram());
- if (!getExistingAppProgramStatus(appSpec, pId).equals(ProgramStatus.STOPPED)) {
- return false;
- }
- }
- return true;
- }
-
- private boolean isConcurrentRunsInSameAppForbidden(ProgramType type) {
- // Concurrent runs in different (or same) versions of an application are forbidden for worker
- return EnumSet.of(ProgramType.WORKER).contains(type);
- }
-
- private boolean isConcurrentRunsAllowed(ProgramType type) {
- // Concurrent runs are only allowed for the Workflow, MapReduce and Spark
- return EnumSet.of(ProgramType.WORKFLOW, ProgramType.MAPREDUCE, ProgramType.SPARK)
- .contains(type);
- }
-
- private Map findRuntimeInfo(
- ProgramId programId, @Nullable String runId) throws BadRequestException {
-
- if (runId != null) {
- RunId run;
- try {
- run = RunIds.fromString(runId);
- } catch (IllegalArgumentException e) {
- throw new BadRequestException("Error parsing run-id.", e);
- }
- ProgramRuntimeService.RuntimeInfo runtimeInfo = runtimeService.lookup(programId, run);
- return runtimeInfo == null ? Collections.emptyMap()
- : Collections.singletonMap(run, runtimeInfo);
- }
- return new HashMap<>(runtimeService.list(programId));
- }
-
- @Nullable
- private ProgramRuntimeService.RuntimeInfo findRuntimeInfo(ProgramId programId)
- throws BadRequestException {
- return findRuntimeInfo(programId, null).values().stream().findFirst().orElse(null);
- }
-
- /**
- * Set instances for the given program. Only supported program types for this action are {@link
- * ProgramType#SERVICE} and {@link ProgramType#WORKER}.
- *
- * @param programId the {@link ProgramId} of the program for which instances are to be
- * updated
- * @param instances the number of instances to be updated.
- * @throws InterruptedException if there is an error while asynchronously updating instances
- * @throws ExecutionException if there is an error while asynchronously updating instances
- * @throws BadRequestException if the number of instances specified is less than 0
- * @throws UnauthorizedException if the user does not have privileges to set instances for the
- * specified program. To set instances for a program, a user needs {@link
- * StandardPermission#UPDATE} on the program.
- */
- public void setInstances(ProgramId programId, int instances) throws Exception {
- accessEnforcer.enforce(programId, authenticationContext.getPrincipal(),
- StandardPermission.UPDATE);
- if (instances < 1) {
- throw new BadRequestException(
- String.format("Instance count should be greater than 0. Got %s.", instances));
- }
- switch (programId.getType()) {
- case SERVICE:
- setServiceInstances(programId, instances);
- break;
- case WORKER:
- setWorkerInstances(programId, instances);
- break;
- default:
- throw new BadRequestException(
- String.format("Setting instances for program type %s is not supported",
- programId.getType().getPrettyName()));
- }
- }
-
/**
* Lists all programs with the specified program type in a namespace. If perimeter security and
* authorization are enabled, only returns the programs that the current user has access to.
@@ -1416,76 +1129,6 @@ private boolean hasAccess(ProgramId programId) {
return !accessEnforcer.isVisible(Collections.singleton(programId), principal).isEmpty();
}
- private void setWorkerInstances(ProgramId programId, int instances)
- throws ExecutionException, InterruptedException, BadRequestException {
- int oldInstances = store.getWorkerInstances(programId);
- if (oldInstances != instances) {
- store.setWorkerInstances(programId, instances);
- ProgramRuntimeService.RuntimeInfo runtimeInfo = findRuntimeInfo(programId);
- if (runtimeInfo != null) {
- runtimeInfo.getController().command(ProgramOptionConstants.INSTANCES,
- ImmutableMap.of("runnable", programId.getProgram(),
- "newInstances", String.valueOf(instances),
- "oldInstances", String.valueOf(oldInstances))).get();
- }
- }
- }
-
- private void setServiceInstances(ProgramId programId, int instances)
- throws ExecutionException, InterruptedException, BadRequestException {
- int oldInstances = store.getServiceInstances(programId);
- if (oldInstances != instances) {
- store.setServiceInstances(programId, instances);
- ProgramRuntimeService.RuntimeInfo runtimeInfo = findRuntimeInfo(programId);
- if (runtimeInfo != null) {
- runtimeInfo.getController().command(ProgramOptionConstants.INSTANCES,
- ImmutableMap.of("runnable", programId.getProgram(),
- "newInstances", String.valueOf(instances),
- "oldInstances", String.valueOf(oldInstances))).get();
- }
- }
- }
-
- /**
- * Helper method to update log levels for Worker or Service.
- */
- private void updateLogLevels(ProgramId programId, Map logLevels,
- @Nullable String runId) throws Exception {
- ProgramRuntimeService.RuntimeInfo runtimeInfo = findRuntimeInfo(programId, runId).values()
- .stream()
- .findFirst().orElse(null);
- if (runtimeInfo != null) {
- LogLevelUpdater logLevelUpdater = getLogLevelUpdater(runtimeInfo);
- logLevelUpdater.updateLogLevels(logLevels, null);
- }
- }
-
- /**
- * Helper method to reset log levels for Worker or Service.
- */
- private void resetLogLevels(ProgramId programId, Set loggerNames, @Nullable String runId)
- throws Exception {
- ProgramRuntimeService.RuntimeInfo runtimeInfo = findRuntimeInfo(programId, runId).values()
- .stream()
- .findFirst().orElse(null);
- if (runtimeInfo != null) {
- LogLevelUpdater logLevelUpdater = getLogLevelUpdater(runtimeInfo);
- logLevelUpdater.resetLogLevels(loggerNames, null);
- }
- }
-
- /**
- * Helper method to get the {@link LogLevelUpdater} for the program.
- */
- private LogLevelUpdater getLogLevelUpdater(RuntimeInfo runtimeInfo) throws Exception {
- ProgramController programController = runtimeInfo.getController();
- if (!(programController instanceof LogLevelUpdater)) {
- throw new BadRequestException(
- "Update log levels at runtime is only supported in distributed mode");
- }
- return ((LogLevelUpdater) programController);
- }
-
/**
* Returns the active run records (PENDING / STARTING / RUNNING / SUSPENDED) based on the given
* program id and an optional run id.
@@ -1536,22 +1179,6 @@ private ProgramSpecification getLatestProgramSpecificationWithoutAuthz(
return getExistingAppProgramSpecification(appMeta.getSpec(), programReference);
}
- /**
- * Adds {@link Constants#APP_CDAP_VERSION} system argument to the argument map if known.
- *
- * @param programId program that corresponds to application with version information
- * @param systemArgs map to add version information to
- */
- public void addAppCdapVersion(ProgramId programId, Map systemArgs) {
- ApplicationSpecification appSpec = store.getApplication(programId.getParent());
- if (appSpec != null) {
- String appCDAPVersion = appSpec.getAppCDAPVersion();
- if (appCDAPVersion != null) {
- systemArgs.put(Constants.APP_CDAP_VERSION, appCDAPVersion);
- }
- }
- }
-
private Set getPluginRequirements(ProgramSpecification programSpecification) {
return programSpecification.getPlugins().values()
.stream().map(plugin -> new PluginRequirement(plugin.getPluginClass().getName(),
@@ -1560,19 +1187,6 @@ private Set getPluginRequirements(ProgramSpecification progra
.collect(Collectors.toSet());
}
- private void authorizePipelineRuntimeImpersonation(Map userArgs)
- throws Exception {
- if ((userArgs.containsKey(SystemArguments.RUNTIME_PRINCIPAL_NAME))
- && (userArgs.containsKey(SystemArguments.RUNTIME_KEYTAB_PATH))) {
- String principal = userArgs.get(SystemArguments.RUNTIME_PRINCIPAL_NAME);
- LOG.debug("Checking authorisation for user: {}, using runtime config principal: {}",
- authenticationContext.getPrincipal(), principal);
- KerberosPrincipalId kid = new KerberosPrincipalId(principal);
- accessEnforcer.enforce(kid, authenticationContext.getPrincipal(),
- AccessPermission.IMPERSONATE);
- }
- }
-
private String decodeUserId(@Nullable String encodedUserId) {
if (encodedUserId == null) {
return "";
@@ -1612,4 +1226,168 @@ private ProgramId getLatestProgramId(ProgramReference programReference)
ApplicationId applicationId = getLatestApplicationId(programReference.getParent());
return applicationId.program(programReference.getType(), programReference.getProgram());
}
+
+ public void checkCapability(ProgramDescriptor programDescriptor) throws Exception {
+ // Check for capability at application class level.
+ Set applicationClasses = artifactRepository
+ .getArtifact(Id.Artifact.fromEntityId(programDescriptor.getArtifactId())).getMeta()
+ .getClasses()
+ .getApps();
+ for (ApplicationClass applicationClass : applicationClasses) {
+ Set capabilities = applicationClass.getRequirements().getCapabilities();
+ capabilityReader.checkAllEnabled(capabilities);
+ }
+ for (Map.Entry pluginEntry : programDescriptor.getApplicationSpecification()
+ .getPlugins()
+ .entrySet()) {
+ Set capabilities = pluginEntry.getValue().getPluginClass().getRequirements()
+ .getCapabilities();
+ capabilityReader.checkAllEnabled(capabilities);
+ }
+ }
+
+ /**
+ * Adds {@link Constants#APP_CDAP_VERSION} system argument to the argument map if known.
+ *
+ * @param programId program that corresponds to application with version information
+ * @param systemArgs map to add version information to
+ */
+ private void addAppCdapVersion(ProgramId programId, Map systemArgs) {
+ ApplicationSpecification appSpec = store.getApplication(programId.getParent());
+ if (appSpec != null) {
+ String appCDAPVersion = appSpec.getAppCDAPVersion();
+ if (appCDAPVersion != null) {
+ systemArgs.put(Constants.APP_CDAP_VERSION, appCDAPVersion);
+ }
+ }
+ }
+
+ private void authorizePipelineRuntimeImpersonation(Map userArgs) {
+ if ((userArgs.containsKey(SystemArguments.RUNTIME_PRINCIPAL_NAME))
+ && (userArgs.containsKey(SystemArguments.RUNTIME_KEYTAB_PATH))) {
+ String principal = userArgs.get(SystemArguments.RUNTIME_PRINCIPAL_NAME);
+ LOG.debug("Checking authorisation for user: {}, using runtime config principal: {}",
+ authenticationContext.getPrincipal(), principal);
+ KerberosPrincipalId kid = new KerberosPrincipalId(principal);
+ accessEnforcer.enforce(kid, authenticationContext.getPrincipal(),
+ AccessPermission.IMPERSONATE);
+ }
+ }
+
+ private static boolean isConcurrentRunsInSameAppForbidden(ProgramType type) {
+ // Concurrent runs in different (or same) versions of an application are forbidden for worker
+ return EnumSet.of(ProgramType.WORKER).contains(type);
+ }
+
+ private static boolean isConcurrentRunsAllowed(ProgramType type) {
+ // Concurrent runs are only allowed for the Workflow, MapReduce and Spark
+ return EnumSet.of(ProgramType.WORKFLOW, ProgramType.MAPREDUCE, ProgramType.SPARK)
+ .contains(type);
+ }
+
+ private boolean isStopped(ProgramId programId) throws Exception {
+ return ProgramStatus.STOPPED == getProgramStatus(programId);
+ }
+
+ /**
+ * Returns whether the given program is stopped in all versions of the app.
+ *
+ * @param programId the id of the program for which the stopped status in all versions of the
+ * app is found
+ * @return whether the given program is stopped in all versions of the app
+ * @throws NotFoundException if the application to which this program belongs was not found
+ */
+ private boolean isStoppedInSameProgram(ProgramId programId) throws Exception {
+ // check that app exists
+ Collection appIds = store.getAllAppVersionsAppIds(
+ programId.getParent().getAppReference());
+ if (appIds == null || appIds.isEmpty()) {
+ throw new NotFoundException(
+ Id.Application.from(programId.getNamespace(), programId.getApplication()));
+ }
+ ApplicationSpecification appSpec = store.getApplication(programId.getParent());
+ for (ApplicationId appId : appIds) {
+ ProgramId pId = appId.program(programId.getType(), programId.getProgram());
+ if (!getExistingAppProgramStatus(appSpec, pId).equals(ProgramStatus.STOPPED)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the program status with no need of application existence check.
+ *
+ * @param appSpec the ApplicationSpecification of the existing application
+ * @param programId the id of the program for which the status call is made
+ * @return the status of the program
+ * @throws NotFoundException if the application to which this program belongs was not found
+ */
+ private ProgramStatus getExistingAppProgramStatus(ApplicationSpecification appSpec,
+ ProgramId programId) throws Exception {
+ // TODO(CDAP-21126): Review access enforcement in this auxiliary function.
+ accessEnforcer.enforce(programId, authenticationContext.getPrincipal(), StandardPermission.GET);
+ ProgramSpecification spec = getExistingAppProgramSpecification(appSpec,
+ programId.getProgramReference());
+ if (spec == null) {
+ // program doesn't exist
+ throw new NotFoundException(programId);
+ }
+
+ return getProgramStatus(store.getActiveRuns(programId).values());
+ }
+
+ /**
+ * Returns the program status based on the active run records of a program. A program is RUNNING
+ * if there are any RUNNING, STOPPING, or SUSPENDED run records. A program is starting if there
+ * are any PENDING or STARTING run records and no RUNNING run records. Otherwise, it is STOPPED.
+ *
+ * @param runRecords run records for the program
+ * @return the program status
+ */
+ @VisibleForTesting
+ static ProgramStatus getProgramStatus(Collection runRecords) {
+ boolean hasStarting = false;
+ for (RunRecordDetail runRecord : runRecords) {
+ ProgramRunStatus runStatus = runRecord.getStatus();
+ if (runStatus == ProgramRunStatus.RUNNING || runStatus == ProgramRunStatus.SUSPENDED
+ || runStatus == ProgramRunStatus.STOPPING) {
+ return ProgramStatus.RUNNING;
+ }
+ hasStarting = hasStarting || runStatus == ProgramRunStatus.STARTING
+ || runStatus == ProgramRunStatus.PENDING;
+ }
+ return hasStarting ? ProgramStatus.STARTING : ProgramStatus.STOPPED;
+ }
+
+ /**
+ * Returns the {@link ProgramSpecification} for the specified {@link ProgramId program}.
+ *
+ * @param appSpec the {@link ApplicationSpecification} of the existing application
+ * @param programReference the {@link ProgramReference program} for which the {@link
+ * ProgramSpecification} is requested
+ * @return the {@link ProgramSpecification} for the specified {@link ProgramId program}, or {@code
+ * null} if it does not exist
+ */
+ @Nullable
+ private ProgramSpecification getExistingAppProgramSpecification(ApplicationSpecification appSpec,
+ ProgramReference programReference) {
+ String programName = programReference.getProgram();
+ ProgramType type = programReference.getType();
+ ProgramSpecification programSpec;
+ if (type == ProgramType.MAPREDUCE && appSpec.getMapReduce().containsKey(programName)) {
+ programSpec = appSpec.getMapReduce().get(programName);
+ } else if (type == ProgramType.SPARK && appSpec.getSpark().containsKey(programName)) {
+ programSpec = appSpec.getSpark().get(programName);
+ } else if (type == ProgramType.WORKFLOW && appSpec.getWorkflows().containsKey(programName)) {
+ programSpec = appSpec.getWorkflows().get(programName);
+ } else if (type == ProgramType.SERVICE && appSpec.getServices().containsKey(programName)) {
+ programSpec = appSpec.getServices().get(programName);
+ } else if (type == ProgramType.WORKER && appSpec.getWorkers().containsKey(programName)) {
+ programSpec = appSpec.getWorkers().get(programName);
+ } else {
+ programSpec = null;
+ }
+ return programSpec;
+ }
}
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ProgramNotificationSubscriberService.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ProgramNotificationSubscriberService.java
index a077d6d0cfc9..4b5b377fa234 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ProgramNotificationSubscriberService.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/ProgramNotificationSubscriberService.java
@@ -33,6 +33,7 @@
import io.cdap.cdap.api.workflow.WorkflowSpecification;
import io.cdap.cdap.app.program.ProgramDescriptor;
import io.cdap.cdap.app.runtime.ProgramOptions;
+import io.cdap.cdap.app.runtime.ProgramRuntimeService;
import io.cdap.cdap.app.runtime.ProgramStateWriter;
import io.cdap.cdap.app.store.Store;
import io.cdap.cdap.common.app.RunIds;
@@ -91,6 +92,7 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
+import org.apache.twill.api.RunId;
import org.apache.twill.internal.CompositeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -108,6 +110,7 @@ public class ProgramNotificationSubscriberService extends AbstractIdleService {
private final MetricsCollectionService metricsCollectionService;
private final ProvisionerNotifier provisionerNotifier;
private final ProgramLifecycleService programLifecycleService;
+ private final ProgramRuntimeService runtimeService;
private final ProvisioningService provisioningService;
private final ProgramStateWriter programStateWriter;
private final TransactionRunner transactionRunner;
@@ -123,6 +126,7 @@ public class ProgramNotificationSubscriberService extends AbstractIdleService {
MetricsCollectionService metricsCollectionService,
ProvisionerNotifier provisionerNotifier,
ProgramLifecycleService programLifecycleService,
+ ProgramRuntimeService runtimeService,
ProvisioningService provisioningService,
ProgramStateWriter programStateWriter,
TransactionRunner transactionRunner,
@@ -134,6 +138,7 @@ public class ProgramNotificationSubscriberService extends AbstractIdleService {
this.metricsCollectionService = metricsCollectionService;
this.provisionerNotifier = provisionerNotifier;
this.programLifecycleService = programLifecycleService;
+ this.runtimeService = runtimeService;
this.provisioningService = provisioningService;
this.programStateWriter = programStateWriter;
this.transactionRunner = transactionRunner;
@@ -229,6 +234,7 @@ private ProgramNotificationSingleTopicSubscriberService createChildService(
metricsCollectionService,
provisionerNotifier,
programLifecycleService,
+ runtimeService,
provisioningService,
programStateWriter,
transactionRunner,
@@ -268,6 +274,7 @@ class ProgramNotificationSingleTopicSubscriberService
private final String recordedProgramStatusPublishTopic;
private final ProvisionerNotifier provisionerNotifier;
private final ProgramLifecycleService programLifecycleService;
+ private final ProgramRuntimeService runtimeService;
private final ProvisioningService provisioningService;
private final ProgramStateWriter programStateWriter;
private final Queue tasks;
@@ -282,6 +289,7 @@ class ProgramNotificationSingleTopicSubscriberService
MetricsCollectionService metricsCollectionService,
ProvisionerNotifier provisionerNotifier,
ProgramLifecycleService programLifecycleService,
+ ProgramRuntimeService runtimeService,
ProvisioningService provisioningService,
ProgramStateWriter programStateWriter,
TransactionRunner transactionRunner,
@@ -303,6 +311,7 @@ class ProgramNotificationSingleTopicSubscriberService
cConf.get(Constants.AppFabric.PROGRAM_STATUS_RECORD_EVENT_TOPIC);
this.provisionerNotifier = provisionerNotifier;
this.programLifecycleService = programLifecycleService;
+ this.runtimeService = runtimeService;
this.provisioningService = provisioningService;
this.programStateWriter = programStateWriter;
this.tasks = new LinkedList<>();
@@ -543,7 +552,8 @@ private void handleProgramEvent(
SecurityRequestContext.setUserId(
prgOptions.getArguments().getOption(ProgramOptionConstants.USER_ID));
try {
- programLifecycleService.startInternal(prgDescriptor, prgOptions, programRunId);
+ RunId runId = RunIds.fromString(programRunId.getRun());
+ runtimeService.run(prgDescriptor, prgOptions, runId);
} catch (Exception e) {
LOG.error("Failed to start program {}", programRunId, e);
programStateWriter.error(programRunId, e);
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/SystemProgramManagementService.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/SystemProgramManagementService.java
index 4757c066b420..da79722933e6 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/SystemProgramManagementService.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/services/SystemProgramManagementService.java
@@ -25,6 +25,7 @@
import io.cdap.cdap.common.conf.Constants;
import io.cdap.cdap.common.service.AbstractRetryableScheduledService;
import io.cdap.cdap.common.service.RetryStrategies;
+import io.cdap.cdap.internal.app.runtime.ProgramStartRequest;
import io.cdap.cdap.proto.ProgramType;
import io.cdap.cdap.proto.id.NamespaceId;
import io.cdap.cdap.proto.id.ProgramId;
@@ -121,7 +122,10 @@ private void startPrograms(Map enabledProgramsMap) {
Map overrides = enabledProgramsMap.get(programId).asMap();
LOG.debug("Starting program {} with args {}", programId, overrides);
try {
- programLifecycleService.start(programId, overrides, false, false);
+ ProgramStartRequest startRequest = programLifecycleService.prepareStart(programId, overrides, false, false);
+ programRuntimeService.run(
+ startRequest.getProgramDescriptor(), startRequest.getProgramOptions(), startRequest.getRunId())
+ .getController();
} catch (ConflictException ex) {
// Ignore if the program is already running.
LOG.debug("Program {} is already running.", programId);
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/store/DefaultStore.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/store/DefaultStore.java
index e0b59e4a2faa..f790165316fd 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/store/DefaultStore.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/store/DefaultStore.java
@@ -39,6 +39,7 @@
import io.cdap.cdap.app.store.ScanApplicationsRequest;
import io.cdap.cdap.app.store.Store;
import io.cdap.cdap.common.ApplicationNotFoundException;
+import io.cdap.cdap.common.BadRequestException;
import io.cdap.cdap.common.ConflictException;
import io.cdap.cdap.common.NotFoundException;
import io.cdap.cdap.common.ProgramNotFoundException;
@@ -939,6 +940,18 @@ public ApplicationMeta getLatest(ApplicationReference appRef) {
});
}
+ @Override
+ public ApplicationId getLatestApp(ApplicationReference appRef) throws ApplicationNotFoundException {
+ ApplicationMeta appMeta = getLatest(appRef);
+ if (appMeta == null) {
+ throw new ApplicationNotFoundException(appRef);
+ }
+
+ return new ApplicationId(appRef.getNamespace(),
+ appRef.getApplication(),
+ appMeta.getSpec().getAppVersion());
+ }
+
@Override
public Collection getAllAppVersionsAppIds(ApplicationReference appRef) {
return TransactionRunners.run(transactionRunner, context -> {
diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/capability/CapabilityManagementService.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/capability/CapabilityManagementService.java
index c8d5645345ad..f4df5e36e2f4 100644
--- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/capability/CapabilityManagementService.java
+++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/capability/CapabilityManagementService.java
@@ -24,7 +24,6 @@
import io.cdap.cdap.common.service.AbstractRetryableScheduledService;
import io.cdap.cdap.common.service.RetryStrategies;
import io.cdap.cdap.common.utils.DirUtils;
-import io.cdap.cdap.internal.app.services.SystemProgramManagementService;
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
@@ -46,8 +45,7 @@ public class CapabilityManagementService extends AbstractRetryableScheduledServi
private final CapabilityApplier capabilityApplier;
@Inject
- CapabilityManagementService(CConfiguration cConf, CapabilityApplier capabilityApplier,
- SystemProgramManagementService systemProgramManagementService) {
+ CapabilityManagementService(CConfiguration cConf, CapabilityApplier capabilityApplier) {
super(RetryStrategies
.fixDelay(cConf.getLong(Constants.Capability.DIR_SCAN_INTERVAL_MINUTES), TimeUnit.MINUTES));
this.cConf = cConf;
diff --git a/cdap-app-fabric/src/test/java/io/cdap/cdap/app/runtime/AbstractProgramRuntimeServiceTest.java b/cdap-app-fabric/src/test/java/io/cdap/cdap/app/runtime/AbstractProgramRuntimeServiceTest.java
index 1c6cd896fe6b..4b1922a33ca6 100644
--- a/cdap-app-fabric/src/test/java/io/cdap/cdap/app/runtime/AbstractProgramRuntimeServiceTest.java
+++ b/cdap-app-fabric/src/test/java/io/cdap/cdap/app/runtime/AbstractProgramRuntimeServiceTest.java
@@ -287,7 +287,8 @@ public void testTetheredRun() throws IOException, ExecutionException, Interrupte
InMemoryProgramRunDispatcher launchDispatcher =
new TestProgramRunDispatcher(cConf, runnerFactory, program, locationFactory,
remoteClientFactory, true);
- ProgramRuntimeService runtimeService = new TestProgramRuntimeService(cConf, runnerFactory, null, launchDispatcher);
+ ProgramRuntimeService runtimeService = new TestProgramRuntimeService(cConf, runnerFactory, null,
+ launchDispatcher);
runtimeService.startAndWait();
try {
ProgramDescriptor descriptor = new ProgramDescriptor(program.getId(), null,
diff --git a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/AppFabricClient.java b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/AppFabricClient.java
index 2c4e0fde2c8a..cd2af980e5d0 100644
--- a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/AppFabricClient.java
+++ b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/AppFabricClient.java
@@ -33,6 +33,7 @@
import io.cdap.cdap.gateway.handlers.AppLifecycleHttpHandler;
import io.cdap.cdap.gateway.handlers.NamespaceHttpHandler;
import io.cdap.cdap.gateway.handlers.ProgramLifecycleHttpHandler;
+import io.cdap.cdap.gateway.handlers.ProgramRuntimeHttpHandler;
import io.cdap.cdap.gateway.handlers.WorkflowHttpHandler;
import io.cdap.cdap.gateway.handlers.util.AbstractAppFabricHttpHandler;
import io.cdap.cdap.internal.app.BufferFileInputStream;
@@ -106,6 +107,7 @@ public class AppFabricClient {
private final LocationFactory locationFactory;
private final AppLifecycleHttpHandler appLifecycleHttpHandler;
private final ProgramLifecycleHttpHandler programLifecycleHttpHandler;
+ private final ProgramRuntimeHttpHandler programRuntimeHttpHandler;
private final WorkflowHttpHandler workflowHttpHandler;
private final NamespaceHttpHandler namespaceHttpHandler;
private final NamespaceQueryAdmin namespaceQueryAdmin;
@@ -114,12 +116,14 @@ public class AppFabricClient {
public AppFabricClient(LocationFactory locationFactory,
AppLifecycleHttpHandler appLifecycleHttpHandler,
ProgramLifecycleHttpHandler programLifecycleHttpHandler,
+ ProgramRuntimeHttpHandler programRuntimeHttpHandler,
NamespaceHttpHandler namespaceHttpHandler,
NamespaceQueryAdmin namespaceQueryAdmin,
WorkflowHttpHandler workflowHttpHandler) {
this.locationFactory = locationFactory;
this.appLifecycleHttpHandler = appLifecycleHttpHandler;
this.programLifecycleHttpHandler = programLifecycleHttpHandler;
+ this.programRuntimeHttpHandler = programRuntimeHttpHandler;
this.namespaceHttpHandler = namespaceHttpHandler;
this.namespaceQueryAdmin = namespaceQueryAdmin;
this.workflowHttpHandler = workflowHttpHandler;
@@ -241,7 +245,7 @@ public void setWorkerInstances(String namespaceId, String appId, String workerId
json.addProperty("instances", instances);
request.content().writeCharSequence(json.toString(), StandardCharsets.UTF_8);
HttpUtil.setContentLength(request, request.content().readableBytes());
- programLifecycleHttpHandler.setWorkerInstances(request, responder, namespaceId, appId, workerId);
+ programRuntimeHttpHandler.setWorkerInstances(request, responder, namespaceId, appId, workerId);
verifyResponse(HttpResponseStatus.OK, responder.getStatus(), "Set worker instances failed");
}
@@ -249,7 +253,7 @@ public Instances getWorkerInstances(String namespaceId, String appId, String wor
MockResponder responder = new MockResponder();
String uri = String.format("%s/apps/%s/worker/%s/instances", getNamespacePath(namespaceId), appId, workerId);
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri);
- programLifecycleHttpHandler.getWorkerInstances(request, responder, namespaceId, appId, workerId);
+ programRuntimeHttpHandler.getWorkerInstances(request, responder, namespaceId, appId, workerId);
verifyResponse(HttpResponseStatus.OK, responder.getStatus(), "Get worker instances failed");
return responder.decodeResponseContent(Instances.class);
}
@@ -264,7 +268,7 @@ public void setServiceInstances(String namespaceId, String applicationId, String
json.addProperty("instances", instances);
request.content().writeCharSequence(json.toString(), StandardCharsets.UTF_8);
HttpUtil.setContentLength(request, request.content().readableBytes());
- programLifecycleHttpHandler.setServiceInstances(request, responder, namespaceId, applicationId, serviceName);
+ programRuntimeHttpHandler.setServiceInstances(request, responder, namespaceId, applicationId, serviceName);
verifyResponse(HttpResponseStatus.OK, responder.getStatus(), "Set service instances failed");
}
@@ -274,7 +278,7 @@ public ServiceInstances getServiceInstances(String namespaceId, String applicati
String uri = String.format("%s/apps/%s/services/%s/instances",
getNamespacePath(namespaceId), applicationId, serviceName);
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri);
- programLifecycleHttpHandler.getServiceInstances(request, responder, namespaceId, applicationId, serviceName);
+ programRuntimeHttpHandler.getServiceInstances(request, responder, namespaceId, applicationId, serviceName);
verifyResponse(HttpResponseStatus.OK, responder.getStatus(), "Get service instances failed");
return responder.decodeResponseContent(ServiceInstances.class);
}
diff --git a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/services/ProgramLifecycleServiceTest.java b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/services/ProgramLifecycleServiceTest.java
index 546198425f35..12f5700ff401 100644
--- a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/services/ProgramLifecycleServiceTest.java
+++ b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/services/ProgramLifecycleServiceTest.java
@@ -87,7 +87,7 @@ public static void shutdown() {
@Test
public void testEmptyRunsIsStopped() {
- Assert.assertEquals(ProgramStatus.STOPPED, ProgramLifecycleService.getProgramStatus(Collections.emptyList()));
+ Assert.assertEquals(ProgramStatus.STOPPED, programLifecycleService.getProgramStatus(Collections.emptyList()));
}
@Test
@@ -101,33 +101,33 @@ public void testProgramStatusFromSingleRun() {
.build();
// pending or starting -> starting
- ProgramStatus status = ProgramLifecycleService.getProgramStatus(Collections.singleton(record));
+ ProgramStatus status = programLifecycleService.getProgramStatus(Collections.singleton(record));
Assert.assertEquals(ProgramStatus.STARTING, status);
record = RunRecordDetail.builder(record).setStatus(ProgramRunStatus.STARTING).build();
- status = ProgramLifecycleService.getProgramStatus(Collections.singleton(record));
+ status = programLifecycleService.getProgramStatus(Collections.singleton(record));
Assert.assertEquals(ProgramStatus.STARTING, status);
// running, suspended, resuming -> running
record = RunRecordDetail.builder(record).setStatus(ProgramRunStatus.RUNNING).build();
- status = ProgramLifecycleService.getProgramStatus(Collections.singleton(record));
+ status = programLifecycleService.getProgramStatus(Collections.singleton(record));
Assert.assertEquals(ProgramStatus.RUNNING, status);
record = RunRecordDetail.builder(record).setStatus(ProgramRunStatus.SUSPENDED).build();
- status = ProgramLifecycleService.getProgramStatus(Collections.singleton(record));
+ status = programLifecycleService.getProgramStatus(Collections.singleton(record));
Assert.assertEquals(ProgramStatus.RUNNING, status);
// failed, killed, completed -> stopped
record = RunRecordDetail.builder(record).setStatus(ProgramRunStatus.FAILED).build();
- status = ProgramLifecycleService.getProgramStatus(Collections.singleton(record));
+ status = programLifecycleService.getProgramStatus(Collections.singleton(record));
Assert.assertEquals(ProgramStatus.STOPPED, status);
record = RunRecordDetail.builder(record).setStatus(ProgramRunStatus.KILLED).build();
- status = ProgramLifecycleService.getProgramStatus(Collections.singleton(record));
+ status = programLifecycleService.getProgramStatus(Collections.singleton(record));
Assert.assertEquals(ProgramStatus.STOPPED, status);
record = RunRecordDetail.builder(record).setStatus(ProgramRunStatus.COMPLETED).build();
- status = ProgramLifecycleService.getProgramStatus(Collections.singleton(record));
+ status = programLifecycleService.getProgramStatus(Collections.singleton(record));
Assert.assertEquals(ProgramStatus.STOPPED, status);
}
@@ -158,18 +158,18 @@ public void testProgramStatusFromMultipleRuns() {
.setStatus(ProgramRunStatus.COMPLETED).build();
// running takes precedence over others
- ProgramStatus status = ProgramLifecycleService.getProgramStatus(
+ ProgramStatus status = programLifecycleService.getProgramStatus(
Arrays.asList(pending, starting, running, killed, failed, completed));
Assert.assertEquals(ProgramStatus.RUNNING, status);
// starting takes precedence over stopped
- status = ProgramLifecycleService.getProgramStatus(Arrays.asList(pending, killed, failed, completed));
+ status = programLifecycleService.getProgramStatus(Arrays.asList(pending, killed, failed, completed));
Assert.assertEquals(ProgramStatus.STARTING, status);
- status = ProgramLifecycleService.getProgramStatus(Arrays.asList(starting, killed, failed, completed));
+ status = programLifecycleService.getProgramStatus(Arrays.asList(starting, killed, failed, completed));
Assert.assertEquals(ProgramStatus.STARTING, status);
// end states are stopped
- status = ProgramLifecycleService.getProgramStatus(Arrays.asList(killed, failed, completed));
+ status = programLifecycleService.getProgramStatus(Arrays.asList(killed, failed, completed));
Assert.assertEquals(ProgramStatus.STOPPED, status);
}
diff --git a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/services/SystemProgramManagementServiceTest.java b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/services/SystemProgramManagementServiceTest.java
index 054de2984f41..620e3225942f 100644
--- a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/services/SystemProgramManagementServiceTest.java
+++ b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/services/SystemProgramManagementServiceTest.java
@@ -26,6 +26,7 @@
import io.cdap.cdap.common.test.AppJarHelper;
import io.cdap.cdap.internal.AppFabricTestHelper;
import io.cdap.cdap.internal.app.runtime.BasicArguments;
+import io.cdap.cdap.internal.app.runtime.ProgramStartRequest;
import io.cdap.cdap.internal.app.runtime.artifact.ArtifactRepository;
import io.cdap.cdap.internal.app.services.http.AppFabricTestBase;
import io.cdap.cdap.proto.ProgramRunStatus;
@@ -53,9 +54,9 @@ public class SystemProgramManagementServiceTest extends AppFabricTestBase {
private static SystemProgramManagementService progmMgmtSvc;
private static ProgramLifecycleService programLifecycleService;
private static ApplicationLifecycleService applicationLifecycleService;
+ private static ProgramRuntimeService runtimeService;
private static LocationFactory locationFactory;
private static ArtifactRepository artifactRepository;
-
private static final String VERSION = "1.0.0";
private static final String APP_NAME = SystemProgramManagementTestApp.NAME;
private static final String NAMESPACE = "system";
@@ -65,6 +66,7 @@ public class SystemProgramManagementServiceTest extends AppFabricTestBase {
@BeforeClass
public static void setup() {
programLifecycleService = getInjector().getInstance(ProgramLifecycleService.class);
+ runtimeService = getInjector().getInstance(ProgramRuntimeService.class);
applicationLifecycleService = getInjector().getInstance(ApplicationLifecycleService.class);
locationFactory = getInjector().getInstance(LocationFactory.class);
artifactRepository = getInjector().getInstance(ArtifactRepository.class);
@@ -100,8 +102,8 @@ public void testIteration() throws Exception {
Assert.assertEquals(ProgramStatus.STOPPED.name(), getProgramStatus(programId));
assertProgramRuns(programId, ProgramRunStatus.RUNNING, 0);
//Run the program manually twice to test pruning. One run should be killed
- programLifecycleService.start(programId, new HashMap<>(), false, false);
- programLifecycleService.start(programId, new HashMap<>(), false, false);
+ startProgram(programId, new HashMap<>());
+ startProgram(programId, new HashMap<>());
assertProgramRuns(programId, ProgramRunStatus.RUNNING, 2);
progmMgmtSvc.setProgramsEnabled(enabledServices);
progmMgmtSvc.runTask();
@@ -125,4 +127,12 @@ private void deployTestApp() throws Exception {
// no-op
}, null, false, false, false, Collections.emptyMap());
}
+
+ private void startProgram(ProgramId programId, Map overrides)
+ throws Exception {
+ ProgramStartRequest startRequest = programLifecycleService.prepareStart(
+ programId, overrides, false, false);
+ runtimeService.run(startRequest.getProgramDescriptor(), startRequest.getProgramOptions(), startRequest.getRunId())
+ .getController();
+ }
}
diff --git a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/capability/CapabilityManagementServiceTest.java b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/capability/CapabilityManagementServiceTest.java
index 041b66a63395..2f24fb2d850a 100644
--- a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/capability/CapabilityManagementServiceTest.java
+++ b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/capability/CapabilityManagementServiceTest.java
@@ -30,6 +30,8 @@
import io.cdap.cdap.api.artifact.ArtifactSummary;
import io.cdap.cdap.app.program.ManifestFields;
import io.cdap.cdap.app.program.ProgramDescriptor;
+import io.cdap.cdap.app.runtime.ProgramRuntimeService;
+import io.cdap.cdap.app.runtime.ProgramStateWriter;
import io.cdap.cdap.common.conf.CConfiguration;
import io.cdap.cdap.common.conf.Constants;
import io.cdap.cdap.common.id.Id;
@@ -38,6 +40,7 @@
import io.cdap.cdap.common.test.PluginJarHelper;
import io.cdap.cdap.internal.AppFabricTestHelper;
import io.cdap.cdap.internal.app.deploy.pipeline.ApplicationWithPrograms;
+import io.cdap.cdap.internal.app.runtime.ProgramStartRequest;
import io.cdap.cdap.internal.app.runtime.artifact.ArtifactRepository;
import io.cdap.cdap.internal.app.services.ApplicationLifecycleService;
import io.cdap.cdap.internal.app.services.ProgramLifecycleService;
@@ -82,6 +85,8 @@ public class CapabilityManagementServiceTest extends AppFabricTestBase {
private static ApplicationLifecycleService applicationLifecycleService;
private static ProgramLifecycleService programLifecycleService;
private static CapabilityStatusStore capabilityStatusStore;
+ private static ProgramStateWriter programStateWriter;
+ private static ProgramRuntimeService runtimeService;
private static final Gson GSON = new Gson();
@BeforeClass
@@ -93,6 +98,8 @@ public static void setup() {
capabilityStatusStore = new CapabilityStatusStore(getInjector().getInstance(TransactionRunner.class));
applicationLifecycleService = getInjector().getInstance(ApplicationLifecycleService.class);
programLifecycleService = getInjector().getInstance(ProgramLifecycleService.class);
+ programStateWriter = getInjector().getInstance(ProgramStateWriter.class);
+ runtimeService = getInjector().getInstance(ProgramRuntimeService.class);
capabilityManagementService.stopAndWait();
}
@@ -561,7 +568,7 @@ public void testProgramStart() throws Exception {
});
Iterable programs = applicationWithPrograms.getPrograms();
for (ProgramDescriptor program : programs) {
- programLifecycleService.start(program.getProgramId(), new HashMap<>(), false, false);
+ startProgram(program.getProgramId(), new HashMap<>());
}
ProgramId programId = new ProgramId(applicationId, ProgramType.WORKFLOW,
CapabilitySleepingWorkflowApp.SleepWorkflow.class.getSimpleName());
@@ -588,7 +595,7 @@ public void testProgramStart() throws Exception {
//try starting programs
for (ProgramDescriptor program : programs) {
try {
- programLifecycleService.start(program.getProgramId(), new HashMap<>(), false, false);
+ startProgram(program.getProgramId(), new HashMap<>());
Assert.fail("expecting exception");
} catch (CapabilityNotAvailableException ex) {
//expecting exception
@@ -641,7 +648,7 @@ public void testProgramWithPluginStart() throws Exception {
});
Iterable programs = applicationWithPrograms.getPrograms();
for (ProgramDescriptor program : programs) {
- programLifecycleService.start(program.getProgramId(), new HashMap<>(), false, false);
+ startProgram(program.getProgramId(), new HashMap<>());
}
ProgramId programId = new ProgramId(applicationId, ProgramType.WORKFLOW,
CapabilitySleepingWorkflowPluginApp.SleepWorkflow.class.getSimpleName());
@@ -668,7 +675,7 @@ public void testProgramWithPluginStart() throws Exception {
//try starting programs
for (ProgramDescriptor program : programs) {
try {
- programLifecycleService.start(program.getProgramId(), new HashMap<>(), false, false);
+ startProgram(program.getProgramId(), new HashMap<>());
Assert.fail("expecting exception");
} catch (CapabilityNotAvailableException ex) {
//expecting exception
@@ -819,4 +826,12 @@ private void deployArtifactAndApp(Class> applicationClass, String appName, Str
null, programId -> {
});
}
+
+ private void startProgram(ProgramId programId, Map overrides)
+ throws Exception {
+ ProgramStartRequest startRequest = programLifecycleService.prepareStart(
+ programId, overrides, false, false);
+ runtimeService.run(startRequest.getProgramDescriptor(), startRequest.getProgramOptions(), startRequest.getRunId())
+ .getController();
+ }
}
diff --git a/cdap-gateway/src/main/java/io/cdap/cdap/gateway/router/RouterPathLookup.java b/cdap-gateway/src/main/java/io/cdap/cdap/gateway/router/RouterPathLookup.java
index b8f08ee4e986..779c017e9f61 100644
--- a/cdap-gateway/src/main/java/io/cdap/cdap/gateway/router/RouterPathLookup.java
+++ b/cdap-gateway/src/main/java/io/cdap/cdap/gateway/router/RouterPathLookup.java
@@ -34,25 +34,6 @@
*/
public final class RouterPathLookup extends AbstractHttpHandler {
- public static final Set APP_FABRIC_PROCESSOR_PATH_PARTS =
- createAppFabricPathParts();
-
- private static Set createAppFabricPathParts() {
- return ImmutableSet.of("previousruntime",
- "nextruntime",
- "status",
- "stop",
- "start",
- "instances",
- "runcount",
- "runs",
- "mapreduce",
- "spark",
- "workflows",
- "services",
- "workers");
- }
-
@SuppressWarnings("unused")
private enum AllowedMethod {
GET, PUT, POST, DELETE
@@ -105,12 +86,6 @@ public RouteDestination getRoutingService(String requestPath, HttpRequest httpRe
if (uriParts[0].equals(Constants.Gateway.API_VERSION_3_TOKEN)) {
return getV3RoutingService(uriParts, requestMethod);
}
-
- // TODO (CDAP-21112): Move HTTP handler from Appfabric processor to server after fixing
- // ProgramRuntimeService and RunRecordMonitorService.
- if (uriParts[0].equals(Gateway.INTERNAL_API_VERSION_3_TOKEN)) {
- return getV3InternalRoutingService(uriParts);
- }
} catch (Exception e) {
// Ignore exception. Default routing to app-fabric.
}
@@ -126,15 +101,6 @@ private boolean isUserServiceType(String uriPart) {
return false;
}
- private RouteDestination getV3InternalRoutingService(String[] uriParts) {
- if (beginsWith(uriParts, "v3Internal", "namespaces", null)) {
- // ProgramLifecycleHttpHandlerInternal and AppLifecycleHttpHandlerInternal paths.
- // Paths: "/v3Internal/namespaces/{namespace-id}/**"
- return APP_FABRIC_PROCESSOR;
- }
- return APP_FABRIC_HTTP;
- }
-
@Nullable
private RouteDestination getV3RoutingService(String[] uriParts, AllowedMethod requestMethod) {
if ((uriParts.length >= 2) && uriParts[1].equals("feeds")) {
@@ -163,6 +129,21 @@ private RouteDestination getV3RoutingService(String[] uriParts, AllowedMethod re
} else if (beginsWith(uriParts, "v3", "system", "services", null, "logs")) {
//Log Handler Path /v3/system/services//logs
return LOG_QUERY;
+ } else if (beginsWith(uriParts, "v3", "namespaces", null)
+ && (endsWith(uriParts, "instances")
+ || endsWith(uriParts, "live-info")
+ || endsWith(uriParts, "loglevels")
+ || endsWith(uriParts, "resetloglevels"))) {
+ // ProgramRuntimeLifecycleHttpHandler Paths:
+ // v3/namespaces/{ns-id}/instances
+ // v3/namespaces/{ns-id}/apps/{app-id}/services/{service-id}/instances
+ // v3/namespaces/{ns-id}/apps/{app-id}/workers/{worker-id}/instances
+ // v3/namespaces/{ns-id}/apps/{app-id}/{program-category}/{program-id}/live-info
+ // v3/namespaces/{ns-id}/apps/{app-name}/{program-type}/{program-name}/runs/{run-id}/loglevels
+ // v3/namespaces/{ns-id}/apps/{app-name}/versions/{appVer}/{progType}/{progName}/runs/{runId}/loglevels
+ // v3/namespaces/{ns-id}/apps/{app-name}/{program-type}/{progName}/runs/{runId}/resetloglevels
+ // v3/namespaces/{ns-id}/apps/{app-name}/versions/{appVer}/{progType}/{progName}/runs/{runId}/resetloglevels
+ return APP_FABRIC_PROCESSOR;
} else if ((!beginsWith(uriParts, "v3", "namespaces", null, "securekeys")) && (
endsWith(uriParts, "metadata")
||
@@ -192,7 +173,7 @@ private RouteDestination getV3RoutingService(String[] uriParts, AllowedMethod re
|| beginsWith(uriParts, "v3", "profiles")) {
return APP_FABRIC_HTTP;
} else if (beginsWith(uriParts, "v3", "namespaces", null, "runs")) {
- return APP_FABRIC_PROCESSOR;
+ return APP_FABRIC_HTTP;
} else if (beginsWith(uriParts, "v3", "namespaces", null, "previews")) {
return PREVIEW_HTTP;
} else if (beginsWith(uriParts, "v3", "system", "serviceproviders")) {
@@ -262,25 +243,6 @@ private RouteDestination getV3RoutingService(String[] uriParts, AllowedMethod re
// we don't want to expose endpoints for direct metadata mutation from CDAP master
// /v3/metadata-internals/{mutation-type}
return DONT_ROUTE;
- } else if (beginsWith(uriParts, "v3", "namespaces", null, "apps", null, "preferences")
- || beginsWith(uriParts, "v3", "namespaces", null, "apps", null, null, null, "preferences")) {
- // App Preferences Paths:
- // /v3/namespaces/{namespace-id}/apps/{application-id}/preferences
- // /v3/namespaces/{namespace-id}/apps/{application-id}/{program-type}/{program-id}/preferences
- return APP_FABRIC_HTTP;
- } else if (beginsWith(uriParts, "v3", "namespaces", null, "apps", null, "datasets")
- || beginsWith(uriParts, "v3", "namespaces", null, "apps", null, null, null, "datasets")) {
- return APP_FABRIC_HTTP;
- } else if (beginsWith(uriParts, "v3", "namespaces", null, "apps")
- || beginsWith(uriParts, "v3", "namespaces", null, "upgrade")
- || beginsWith(uriParts, "v3", "namespaces", null, "appdetail")
- || beginsWith(uriParts, "v3", "namespaces", null, "schedules")) {
- // Paths for AppLifecycleHttpHandler, ProgramLifecycleHttpHandler, WorkflowHttpHandler.
- return APP_FABRIC_PROCESSOR;
- } else if (beginsWith(uriParts, "v3", "namespaces")
- && APP_FABRIC_PROCESSOR_PATH_PARTS.contains(uriParts[3])) {
- // Paths for ProgramLifecycleHttpHandler.
- return APP_FABRIC_PROCESSOR;
}
return APP_FABRIC_HTTP;
}
diff --git a/cdap-gateway/src/test/java/io/cdap/cdap/gateway/router/RouterPathLookupTest.java b/cdap-gateway/src/test/java/io/cdap/cdap/gateway/router/RouterPathLookupTest.java
index 0b28272422c6..90b30c9f8b35 100644
--- a/cdap-gateway/src/test/java/io/cdap/cdap/gateway/router/RouterPathLookupTest.java
+++ b/cdap-gateway/src/test/java/io/cdap/cdap/gateway/router/RouterPathLookupTest.java
@@ -56,7 +56,7 @@ public void testBatchRunsPath() {
String path = "/v3/namespaces/n1/runs";
HttpRequest httpRequest = new DefaultHttpRequest(VERSION, new HttpMethod("POST"), path);
RouteDestination result = pathLookup.getRoutingService(path, httpRequest);
- Assert.assertEquals(RouterPathLookup.APP_FABRIC_PROCESSOR, result);
+ Assert.assertEquals(RouterPathLookup.APP_FABRIC_HTTP, result);
}
@Test
@@ -165,13 +165,13 @@ public void testServicePath() {
HttpRequest httpRequest = new DefaultHttpRequest(VERSION, new HttpMethod("PUT"), servicePath);
httpRequest.headers().set(Constants.Gateway.API_KEY, API_KEY);
RouteDestination result = pathLookup.getRoutingService(servicePath, httpRequest);
- Assert.assertEquals(RouterPathLookup.APP_FABRIC_PROCESSOR, result);
+ Assert.assertEquals(RouterPathLookup.APP_FABRIC_HTTP, result);
servicePath = "v3/namespaces/some/apps/otherAppName/services/CatalogLookup//methods////";
httpRequest = new DefaultHttpRequest(VERSION, new HttpMethod("GET"), servicePath);
httpRequest.headers().set(Constants.Gateway.API_KEY, API_KEY);
result = pathLookup.getRoutingService(servicePath, httpRequest);
- Assert.assertEquals(RouterPathLookup.APP_FABRIC_PROCESSOR, result);
+ Assert.assertEquals(RouterPathLookup.APP_FABRIC_HTTP, result);
// v3 servicePaths
servicePath = "/v3/namespaces/testnamespace/apps//PurchaseHistory///services/CatalogLookup///methods//ping/1";
@@ -207,13 +207,13 @@ public void testServicePath() {
httpRequest = new DefaultHttpRequest(VERSION, new HttpMethod("PUT"), servicePath);
httpRequest.headers().set(Constants.Gateway.API_KEY, API_KEY);
result = pathLookup.getRoutingService(servicePath, httpRequest);
- Assert.assertEquals(RouterPathLookup.APP_FABRIC_PROCESSOR, result);
+ Assert.assertEquals(RouterPathLookup.APP_FABRIC_HTTP, result);
servicePath = "v3/namespaces/testnamespace/apps/AppName/services/CatalogLookup////methods////";
httpRequest = new DefaultHttpRequest(VERSION, new HttpMethod("GET"), servicePath);
httpRequest.headers().set(Constants.Gateway.API_KEY, API_KEY);
result = pathLookup.getRoutingService(servicePath, httpRequest);
- Assert.assertEquals(RouterPathLookup.APP_FABRIC_PROCESSOR, result);
+ Assert.assertEquals(RouterPathLookup.APP_FABRIC_HTTP, result);
}
@Test
@@ -226,7 +226,7 @@ public void testRouterServicePathLookUp() {
String path = "/v3/namespaces/default//apps/ResponseCodeAnalytics/services/LogAnalyticsService/status";
HttpRequest httpRequest = new DefaultHttpRequest(VERSION, new HttpMethod("GET"), path);
RouteDestination result = pathLookup.getRoutingService(path, httpRequest);
- Assert.assertEquals(RouterPathLookup.APP_FABRIC_PROCESSOR, result);
+ Assert.assertEquals(RouterPathLookup.APP_FABRIC_HTTP, result);
}
@Test
@@ -234,7 +234,7 @@ public void testRouterWorkFlowPathLookUp() {
String path = "/v3/namespaces/default/apps///PurchaseHistory///workflows/PurchaseHistoryWorkflow/status";
HttpRequest httpRequest = new DefaultHttpRequest(VERSION, new HttpMethod("GET"), path);
RouteDestination result = pathLookup.getRoutingService(path, httpRequest);
- Assert.assertEquals(RouterPathLookup.APP_FABRIC_PROCESSOR, result);
+ Assert.assertEquals(RouterPathLookup.APP_FABRIC_HTTP, result);
}
@Test
@@ -242,7 +242,7 @@ public void testRouterDeployPathLookUp() {
String path = "/v3/namespaces/default//apps/";
HttpRequest httpRequest = new DefaultHttpRequest(VERSION, new HttpMethod("PUT"), path);
RouteDestination result = pathLookup.getRoutingService(path, httpRequest);
- Assert.assertEquals(RouterPathLookup.APP_FABRIC_PROCESSOR, result);
+ Assert.assertEquals(RouterPathLookup.APP_FABRIC_HTTP, result);
}
@Test
@@ -422,23 +422,16 @@ public void testProfilePaths() {
@Test
public void testAppLifecycleAndWorkflowPaths() {
- assertRouting("v3/namespaces/default/apps", RouterPathLookup.APP_FABRIC_PROCESSOR);
- assertRouting("v3/namespaces/default/apps/myapp", RouterPathLookup.APP_FABRIC_PROCESSOR);
- assertRouting("v3/namespaces/default/upgrade", RouterPathLookup.APP_FABRIC_PROCESSOR);
- assertRouting("v3/namespaces/default/appdetail", RouterPathLookup.APP_FABRIC_PROCESSOR);
- }
-
- @Test
- public void testProgramLifecyclePaths() {
- for (String part : RouterPathLookup.APP_FABRIC_PROCESSOR_PATH_PARTS) {
- assertRouting("v3/namespaces/default/" + part, RouterPathLookup.APP_FABRIC_PROCESSOR);
- }
+ assertRouting("v3/namespaces/default/apps", RouterPathLookup.APP_FABRIC_HTTP);
+ assertRouting("v3/namespaces/default/apps/myapp", RouterPathLookup.APP_FABRIC_HTTP);
+ assertRouting("v3/namespaces/default/upgrade", RouterPathLookup.APP_FABRIC_HTTP);
+ assertRouting("v3/namespaces/default/appdetail", RouterPathLookup.APP_FABRIC_HTTP);
}
@Test
public void testProgramLifecycleInternalAndAppLifecycleInternalPaths() {
assertRouting("v3Internal/namespaces//apps//versions//workflows/DataPipelineWorkflow/runs/",
- RouterPathLookup.APP_FABRIC_PROCESSOR);
+ RouterPathLookup.APP_FABRIC_HTTP);
}
@Test
@@ -453,6 +446,18 @@ public void testAppPreferencesPaths() {
assertRouting("v3/namespaces//apps////preferences", RouterPathLookup.APP_FABRIC_HTTP);
}
+ @Test
+ public void testProgramRuntimeLifecyclePaths() {
+ assertRouting("v3/namespaces//instances", RouterPathLookup.APP_FABRIC_PROCESSOR);
+ assertRouting("v3/namespaces//apps//services//instances", RouterPathLookup.APP_FABRIC_PROCESSOR);
+ assertRouting("v3/namespaces//apps//workers//instances", RouterPathLookup.APP_FABRIC_PROCESSOR);
+ assertRouting("v3/namespaces//apps////live-info", RouterPathLookup.APP_FABRIC_PROCESSOR);
+ assertRouting("v3/namespaces//apps////runs//loglevels", RouterPathLookup.APP_FABRIC_PROCESSOR);
+ assertRouting("v3/namespaces//apps////runs//resetloglevels", RouterPathLookup.APP_FABRIC_PROCESSOR);
+ assertRouting("v3/namespaces//apps//versions////runs//loglevels", RouterPathLookup.APP_FABRIC_PROCESSOR);
+ assertRouting("v3/namespaces//apps//versions////runs//resetloglevels", RouterPathLookup.APP_FABRIC_PROCESSOR);
+ }
+
@Test
public void testBeginsWith() {
// anything begins empty sequence
diff --git a/cdap-watchdog/src/main/java/io/cdap/cdap/logging/gateway/handlers/RemoteProgramRunRecordFetcher.java b/cdap-watchdog/src/main/java/io/cdap/cdap/logging/gateway/handlers/RemoteProgramRunRecordFetcher.java
index 001fb64595c2..1eea3a4efb79 100644
--- a/cdap-watchdog/src/main/java/io/cdap/cdap/logging/gateway/handlers/RemoteProgramRunRecordFetcher.java
+++ b/cdap-watchdog/src/main/java/io/cdap/cdap/logging/gateway/handlers/RemoteProgramRunRecordFetcher.java
@@ -49,7 +49,7 @@ public RemoteProgramRunRecordFetcher(RemoteClientFactory remoteClientFactory) {
// ProgramRuntimeService and RunRecordMonitorService.
// Use APP_FABRIC_HTTP instead of APP_FABRIC_PROCESSOR after the fix.
this.remoteClient = remoteClientFactory.createRemoteClient(
- Constants.Service.APP_FABRIC_PROCESSOR,
+ Constants.Service.APP_FABRIC_HTTP,
new DefaultHttpRequestConfig(false),
Constants.Gateway.INTERNAL_API_VERSION_3);
}