/*
* Copyright © 2015 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.logging.gateway.handlers.store;
import co.cask.cdap.api.dataset.table.Table;
import co.cask.cdap.common.app.RunIds;
import co.cask.cdap.data2.dataset2.lib.table.MDSKey;
import co.cask.cdap.data2.dataset2.lib.table.MetadataStoreDataset;
import co.cask.cdap.internal.app.store.RunRecordMeta;
import co.cask.cdap.proto.Id;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Duplicate store class for application meatadata.
* JIRA https://issues.cask.co/browse/CDAP-2172
*/
public class AppMetadataStore extends MetadataStoreDataset {
public static final String TYPE_RUN_RECORD_STARTED = "runRecordStarted";
public static final String TYPE_RUN_RECORD_COMPLETED = "runRecordCompleted";
public static final String TYPE_RUN_RECORD_SUSPENDED = "runRecordSuspended";
public AppMetadataStore(Table table) {
super(table);
}
// TODO: getRun is duplicated from cdap-app-fabric AppMetadataStore class.
// Any changes made here will have to be made over there too.
// JIRA https://issues.cask.co/browse/CDAP-2172
public RunRecordMeta getRun(Id.Program program, final String runid) {
// Query active run record first
RunRecordMeta running = getUnfinishedRun(program, TYPE_RUN_RECORD_STARTED, runid);
// If program is running, this will be non-null
if (running != null) {
return running;
}
// If program is not running, query completed run records
RunRecordMeta complete = getCompletedRun(program, runid);
if (complete != null) {
return complete;
}
// Else query suspended run records
return getUnfinishedRun(program, TYPE_RUN_RECORD_SUSPENDED, runid);
}
/**
* @return run records for runs that do not have start time in mds key for the run record.
*/
private RunRecordMeta getUnfinishedRun(Id.Program program, String recordType, String runid) {
MDSKey runningKey = new MDSKey.Builder()
.add(recordType)
.add(program.getNamespaceId())
.add(program.getApplicationId())
.add(program.getType().name())
.add(program.getId())
.add(runid)
.build();
return get(runningKey, RunRecordMeta.class);
}
private RunRecordMeta getCompletedRun(Id.Program program, final String runid) {
MDSKey completedKey = new MDSKey.Builder()
.add(TYPE_RUN_RECORD_COMPLETED)
.add(program.getNamespaceId())
.add(program.getApplicationId())
.add(program.getType().name())
.add(program.getId())
.build();
// Get start time from RunId
long programStartSecs = RunIds.getTime(RunIds.fromString(runid), TimeUnit.SECONDS);
if (programStartSecs > -1) {
// If start time is found, run a get
MDSKey key = new MDSKey.Builder(completedKey)
.add(getInvertedTsKeyPart(programStartSecs))
.add(runid)
.build();
return get(key, RunRecordMeta.class);
} else {
// If start time is not found, scan the table (backwards compatibility when run ids were random UUIDs)
MDSKey startKey = new MDSKey.Builder(completedKey).add(getInvertedTsScanKeyPart(Long.MAX_VALUE)).build();
MDSKey stopKey = new MDSKey.Builder(completedKey).add(getInvertedTsScanKeyPart(0)).build();
List<RunRecordMeta> runRecords =
list(startKey, stopKey, RunRecordMeta.class, 1, // Should have only one record for this runid
new Predicate<RunRecordMeta>() {
@Override
public boolean apply(RunRecordMeta input) {
return input.getPid().equals(runid);
}
});
return Iterables.getFirst(runRecords, null);
}
}
private long getInvertedTsKeyPart(long endTime) {
return Long.MAX_VALUE - endTime;
}
/**
* Returns inverted scan key for given time. The scan key needs to be adjusted to maintain the property that
* start key is inclusive and end key is exclusive on a scan. Since when you invert start key, it becomes end key and
* vice-versa.
*/
private long getInvertedTsScanKeyPart(long time) {
long invertedTsKey = getInvertedTsKeyPart(time);
return invertedTsKey < Long.MAX_VALUE ? invertedTsKey + 1 : invertedTsKey;
}
}