/*
* Copyright © 2014-2016 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 co.cask.cdap.internal.app.store;
import co.cask.cdap.api.ProgramSpecification;
import co.cask.cdap.api.app.ApplicationSpecification;
import co.cask.cdap.api.data.DatasetInstantiationException;
import co.cask.cdap.api.data.stream.StreamSpecification;
import co.cask.cdap.api.dataset.DatasetAdmin;
import co.cask.cdap.api.dataset.DatasetDefinition;
import co.cask.cdap.api.dataset.DatasetManagementException;
import co.cask.cdap.api.dataset.DatasetProperties;
import co.cask.cdap.api.dataset.table.Table;
import co.cask.cdap.api.flow.FlowSpecification;
import co.cask.cdap.api.flow.FlowletConnection;
import co.cask.cdap.api.flow.FlowletDefinition;
import co.cask.cdap.api.schedule.ScheduleSpecification;
import co.cask.cdap.api.service.ServiceSpecification;
import co.cask.cdap.api.worker.WorkerSpecification;
import co.cask.cdap.api.workflow.WorkflowActionNode;
import co.cask.cdap.api.workflow.WorkflowNode;
import co.cask.cdap.api.workflow.WorkflowSpecification;
import co.cask.cdap.api.workflow.WorkflowToken;
import co.cask.cdap.app.program.Program;
import co.cask.cdap.app.program.Programs;
import co.cask.cdap.app.store.Store;
import co.cask.cdap.archive.ArchiveBundler;
import co.cask.cdap.common.ApplicationNotFoundException;
import co.cask.cdap.common.ProgramNotFoundException;
import co.cask.cdap.common.conf.CConfiguration;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.common.namespace.NamespacedLocationFactory;
import co.cask.cdap.data.dataset.SystemDatasetInstantiator;
import co.cask.cdap.data2.datafabric.dataset.DatasetsUtil;
import co.cask.cdap.data2.dataset2.DatasetFramework;
import co.cask.cdap.data2.dataset2.MultiThreadDatasetCache;
import co.cask.cdap.internal.app.ForwardingApplicationSpecification;
import co.cask.cdap.internal.app.ForwardingFlowSpecification;
import co.cask.cdap.internal.app.program.ProgramBundle;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.ProgramRunStatus;
import co.cask.cdap.proto.ProgramType;
import co.cask.cdap.proto.WorkflowNodeStateDetail;
import co.cask.cdap.proto.WorkflowStatistics;
import co.cask.cdap.proto.id.NamespaceId;
import co.cask.cdap.proto.id.ProgramRunId;
import co.cask.tephra.TransactionAware;
import co.cask.tephra.TransactionExecutor;
import co.cask.tephra.TransactionExecutorFactory;
import co.cask.tephra.TransactionSystemClient;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.inject.Inject;
import org.apache.twill.api.RunId;
import org.apache.twill.filesystem.Location;
import org.apache.twill.filesystem.LocationFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* Implementation of the Store that ultimately places data into MetaDataTable.
*/
public class DefaultStore implements Store {
// mds is specific for metadata, we do not want to add workflow stats related information to the mds,
// as it is not specifically metadata
public static final String WORKFLOW_STATS_TABLE = "workflow.stats";
private static final Logger LOG = LoggerFactory.getLogger(DefaultStore.class);
private static final Id.DatasetInstance APP_META_INSTANCE_ID =
Id.DatasetInstance.from(Id.Namespace.SYSTEM, Constants.AppMetaStore.TABLE);
private static final Id.DatasetInstance WORKFLOW_STATS_INSTANCE_ID =
Id.DatasetInstance.from(Id.Namespace.SYSTEM, WORKFLOW_STATS_TABLE);
private static final Gson GSON = new Gson();
private static final Map<String, String> EMPTY_STRING_MAP = ImmutableMap.of();
private static final Type STRING_MAP_TYPE = new TypeToken<Map<String, String>>() { }.getType();
private final LocationFactory locationFactory;
private final NamespacedLocationFactory namespacedLocationFactory;
private final CConfiguration configuration;
private final DatasetFramework dsFramework;
private final Supplier<AppMetadataStore> apps;
private final Supplier<WorkflowDataset> workflows;
private final Supplier<TransactionExecutor> appsTx;
private final Supplier<TransactionExecutor> workflowsTx;
private final MultiThreadDatasetCache dsCache;
@Inject
public DefaultStore(CConfiguration conf,
LocationFactory locationFactory,
NamespacedLocationFactory namespacedLocationFactory,
final TransactionExecutorFactory txExecutorFactory,
DatasetFramework framework,
TransactionSystemClient txClient) {
this.configuration = conf;
this.locationFactory = locationFactory;
this.namespacedLocationFactory = namespacedLocationFactory;
this.dsFramework = framework;
this.dsCache = new MultiThreadDatasetCache(
new SystemDatasetInstantiator(framework, null, null), txClient,
NamespaceId.SYSTEM, ImmutableMap.<String, String>of(), null, null);
this.apps =
new Supplier<AppMetadataStore>() {
@Override
public AppMetadataStore get() {
Table table = getCachedOrCreateTable(APP_META_INSTANCE_ID.getId());
return new AppMetadataStore(table, configuration);
}
};
this.appsTx = new Supplier<TransactionExecutor>() {
@Override
public TransactionExecutor get() {
return txExecutorFactory.createExecutor(ImmutableList.of((TransactionAware) apps.get()));
}
};
this.workflows =
new Supplier<WorkflowDataset>() {
@Override
public WorkflowDataset get() {
Table table = getCachedOrCreateTable(WORKFLOW_STATS_INSTANCE_ID.getId());
return new WorkflowDataset(table);
}
};
this.workflowsTx = new Supplier<TransactionExecutor>() {
@Override
public TransactionExecutor get() {
return txExecutorFactory.createExecutor(ImmutableList.of((TransactionAware) workflows.get()));
}
};
}
/**
* Adds datasets and types to the given {@link DatasetFramework} used by app mds.
*
* @param framework framework to add types and datasets to
*/
public static void setupDatasets(DatasetFramework framework) throws IOException, DatasetManagementException {
framework.addInstance(Table.class.getName(), APP_META_INSTANCE_ID, DatasetProperties.EMPTY);
framework.addInstance(Table.class.getName(), WORKFLOW_STATS_INSTANCE_ID, DatasetProperties.EMPTY);
}
private Table getCachedOrCreateTable(String name) {
try {
return dsCache.getDataset(name);
} catch (DatasetInstantiationException e) {
try {
DatasetsUtil.getOrCreateDataset(
dsFramework, Id.DatasetInstance.from(Id.Namespace.SYSTEM, name), "table",
DatasetProperties.EMPTY, DatasetDefinition.NO_ARGUMENTS, null);
return dsCache.getDataset(name);
} catch (DatasetManagementException | IOException e1) {
throw Throwables.propagate(e);
}
}
}
@Override
public Program loadProgram(final Id.Program id)
throws IOException, ApplicationNotFoundException, ProgramNotFoundException {
ApplicationMeta appMeta = appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, ApplicationMeta>() {
@Override
public ApplicationMeta apply(AppMetadataStore mds) throws Exception {
return mds.getApplication(id.getNamespaceId(), id.getApplicationId());
}
}, apps.get());
if (appMeta == null) {
throw new ApplicationNotFoundException(Id.Application.from(id.getNamespaceId(), id.getApplicationId()));
}
if (!programExists(id, appMeta.getSpec())) {
throw new ProgramNotFoundException(id);
}
Location programLocation = getProgramLocation(id);
// I guess this can happen when app is being deployed at the moment...
// todo: should be prevented by framework
// todo: this should not be checked here but in start()
Preconditions.checkArgument(appMeta.getLastUpdateTs() >= programLocation.lastModified(),
"Newer program update time than the specification update time. " +
"Application must be redeployed");
return Programs.create(programLocation);
}
@Override
public void compareAndSetStatus(final Id.Program id, final String pid, final ProgramRunStatus expectedStatus,
final ProgramRunStatus updateStatus) {
Preconditions.checkArgument(expectedStatus != null, "Expected of program run should be defined");
Preconditions.checkArgument(updateStatus != null, "Updated state of program run should be defined");
appsTx.get().executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
RunRecordMeta target = mds.getRun(id, pid);
if (target.getStatus() == expectedStatus) {
long now = System.currentTimeMillis();
long nowSecs = TimeUnit.MILLISECONDS.toSeconds(now);
switch (updateStatus) {
case RUNNING:
Map<String, String> runtimeArgs = GSON.fromJson(target.getProperties().get("runtimeArgs"),
STRING_MAP_TYPE);
Map<String, String> systemArgs = GSON.fromJson(target.getProperties().get("systemArgs"),
STRING_MAP_TYPE);
if (runtimeArgs == null) {
runtimeArgs = EMPTY_STRING_MAP;
}
if (systemArgs == null) {
systemArgs = EMPTY_STRING_MAP;
}
mds.recordProgramStart(id, pid, nowSecs, target.getTwillRunId(), runtimeArgs, systemArgs);
break;
case SUSPENDED:
mds.recordProgramSuspend(id, pid);
break;
case COMPLETED:
case KILLED:
case FAILED:
Throwable failureCause = updateStatus == ProgramRunStatus.FAILED
? new Throwable("Marking run record as failed since no running program found.") : null;
mds.recordProgramStop(id, pid, nowSecs, updateStatus, failureCause);
break;
default:
break;
}
}
return null;
}
}, apps.get());
}
@Override
public void setStart(final Id.Program id, final String pid, final long startTime,
final String twillRunId, final Map<String, String> runtimeArgs,
final Map<String, String> systemArgs) {
appsTx.get().executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.recordProgramStart(id, pid, startTime, twillRunId, runtimeArgs, systemArgs);
return null;
}
}, apps.get());
}
@Override
public void setStart(Id.Program id, String pid, long startTime) {
setStart(id, pid, startTime, null, EMPTY_STRING_MAP, EMPTY_STRING_MAP);
}
@Override
public void setStop(final Id.Program id, final String pid, final long endTime, final ProgramRunStatus runStatus) {
setStop(id, pid, endTime, runStatus, null);
}
@Override
public void setStop(final Id.Program id, final String pid, final long endTime, final ProgramRunStatus runStatus,
final Throwable failureCause) {
Preconditions.checkArgument(runStatus != null, "Run state of program run should be defined");
appsTx.get().executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.recordProgramStop(id, pid, endTime, runStatus, failureCause);
return null;
}
}, apps.get());
// This block has been added so that completed workflow runs can be logged to the workflow dataset
if (id.getType() == ProgramType.WORKFLOW && runStatus == ProgramRunStatus.COMPLETED) {
Id.Workflow workflow = Id.Workflow.from(id.getApplication(), id.getId());
recordCompletedWorkflow(workflow, pid);
}
// todo: delete old history data
}
private void recordCompletedWorkflow(final Id.Workflow id, String pid) {
final RunRecordMeta run = getRun(id, pid);
if (run == null) {
return;
}
Id.Application app = id.getApplication();
ApplicationSpecification appSpec = getApplication(app);
if (appSpec == null || appSpec.getWorkflows() == null || appSpec.getWorkflows().get(id.getId()) == null) {
return;
}
boolean workFlowNodeFailed = false;
WorkflowSpecification workflowSpec = appSpec.getWorkflows().get(id.getId());
Map<String, WorkflowNode> nodeIdMap = workflowSpec.getNodeIdMap();
final List<WorkflowDataset.ProgramRun> programRunsList = new ArrayList<>();
for (Map.Entry<String, String> entry : run.getProperties().entrySet()) {
if (!("workflowToken".equals(entry.getKey()) || "runtimeArgs".equals(entry.getKey())
|| "workflowNodeState".equals(entry.getKey()))) {
WorkflowActionNode workflowNode = (WorkflowActionNode) nodeIdMap.get(entry.getKey());
ProgramType programType = ProgramType.valueOfSchedulableType(workflowNode.getProgram().getProgramType());
Id.Program innerProgram = Id.Program.from(app.getNamespaceId(), app.getId(), programType, entry.getKey());
RunRecordMeta innerProgramRun = getRun(innerProgram, entry.getValue());
if (innerProgramRun != null && innerProgramRun.getStatus().equals(ProgramRunStatus.COMPLETED)) {
Long stopTs = innerProgramRun.getStopTs();
// since the program is completed, the stop ts cannot be null
Preconditions.checkState(stopTs != null, "Since the program has completed, expected its stop time to not " +
"be null. Program = %s, Workflow = %s, Run = %s, Stop Ts = %s", innerProgram, id, run, stopTs);
programRunsList.add(new WorkflowDataset.ProgramRun(
entry.getKey(), entry.getValue(), programType, stopTs - innerProgramRun.getStartTs()));
} else {
workFlowNodeFailed = true;
break;
}
}
}
if (workFlowNodeFailed) {
return;
}
workflowsTx.get().executeUnchecked(new TransactionExecutor.Function<WorkflowDataset, Void>() {
@Override
public Void apply(WorkflowDataset dataset) {
dataset.write(id, run, programRunsList);
return null;
}
}, workflows.get());
}
@Override
public void setSuspend(final Id.Program id, final String pid) {
appsTx.get().executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.recordProgramSuspend(id, pid);
return null;
}
}, apps.get());
}
@Override
public void setResume(final Id.Program id, final String pid) {
appsTx.get().executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.recordProgramResumed(id, pid);
return null;
}
}, apps.get());
}
@Nullable
public WorkflowStatistics getWorkflowStatistics(final Id.Workflow id, final long startTime,
final long endTime, final List<Double> percentiles) {
return workflowsTx.get().executeUnchecked(
new TransactionExecutor.Function<WorkflowDataset, WorkflowStatistics>() {
@Override
public WorkflowStatistics apply(WorkflowDataset dataset) throws Exception {
return dataset.getStatistics(id, startTime, endTime, percentiles);
}
}, workflows.get());
}
@Override
public WorkflowDataset.WorkflowRunRecord getWorkflowRun(final Id.Workflow workflowId, final String runId) {
return workflowsTx.get().executeUnchecked(
new TransactionExecutor.Function<WorkflowDataset, WorkflowDataset.WorkflowRunRecord>() {
@Override
public WorkflowDataset.WorkflowRunRecord apply(WorkflowDataset dataset) throws Exception {
return dataset.getRecord(workflowId, runId);
}
}, workflows.get());
}
@Override
public Collection<WorkflowDataset.WorkflowRunRecord> retrieveSpacedRecords(final Id.Workflow workflow,
final String runId,
final int limit,
final long timeInterval) {
return workflowsTx.get().executeUnchecked(
new TransactionExecutor.Function<WorkflowDataset, Collection<WorkflowDataset.WorkflowRunRecord>>() {
@Override
public Collection<WorkflowDataset.WorkflowRunRecord> apply(WorkflowDataset dataset) throws Exception {
return dataset.getDetailsOfRange(workflow, runId, limit, timeInterval);
}
}, workflows.get());
}
@Override
public List<RunRecordMeta> getRuns(final Id.Program id, final ProgramRunStatus status,
final long startTime, final long endTime, final int limit) {
return getRuns(id, status, startTime, endTime, limit, null);
}
@Override
public List<RunRecordMeta> getRuns(final Id.Program id, final ProgramRunStatus status,
final long startTime, final long endTime, final int limit,
@Nullable final Predicate<RunRecordMeta> filter) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, List<RunRecordMeta>>() {
@Override
public List<RunRecordMeta> apply(AppMetadataStore mds) throws Exception {
return mds.getRuns(id, status, startTime, endTime, limit, filter);
}
}, apps.get());
}
@Override
public List<RunRecordMeta> getRuns(final ProgramRunStatus status, final Predicate<RunRecordMeta> filter) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, List<RunRecordMeta>>() {
@Override
public List<RunRecordMeta> apply(AppMetadataStore mds) throws Exception {
return mds.getRuns(status, filter);
}
}, apps.get());
}
/**
* Returns run record for a given run.
*
* @param id program id
* @param runid run id
* @return run record for runid
*/
@Override
public RunRecordMeta getRun(final Id.Program id, final String runid) {
return appsTx.get().executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, RunRecordMeta>() {
@Override
public RunRecordMeta apply(AppMetadataStore mds) throws Exception {
return mds.getRun(id, runid);
}
}, apps.get());
}
@Override
public void addApplication(final Id.Application id,
final ApplicationSpecification spec, final Location appArchiveLocation) {
appsTx.get().executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.writeApplication(id.getNamespaceId(), id.getId(), spec, appArchiveLocation.toURI().toString());
return null;
}
}, apps.get());
}
// todo: this method should be moved into DeletedProgramHandlerState, bad design otherwise
@Override
public List<ProgramSpecification> getDeletedProgramSpecifications(final Id.Application id,
ApplicationSpecification appSpec) {
ApplicationMeta existing = appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, ApplicationMeta>() {
@Override
public ApplicationMeta apply(AppMetadataStore mds) throws Exception {
return mds.getApplication(id.getNamespaceId(), id.getId());
}
}, apps.get());
List<ProgramSpecification> deletedProgramSpecs = Lists.newArrayList();
if (existing != null) {
ApplicationSpecification existingAppSpec = existing.getSpec();
ImmutableMap<String, ProgramSpecification> existingSpec = new ImmutableMap.Builder<String, ProgramSpecification>()
.putAll(existingAppSpec.getMapReduce())
.putAll(existingAppSpec.getSpark())
.putAll(existingAppSpec.getWorkflows())
.putAll(existingAppSpec.getFlows())
.putAll(existingAppSpec.getServices())
.putAll(existingAppSpec.getWorkers())
.build();
ImmutableMap<String, ProgramSpecification> newSpec = new ImmutableMap.Builder<String, ProgramSpecification>()
.putAll(appSpec.getMapReduce())
.putAll(appSpec.getSpark())
.putAll(appSpec.getWorkflows())
.putAll(appSpec.getFlows())
.putAll(appSpec.getServices())
.putAll(appSpec.getWorkers())
.build();
MapDifference<String, ProgramSpecification> mapDiff = Maps.difference(existingSpec, newSpec);
deletedProgramSpecs.addAll(mapDiff.entriesOnlyOnLeft().values());
}
return deletedProgramSpecs;
}
@Override
public void addStream(final Id.Namespace id, final StreamSpecification streamSpec) {
appsTx.get().executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.writeStream(id.getId(), streamSpec);
return null;
}
}, apps.get());
}
@Override
public StreamSpecification getStream(final Id.Namespace id, final String name) {
return appsTx.get().executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, StreamSpecification>() {
@Override
public StreamSpecification apply(AppMetadataStore mds) throws Exception {
return mds.getStream(id.getId(), name);
}
}, apps.get());
}
@Override
public Collection<StreamSpecification> getAllStreams(final Id.Namespace id) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Collection<StreamSpecification>>() {
@Override
public Collection<StreamSpecification> apply(AppMetadataStore mds) throws Exception {
return mds.getAllStreams(id.getId());
}
}, apps.get());
}
@Override
public FlowSpecification setFlowletInstances(final Id.Program id, final String flowletId, final int count) {
Preconditions.checkArgument(count > 0, "cannot change number of flowlet instances to negative number: " + count);
LOG.trace("Setting flowlet instances: namespace: {}, application: {}, flow: {}, flowlet: {}, " +
"new instances count: {}", id.getNamespaceId(), id.getApplicationId(), id.getId(), flowletId, count);
FlowSpecification flowSpec = appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, FlowSpecification>() {
@Override
public FlowSpecification apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getAppSpecOrFail(mds, id);
ApplicationSpecification newAppSpec = updateFlowletInstancesInAppSpec(appSpec, id, flowletId, count);
replaceAppSpecInProgramJar(id, newAppSpec);
mds.updateAppSpec(id.getNamespaceId(), id.getApplicationId(), newAppSpec);
return appSpec.getFlows().get(id.getId());
}
}, apps.get());
LOG.trace("Set flowlet instances: namespace: {}, application: {}, flow: {}, flowlet: {}, instances now: {}",
id.getNamespaceId(), id.getApplicationId(), id.getId(), flowletId, count);
return flowSpec;
}
@Override
public int getFlowletInstances(final Id.Program id, final String flowletId) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Integer>() {
@Override
public Integer apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getAppSpecOrFail(mds, id);
FlowSpecification flowSpec = getFlowSpecOrFail(id, appSpec);
FlowletDefinition flowletDef = getFlowletDefinitionOrFail(flowSpec, flowletId, id);
return flowletDef.getInstances();
}
}, apps.get());
}
@Override
public void setWorkerInstances(final Id.Program id, final int instances) {
Preconditions.checkArgument(instances > 0, "cannot change number of program " +
"instances to negative number: " + instances);
appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getAppSpecOrFail(mds, id);
WorkerSpecification workerSpec = getWorkerSpecOrFail(id, appSpec);
WorkerSpecification newSpecification = new WorkerSpecification(workerSpec.getClassName(),
workerSpec.getName(),
workerSpec.getDescription(),
workerSpec.getProperties(),
workerSpec.getDatasets(),
workerSpec.getResources(),
instances);
ApplicationSpecification newAppSpec = replaceWorkerInAppSpec(appSpec, id, newSpecification);
replaceAppSpecInProgramJar(id, newAppSpec);
mds.updateAppSpec(id.getNamespaceId(), id.getApplicationId(), newAppSpec);
return null;
}
}, apps.get());
LOG.trace("Setting program instances: namespace: {}, application: {}, worker: {}, new instances count: {}",
id.getNamespaceId(), id.getApplicationId(), id.getId(), instances);
}
@Override
public void setServiceInstances(final Id.Program id, final int instances) {
Preconditions.checkArgument(instances > 0,
"cannot change number of program instances to negative number: %s", instances);
appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getAppSpecOrFail(mds, id);
ServiceSpecification serviceSpec = getServiceSpecOrFail(id, appSpec);
// Create a new spec copy from the old one, except with updated instances number
serviceSpec = new ServiceSpecification(serviceSpec.getClassName(), serviceSpec.getName(),
serviceSpec.getDescription(), serviceSpec.getHandlers(),
serviceSpec.getResources(), instances);
ApplicationSpecification newAppSpec = replaceServiceSpec(appSpec, id.getId(), serviceSpec);
replaceAppSpecInProgramJar(id, newAppSpec);
mds.updateAppSpec(id.getNamespaceId(), id.getApplicationId(), newAppSpec);
return null;
}
}, apps.get());
LOG.trace("Setting program instances: namespace: {}, application: {}, service: {}, new instances count: {}",
id.getNamespaceId(), id.getApplicationId(), id.getId(), instances);
}
@Override
public int getServiceInstances(final Id.Program id) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Integer>() {
@Override
public Integer apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getAppSpecOrFail(mds, id);
ServiceSpecification serviceSpec = getServiceSpecOrFail(id, appSpec);
return serviceSpec.getInstances();
}
}, apps.get());
}
@Override
public int getWorkerInstances(final Id.Program id) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Integer>() {
@Override
public Integer apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getAppSpecOrFail(mds, id);
WorkerSpecification workerSpec = getWorkerSpecOrFail(id, appSpec);
return workerSpec.getInstances();
}
}, apps.get());
}
@Override
public void removeApplication(final Id.Application id) {
LOG.trace("Removing application: namespace: {}, application: {}", id.getNamespaceId(), id.getId());
appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.deleteApplication(id.getNamespaceId(), id.getId());
mds.deleteProgramHistory(id.getNamespaceId(), id.getId());
return null;
}
}, apps.get());
}
@Override
public void removeAllApplications(final Id.Namespace id) {
LOG.trace("Removing all applications of namespace with id: {}", id.getId());
appsTx.get()
.executeUnchecked(new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.deleteApplications(id.getId());
mds.deleteProgramHistory(id.getId());
return null;
}
}, apps.get());
}
@Override
public void removeAll(final Id.Namespace id) {
LOG.trace("Removing all applications of namespace with id: {}", id.getId());
appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.deleteApplications(id.getId());
mds.deleteAllStreams(id.getId());
mds.deleteProgramHistory(id.getId());
return null;
}
}, apps.get());
}
@Override
public Map<String, String> getRuntimeArguments(final Id.Run runId) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Map<String, String>>() {
@Override
public Map<String, String> apply(AppMetadataStore mds) throws Exception {
RunRecordMeta runRecord = mds.getRun(runId.getProgram(), runId.getId());
if (runRecord != null) {
Map<String, String> properties = runRecord.getProperties();
Map<String, String> runtimeArgs = GSON.fromJson(properties.get("runtimeArgs"), STRING_MAP_TYPE);
if (runtimeArgs != null) {
return runtimeArgs;
}
LOG.debug("Runtime arguments for program {}, run {} not found. Returning empty.",
runId.getProgram(), runId.getId());
}
return EMPTY_STRING_MAP;
}
}, apps.get());
}
@Nullable
@Override
public ApplicationSpecification getApplication(final Id.Application id) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, ApplicationSpecification>() {
@Override
public ApplicationSpecification apply(AppMetadataStore mds) throws Exception {
return getApplicationSpec(mds, id);
}
}, apps.get());
}
@Override
public Collection<ApplicationSpecification> getAllApplications(final Id.Namespace id) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Collection<ApplicationSpecification>>() {
@Override
public Collection<ApplicationSpecification> apply(AppMetadataStore mds) throws Exception {
return Lists.transform(
mds.getAllApplications(id.getId()),
new Function<ApplicationMeta, ApplicationSpecification>() {
@Override
public ApplicationSpecification apply(ApplicationMeta input) {
return input.getSpec();
}
});
}
}, apps.get());
}
@Nullable
@Override
public Location getApplicationArchiveLocation(final Id.Application id) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Location>() {
@Override
public Location apply(AppMetadataStore mds) throws Exception {
ApplicationMeta meta = mds.getApplication(id.getNamespaceId(), id.getId());
return meta == null ? null : locationFactory.create(URI.create(meta.getArchiveLocation()));
}
}, apps.get());
}
@Override
public void addSchedule(final Id.Program program, final ScheduleSpecification scheduleSpecification) {
appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getAppSpecOrFail(mds, program);
Map<String, ScheduleSpecification> schedules = Maps.newHashMap(appSpec.getSchedules());
String scheduleName = scheduleSpecification.getSchedule().getName();
Preconditions.checkArgument(!schedules.containsKey(scheduleName), "Schedule with the name '" +
scheduleName + "' already exists.");
schedules.put(scheduleSpecification.getSchedule().getName(), scheduleSpecification);
ApplicationSpecification newAppSpec = new AppSpecificationWithChangedSchedules(appSpec, schedules);
replaceAppSpecInProgramJar(program, newAppSpec);
mds.updateAppSpec(program.getNamespaceId(), program.getApplicationId(), newAppSpec);
return null;
}
}, apps.get());
}
@Override
public void deleteSchedule(final Id.Program program, final String scheduleName) {
appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getAppSpecOrFail(mds, program);
Map<String, ScheduleSpecification> schedules = Maps.newHashMap(appSpec.getSchedules());
ScheduleSpecification removed = schedules.remove(scheduleName);
if (removed == null) {
throw new NoSuchElementException("no such schedule @ account id: " + program.getNamespaceId() +
", app id: " + program.getApplication() +
", program id: " + program.getId() +
", schedule name: " + scheduleName);
}
ApplicationSpecification newAppSpec = new AppSpecificationWithChangedSchedules(appSpec, schedules);
replaceAppSpecInProgramJar(program, newAppSpec);
mds.updateAppSpec(program.getNamespaceId(), program.getApplicationId(), newAppSpec);
return null;
}
}, apps.get());
}
private static class AppSpecificationWithChangedSchedules extends ForwardingApplicationSpecification {
private final Map<String, ScheduleSpecification> newSchedules;
private AppSpecificationWithChangedSchedules(ApplicationSpecification delegate,
Map<String, ScheduleSpecification> newSchedules) {
super(delegate);
this.newSchedules = newSchedules;
}
@Override
public Map<String, ScheduleSpecification> getSchedules() {
return newSchedules;
}
}
@Override
public boolean applicationExists(final Id.Application id) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Boolean>() {
@Override
public Boolean apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getApplicationSpec(mds, id);
return appSpec != null;
}
}, apps.get());
}
@Override
public boolean programExists(final Id.Program id) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Boolean>() {
@Override
public Boolean apply(AppMetadataStore mds) throws Exception {
ApplicationSpecification appSpec = getApplicationSpec(mds, id.getApplication());
return appSpec != null && programExists(id, appSpec);
}
}, apps.get());
}
private boolean programExists(Id.Program id, ApplicationSpecification appSpec) {
switch (id.getType()) {
case FLOW: return appSpec.getFlows().containsKey(id.getId());
case MAPREDUCE: return appSpec.getMapReduce().containsKey(id.getId());
case SERVICE: return appSpec.getServices().containsKey(id.getId());
case SPARK: return appSpec.getSpark().containsKey(id.getId());
case WEBAPP: return false;
case WORKER: return appSpec.getWorkers().containsKey(id.getId());
case WORKFLOW: return appSpec.getWorkflows().containsKey(id.getId());
default: throw new IllegalArgumentException("Unexpected ProgramType " + id.getType());
}
}
@Override
public void updateWorkflowToken(final ProgramRunId workflowRunId, final WorkflowToken token) {
appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.updateWorkflowToken(workflowRunId, token);
return null;
}
}, apps.get());
}
@Override
public WorkflowToken getWorkflowToken(final Id.Workflow workflowId, final String workflowRunId) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, WorkflowToken>() {
@Override
public WorkflowToken apply(AppMetadataStore mds) throws Exception {
return mds.getWorkflowToken(workflowId, workflowRunId);
}
}, apps.get());
}
@Override
public void addWorkflowNodeState(final ProgramRunId workflowRunId, final WorkflowNodeStateDetail nodeStateDetail) {
appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.addWorkflowNodeState(workflowRunId, nodeStateDetail);
return null;
}
}, apps.get());
}
@Override
public List<WorkflowNodeStateDetail> getWorkflowNodeStates(final ProgramRunId workflowRunId) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, List<WorkflowNodeStateDetail>>() {
@Override
public List<WorkflowNodeStateDetail> apply(AppMetadataStore mds) throws Exception {
return mds.getWorkflowNodeStates(workflowRunId);
}
}, apps.get());
}
@VisibleForTesting
void clear() throws Exception {
truncate(dsFramework.getAdmin(APP_META_INSTANCE_ID, null));
truncate(dsFramework.getAdmin(WORKFLOW_STATS_INSTANCE_ID, null));
}
private void truncate(DatasetAdmin admin) throws Exception {
if (admin != null) {
admin.truncate();
}
}
/**
* @return The {@link Location} of the given program.
* @throws RuntimeException if program can't be found.
*/
private Location getProgramLocation(Id.Program id) throws IOException {
String appFabricOutputDir = configuration.get(Constants.AppFabric.OUTPUT_DIR,
System.getProperty("java.io.tmpdir"));
return Programs.programLocation(namespacedLocationFactory, appFabricOutputDir, id);
}
private ApplicationSpecification getApplicationSpec(AppMetadataStore mds, Id.Application id) {
ApplicationMeta meta = mds.getApplication(id.getNamespaceId(), id.getId());
return meta == null ? null : meta.getSpec();
}
private static ApplicationSpecification replaceServiceSpec(ApplicationSpecification appSpec,
String serviceName,
ServiceSpecification serviceSpecification) {
return new ApplicationSpecificationWithChangedServices(appSpec, serviceName, serviceSpecification);
}
private static final class ApplicationSpecificationWithChangedServices extends ForwardingApplicationSpecification {
private final String serviceName;
private final ServiceSpecification serviceSpecification;
private ApplicationSpecificationWithChangedServices(ApplicationSpecification delegate,
String serviceName, ServiceSpecification serviceSpecification) {
super(delegate);
this.serviceName = serviceName;
this.serviceSpecification = serviceSpecification;
}
@Override
public Map<String, ServiceSpecification> getServices() {
Map<String, ServiceSpecification> services = Maps.newHashMap(super.getServices());
services.put(serviceName, serviceSpecification);
return services;
}
}
private void replaceAppSpecInProgramJar(Id.Program id, ApplicationSpecification appSpec) {
try {
Location programLocation = getProgramLocation(id);
ArchiveBundler bundler = new ArchiveBundler(programLocation);
Program program = Programs.create(programLocation);
String className = program.getMainClassName();
Location tmpProgramLocation = programLocation.getTempFile("");
try {
ProgramBundle.create(id, bundler, tmpProgramLocation, className, appSpec);
Location movedTo = tmpProgramLocation.renameTo(programLocation);
if (movedTo == null) {
throw new RuntimeException("Could not replace program jar with the one with updated app spec, " +
"original program file: " + programLocation +
", was trying to replace with file: " + tmpProgramLocation);
}
} finally {
if (tmpProgramLocation != null && tmpProgramLocation.exists()) {
tmpProgramLocation.delete();
}
}
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
private static FlowletDefinition getFlowletDefinitionOrFail(FlowSpecification flowSpec,
String flowletId, Id.Program id) {
FlowletDefinition flowletDef = flowSpec.getFlowlets().get(flowletId);
if (flowletDef == null) {
throw new NoSuchElementException("no such flowlet @ namespace id: " + id.getNamespaceId() +
", app id: " + id.getApplication() +
", flow id: " + id.getId() +
", flowlet id: " + flowletId);
}
return flowletDef;
}
private static FlowSpecification getFlowSpecOrFail(Id.Program id, ApplicationSpecification appSpec) {
FlowSpecification flowSpec = appSpec.getFlows().get(id.getId());
if (flowSpec == null) {
throw new NoSuchElementException("no such flow @ namespace id: " + id.getNamespaceId() +
", app id: " + id.getApplication() +
", flow id: " + id.getId());
}
return flowSpec;
}
private static ServiceSpecification getServiceSpecOrFail(Id.Program id, ApplicationSpecification appSpec) {
ServiceSpecification spec = appSpec.getServices().get(id.getId());
if (spec == null) {
throw new NoSuchElementException("no such service @ namespace id: " + id.getNamespaceId() +
", app id: " + id.getApplication() +
", service id: " + id.getId());
}
return spec;
}
private static WorkerSpecification getWorkerSpecOrFail(Id.Program id, ApplicationSpecification appSpec) {
WorkerSpecification workerSpecification = appSpec.getWorkers().get(id.getId());
if (workerSpecification == null) {
throw new NoSuchElementException("no such worker @ namespace id: " + id.getNamespaceId() +
", app id: " + id.getApplication() +
", worker id: " + id.getId());
}
return workerSpecification;
}
private static ApplicationSpecification updateFlowletInstancesInAppSpec(ApplicationSpecification appSpec,
Id.Program id, String flowletId, int count) {
FlowSpecification flowSpec = getFlowSpecOrFail(id, appSpec);
FlowletDefinition flowletDef = getFlowletDefinitionOrFail(flowSpec, flowletId, id);
final FlowletDefinition adjustedFlowletDef = new FlowletDefinition(flowletDef, count);
return replaceFlowletInAppSpec(appSpec, id, flowSpec, adjustedFlowletDef);
}
private ApplicationSpecification getAppSpecOrFail(AppMetadataStore mds, Id.Program id) {
ApplicationSpecification appSpec = getApplicationSpec(mds, id.getApplication());
if (appSpec == null) {
throw new NoSuchElementException("no such application @ namespace id: " + id.getNamespaceId() +
", app id: " + id.getApplication().getId());
}
return appSpec;
}
private static ApplicationSpecification replaceInAppSpec(final ApplicationSpecification appSpec,
final Id.Program id,
final FlowSpecification flowSpec,
final FlowletDefinition adjustedFlowletDef,
final List<FlowletConnection> connections) {
// as app spec is immutable we have to do this trick
return replaceFlowInAppSpec(appSpec, id,
new FlowSpecificationWithChangedFlowletsAndConnections(flowSpec,
adjustedFlowletDef,
connections));
}
private static class FlowSpecificationWithChangedFlowlets extends ForwardingFlowSpecification {
private final FlowletDefinition adjustedFlowletDef;
private FlowSpecificationWithChangedFlowlets(FlowSpecification delegate,
FlowletDefinition adjustedFlowletDef) {
super(delegate);
this.adjustedFlowletDef = adjustedFlowletDef;
}
@Override
public Map<String, FlowletDefinition> getFlowlets() {
Map<String, FlowletDefinition> flowlets = Maps.newHashMap(super.getFlowlets());
flowlets.put(adjustedFlowletDef.getFlowletSpec().getName(), adjustedFlowletDef);
return flowlets;
}
}
private static final class FlowSpecificationWithChangedFlowletsAndConnections
extends FlowSpecificationWithChangedFlowlets {
private final List<FlowletConnection> connections;
private FlowSpecificationWithChangedFlowletsAndConnections(FlowSpecification delegate,
FlowletDefinition adjustedFlowletDef,
List<FlowletConnection> connections) {
super(delegate, adjustedFlowletDef);
this.connections = connections;
}
@Override
public List<FlowletConnection> getConnections() {
return connections;
}
}
private static ApplicationSpecification replaceFlowletInAppSpec(final ApplicationSpecification appSpec,
final Id.Program id,
final FlowSpecification flowSpec,
final FlowletDefinition adjustedFlowletDef) {
// as app spec is immutable we have to do this trick
return replaceFlowInAppSpec(appSpec, id, new FlowSpecificationWithChangedFlowlets(flowSpec, adjustedFlowletDef));
}
private static ApplicationSpecification replaceFlowInAppSpec(final ApplicationSpecification appSpec,
final Id.Program id,
final FlowSpecification newFlowSpec) {
// as app spec is immutable we have to do this trick
return new ApplicationSpecificationWithChangedFlows(appSpec, id.getId(), newFlowSpec);
}
private static final class ApplicationSpecificationWithChangedFlows extends ForwardingApplicationSpecification {
private final FlowSpecification newFlowSpec;
private final String flowId;
private ApplicationSpecificationWithChangedFlows(ApplicationSpecification delegate,
String flowId, FlowSpecification newFlowSpec) {
super(delegate);
this.newFlowSpec = newFlowSpec;
this.flowId = flowId;
}
@Override
public Map<String, FlowSpecification> getFlows() {
Map<String, FlowSpecification> flows = Maps.newHashMap(super.getFlows());
flows.put(flowId, newFlowSpec);
return flows;
}
}
private static ApplicationSpecification replaceWorkerInAppSpec(final ApplicationSpecification appSpec,
final Id.Program id,
final WorkerSpecification workerSpecification) {
return new ApplicationSpecificationWithChangedWorkers(appSpec, id.getId(), workerSpecification);
}
private static final class ApplicationSpecificationWithChangedWorkers extends ForwardingApplicationSpecification {
private final String workerId;
private final WorkerSpecification workerSpecification;
private ApplicationSpecificationWithChangedWorkers(ApplicationSpecification delegate, String workerId,
WorkerSpecification workerSpec) {
super(delegate);
this.workerId = workerId;
this.workerSpecification = workerSpec;
}
@Override
public Map<String, WorkerSpecification> getWorkers() {
Map<String, WorkerSpecification> workers = Maps.newHashMap(super.getWorkers());
workers.put(workerId, workerSpecification);
return workers;
}
}
public Set<RunId> getRunningInRange(final long startTimeInSecs, final long endTimeInSecs) {
return appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Set<RunId>>() {
@Override
public Set<RunId> apply(AppMetadataStore mds) throws Exception {
return mds.getRunningInRange(startTimeInSecs, endTimeInSecs);
}
}, apps.get());
}
public void upgrade() {
appsTx.get().executeUnchecked(
new TransactionExecutor.Function<AppMetadataStore, Void>() {
@Override
public Void apply(AppMetadataStore mds) throws Exception {
mds.upgradeWorkflowRunRecords();
return null;
}
}, apps.get());
}
}