/*
* Copyright (c) 2008-2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.db.client.impl;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.emc.storageos.db.client.model.uimodels.ExecutionLog;
import com.emc.storageos.db.client.model.uimodels.ExecutionState;
import com.emc.storageos.db.client.model.uimodels.ExecutionTaskLog;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.PasswordHistory;
import com.emc.storageos.db.client.model.ScopedLabel;
import com.emc.storageos.db.client.model.uimodels.Order;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.google.common.collect.Lists;
import com.netflix.astyanax.Keyspace;
import com.netflix.astyanax.connectionpool.OperationResult;
import com.netflix.astyanax.connectionpool.exceptions.ConnectionException;
import com.netflix.astyanax.connectionpool.exceptions.NotFoundException;
import com.netflix.astyanax.cql.CqlStatement;
import com.netflix.astyanax.cql.CqlStatementResult;
import com.netflix.astyanax.model.Column;
import com.netflix.astyanax.model.ColumnFamily;
import com.netflix.astyanax.model.ColumnList;
import com.netflix.astyanax.model.CqlResult;
import com.netflix.astyanax.model.Row;
import com.netflix.astyanax.model.Rows;
import com.netflix.astyanax.query.ColumnFamilyQuery;
import com.netflix.astyanax.query.RowQuery;
import com.netflix.astyanax.serializers.StringSerializer;
import com.netflix.astyanax.util.RangeBuilder;
import com.netflix.astyanax.util.TimeUUIDUtils;
public class DbConsistencyCheckerHelper {
private static final Logger _log = LoggerFactory.getLogger(DbConsistencyCheckerHelper.class);
public static final String MSG_OBJECT_ID_START = "\nStart to check DataObject records id that is illegal.\n";
public static final String MSG_OBJECT_ID_END = "\nFinish to check DataObject records id: totally checked %d data CFs, %d corrupted rows found.\n";
public static final String MSG_OBJECT_ID_END_SPECIFIED = "\nFinish to check DataObject records id for CF %s, %d corrupted rows found.\n";
public static final String MSG_OBJECT_INDICES_START = "\nStart to check DataObject records that the related index is missing.\n";
public static final String MSG_OBJECT_INDICES_END = "Finish to check DataObject records index: totally checked %d data CFs, %d corrupted rows found.\n";
public static final String MSG_OBJECT_INDICES_END_SPECIFIED = "\nFinish to check DataObject records index for CF %s, %d corrupted rows found.\n";
public static final String MSG_INDEX_OBJECTS_START = "\nStart to check INDEX data that the related object records are missing.\n";
public static final String MSG_INDEX_OBJECTS_END = "Finish to check INDEX records: totally checked %d indices and %d corrupted rows found.\n";
public static final String MSG_INDEX_OBJECTS_END_SPECIFIED = "\nFinish to check INDEX records: totally checked %d indices for CF %s and %d corrupted rows found.\n";
private static final String DELETE_INDEX_CQL = "delete from \"%s\" where key='%s' and column1='%s' and column2='%s' and column3='%s' and column4='%s' and column5=%s;";
private static final String DELETE_INDEX_CQL_WITHOUT_UUID = "delete from \"%s\" where key='%s' and column1='%s' and column2='%s' and column3='%s' and column4='%s';";
private static final String DELETE_ORDER_INDEX_CQL = "delete from \"%s\" where key='%s' and column1='%s' and column2=%s and column3='%s' and column4='%s' and column5=%s;";
private static final String DELETE_ORDER_INDEX_CQL_WITHOUT_UUID = "delete from \"%s\" where key='%s' and column1='%s' and column2=%s and column3='%s' and column4='%s';";
private static final String CQL_QUERY_SCHEMA_VERSION_TIMESTAMP = "SELECT key, writetime(value) FROM \"SchemaRecord\";";
private static final int INDEX_OBJECTS_BATCH_SIZE = 1000;
private static final int THREAD_POOL_QUEUE_SIZE = 50;
private static final int WAITING_TIME_FOR_QUEUE_FULL_MS = 3000;
private DbClientImpl dbClient;
private Set<Class<? extends DataObject>> excludeClasses = new HashSet<Class<? extends DataObject>>(Arrays.asList(PasswordHistory.class));
private final Set<String> skipCheckCFs = new HashSet<>(Arrays.asList(ExecutionState.class.getSimpleName(), ExecutionLog.class.getSimpleName(), ExecutionTaskLog.class.getSimpleName()));
private volatile Map<Long, String> schemaVersionsTime;
private BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(THREAD_POOL_QUEUE_SIZE);
private ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 50, TimeUnit.MILLISECONDS, blockingQueue);
private boolean doubleConfirmed = true;
public DbConsistencyCheckerHelper() {
}
public DbConsistencyCheckerHelper(DbClientImpl dbClient) {
this.dbClient = dbClient;
initSchemaVersions();
}
/**
* Find out all rows in DataObject CFs that can't be deserialized,
* such as such as object id cannot be converted to URI.
*
* @return number of corrupted rows
*/
public int checkDataObject(DataObjectType doType, boolean toConsole) {
int dirtyCount = 0;
Class<? extends DataObject> dataObjectClass = doType.getDataObjectClass();
_log.info("Check CF {}", dataObjectClass.getName());
if (excludeClasses.contains(dataObjectClass)) {
_log.info("Skip CF {} since its URI is special", dataObjectClass);
return 0;
}
try {
OperationResult<Rows<String, CompositeColumnName>> result = dbClient.getKeyspace(
dataObjectClass).prepareQuery(doType.getCF())
.getAllRows().setRowLimit(dbClient.DEFAULT_PAGE_SIZE)
.withColumnRange(new RangeBuilder().setLimit(1).build())
.execute();
for (Row<String, CompositeColumnName> row : result.getResult()) {
try {
if (!isValidDataObjectKey(URI.create(row.getKey()), dataObjectClass)) {
dirtyCount++;
logMessage(String.format("Inconsistency found: Row key '%s' failed to convert to URI in CF %s",
row.getKey(), dataObjectClass.getName()), true, toConsole);
}
} catch (Exception ex) {
dirtyCount++;
logMessage(String.format("Inconsistency found: Row key '%s' failed to convert to URI in CF %s with exception %s",
row.getKey(), dataObjectClass.getName(),
ex.getMessage()), true, toConsole);
}
}
} catch (ConnectionException e) {
throw DatabaseException.retryables.connectionFailed(e);
}
return dirtyCount;
}
/**
* Scan all the data object records, to find out the object record is existing but the related index is missing.
*
* @param doType
* @param toConsole whether print out in the console
* @return the number of corrupted data
* @throws ConnectionException
*/
public void checkCFIndices(DataObjectType doType, boolean toConsole, CheckResult checkResult) throws ConnectionException {
initSchemaVersions();
Class objClass = doType.getDataObjectClass();
if (skipCheckCFs.contains(objClass.getSimpleName())) {
_log.info("Skip checking CF {}", objClass);
return;
} else {
_log.info("Check Data Object CF {} with double confirmed option: {}", objClass, doubleConfirmed);
}
Map<String, ColumnField> indexedFields = new HashMap<String, ColumnField>();
for (ColumnField field : doType.getColumnFields()) {
if (field.getIndex() != null) {
indexedFields.put(field.getName(), field);
}
}
if (indexedFields.isEmpty()) {
return;
}
Keyspace keyspace = dbClient.getKeyspace(objClass);
ColumnFamilyQuery<String, CompositeColumnName> query = keyspace.prepareQuery(doType.getCF());
OperationResult<Rows<String, CompositeColumnName>> result = query.getAllRows().setRowLimit(dbClient.DEFAULT_PAGE_SIZE).execute();
for (Row<String, CompositeColumnName> objRow : result.getResult()) {
boolean inactiveObject = false;
for (Column<CompositeColumnName> column : objRow.getColumns()) {
if (column.getName().getOne().equals(DataObject.INACTIVE_FIELD_NAME) && column.getBooleanValue()) {
inactiveObject = true;
break;
}
}
if (inactiveObject) {
continue;
}
for (Column<CompositeColumnName> column : objRow.getColumns()) {
if (!indexedFields.containsKey(column.getName().getOne())) {
continue;
}
// we don't build index if the value is null, refer to ColumnField.
if (!column.hasValue()) {
continue;
}
ColumnField indexedField = indexedFields.get(column.getName().getOne());
String indexKey = getIndexKey(indexedField, column, objRow);
if (indexKey == null) {
continue;
}
boolean isColumnInIndex = isColumnInIndex(keyspace, indexedField.getIndexCF(), indexKey,
getIndexColumns(indexedField, column, objRow.getKey()));
if (!isColumnInIndex) {
if (doubleConfirmed && isDataObjectRemoved(doType.getDataObjectClass(), objRow.getKey())) {
continue;
}
String dbVersion = findDataCreatedInWhichDBVersion(column.getName().getTimeUUID());
checkResult.increaseByVersion(dbVersion);
logMessage(String.format(
"Inconsistency found Object(%s, id: %s, field: %s) is existing, but the related Index(%s, type: %s, id: %s) is missing. This entry is updated by version %s",
indexedField.getDataObjectType().getSimpleName(), objRow.getKey(), indexedField.getName(),
indexedField.getIndexCF().getName(), indexedField.getIndex().getClass().getSimpleName(), indexKey, dbVersion),
true, toConsole);
DbCheckerFileWriter.writeTo(DbCheckerFileWriter.WRITER_REBUILD_INDEX,
String.format("id:%s, cfName:%s", objRow.getKey(),
indexedField.getDataObjectType().getSimpleName()));
}
}
}
}
public void checkIndexingCF(IndexAndCf indexAndCf, boolean toConsole, CheckResult checkResult) throws ConnectionException {
checkIndexingCF(indexAndCf, toConsole, checkResult, false);
}
/**
* Scan all the indices and related data object records, to find out
* the index record is existing but the related data object records is missing.
*
* @return number of the corrupted rows in this index CF
* @throws ConnectionException
*/
public void checkIndexingCF(IndexAndCf indexAndCf, boolean toConsole, CheckResult checkResult, boolean isParallel) throws ConnectionException {
initSchemaVersions();
String indexCFName = indexAndCf.cf.getName();
Map<String, ColumnFamily<String, CompositeColumnName>> objCfs = getDataObjectCFs();
_log.info("Start checking the index CF {} with double confirmed option: {}", indexCFName, doubleConfirmed);
Map<ColumnFamily<String, CompositeColumnName>, Map<String, List<IndexEntry>>> objsToCheck = new HashMap<>();
ColumnFamilyQuery<String, IndexColumnName> query = indexAndCf.keyspace
.prepareQuery(indexAndCf.cf);
OperationResult<Rows<String, IndexColumnName>> result = query.getAllRows()
.setRowLimit(dbClient.DEFAULT_PAGE_SIZE)
.withColumnRange(new RangeBuilder().setLimit(0).build())
.execute();
for (Row<String, IndexColumnName> row : result.getResult()) {
RowQuery<String, IndexColumnName> rowQuery = indexAndCf.keyspace.prepareQuery(indexAndCf.cf).getKey(row.getKey())
.autoPaginate(true)
.withColumnRange(new RangeBuilder().setLimit(dbClient.DEFAULT_PAGE_SIZE).build());
ColumnList<IndexColumnName> columns;
while (!(columns = rowQuery.execute().getResult()).isEmpty()) {
for (Column<IndexColumnName> column : columns) {
ObjectEntry objEntry = extractObjectEntryFromIndex(row.getKey(),
column.getName(), indexAndCf.indexType, toConsole);
if (objEntry == null) {
continue;
}
ColumnFamily<String, CompositeColumnName> objCf = objCfs
.get(objEntry.getClassName());
if (objCf == null) {
logMessage(String.format("DataObject does not exist for %s", row.getKey()), true, toConsole);
continue;
}
if (skipCheckCFs.contains(objCf.getName())) {
_log.debug("Skip checking CF {} for index CF {}", objCf.getName(), indexAndCf.cf.getName());
continue;
}
Map<String, List<IndexEntry>> objKeysIdxEntryMap = objsToCheck.get(objCf);
if (objKeysIdxEntryMap == null) {
objKeysIdxEntryMap = new HashMap<>();
objsToCheck.put(objCf, objKeysIdxEntryMap);
}
List<IndexEntry> idxEntries = objKeysIdxEntryMap.get(objEntry.getObjectId());
if (idxEntries == null) {
idxEntries = new ArrayList<>();
objKeysIdxEntryMap.put(objEntry.getObjectId(), idxEntries);
}
idxEntries.add(new IndexEntry(row.getKey(), column.getName()));
}
int size = getObjsSize(objsToCheck);
if (size >= INDEX_OBJECTS_BATCH_SIZE ) {
if (isParallel) {
processBatchIndexObjectsWithMultipleThreads(indexAndCf, toConsole, objsToCheck, checkResult);
} else {
processBatchIndexObjects(indexAndCf, toConsole, objsToCheck, checkResult);
}
objsToCheck = new HashMap<>();
}
}
}
// Detect whether the DataObject CFs have the records
if (isParallel) {
processBatchIndexObjectsWithMultipleThreads(indexAndCf, toConsole, objsToCheck, checkResult);
} else {
processBatchIndexObjects(indexAndCf, toConsole, objsToCheck, checkResult);
}
}
private int getObjsSize(Map<ColumnFamily<String, CompositeColumnName>, Map<String, List<IndexEntry>>> objsToCheck) {
int size = 0;
for (Map<String, List<IndexEntry>> objMap : objsToCheck.values()) {
for (List<IndexEntry> objs : objMap.values()) {
size += objs.size();
}
}
return size;
}
public void waitForCheckIndexFinihsed(int waitTimeInSeconds) {
executor.shutdown();
try {
if (!executor.awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (Exception e) {
executor.shutdownNow();
}
}
private void processBatchIndexObjects(IndexAndCf indexAndCf, boolean toConsole,
Map<ColumnFamily<String, CompositeColumnName>, Map<String, List<IndexEntry>>> objsToCheck, CheckResult checkResult) throws ConnectionException {
for (ColumnFamily<String, CompositeColumnName> objCf : objsToCheck.keySet()) {
Map<String, List<IndexEntry>> objKeysIdxEntryMap = objsToCheck.get(objCf);
_log.info("query {} data object from CF {} for index CF {}", objKeysIdxEntryMap.keySet().size(), objCf.getName(), indexAndCf.cf.getName());
OperationResult<Rows<String, CompositeColumnName>> objResult = indexAndCf.keyspace
.prepareQuery(objCf).getRowSlice(objKeysIdxEntryMap.keySet())
.execute();
for (Row<String, CompositeColumnName> row : objResult.getResult()) {
Set<UUID> existingDataColumnUUIDSet = new HashSet<>();
for (Column<CompositeColumnName> column : row.getColumns()) {
if (column.getName().getTimeUUID() != null) {
existingDataColumnUUIDSet.add(column.getName().getTimeUUID());
}
}
List<IndexEntry> idxEntries = objKeysIdxEntryMap.get(row.getKey());
for (IndexEntry idxEntry : idxEntries) {
if (row.getColumns().isEmpty()
|| (idxEntry.getColumnName().getTimeUUID() != null && !existingDataColumnUUIDSet.contains(idxEntry
.getColumnName().getTimeUUID()))) {
//double confirm it is inconsistent data, please see issue COP-27749
if (doubleConfirmed && !isIndexExists(indexAndCf.keyspace, indexAndCf.cf, idxEntry.getIndexKey(), idxEntry.getColumnName())) {
continue;
}
String dbVersion = findDataCreatedInWhichDBVersion(idxEntry.getColumnName().getTimeUUID());
checkResult.increaseByVersion(dbVersion);
if (row.getColumns().isEmpty()) {
logMessage(String.format("Inconsistency found: Index(%s, type: %s, id: %s, column: %s) is existing "
+ "but the related object record(%s, id: %s) is missing. This entry is updated by version %s",
indexAndCf.cf.getName(), indexAndCf.indexType.getSimpleName(),
idxEntry.getIndexKey(), idxEntry.getColumnName(),
objCf.getName(), row.getKey(), dbVersion), true, toConsole);
} else {
logMessage(String.format("Inconsistency found: Index(%s, type: %s, id: %s, column: %s) is existing, "
+ "but the related object record(%s, id: %s) has not data column can match this index. This entry is updated by version %s",
indexAndCf.cf.getName(), indexAndCf.indexType.getSimpleName(),
idxEntry.getIndexKey(), idxEntry.getColumnName(),
objCf.getName(), row.getKey(), dbVersion), true, toConsole);
}
UUID timeUUID = idxEntry.getColumnName().getTimeUUID();
DbCheckerFileWriter.writeTo(indexAndCf.keyspace.getKeyspaceName(),
generateCleanIndexCQL(indexAndCf, idxEntry, timeUUID, idxEntry.getColumnName()));
}
}
}
}
}
protected String generateCleanIndexCQL(IndexAndCf indexAndCf, IndexEntry idxEntry, UUID timeUUID, CompositeIndexColumnName compositeIndexColumnName) {
if (compositeIndexColumnName instanceof ClassNameTimeSeriesIndexColumnName ||
compositeIndexColumnName instanceof TimeSeriesIndexColumnName) {
return String.format(timeUUID != null ? DELETE_ORDER_INDEX_CQL : DELETE_ORDER_INDEX_CQL_WITHOUT_UUID,
indexAndCf.cf.getName(), idxEntry.getIndexKey(), idxEntry.getColumnName().getOne(),
handleNullValue(idxEntry.getColumnName().getTwo()),
handleNullValue(idxEntry.getColumnName().getThree()),
handleNullValue(idxEntry.getColumnName().getFour()),
timeUUID);
} else {
return String.format(timeUUID != null ? DELETE_INDEX_CQL : DELETE_INDEX_CQL_WITHOUT_UUID,
indexAndCf.cf.getName(), idxEntry.getIndexKey(), idxEntry.getColumnName().getOne(),
handleNullValue(idxEntry.getColumnName().getTwo()),
handleNullValue(idxEntry.getColumnName().getThree()),
handleNullValue(idxEntry.getColumnName().getFour()),
timeUUID);
}
}
/*
* We need to process index objects in batch to avoid occupy too many memory
* */
private void processBatchIndexObjectsWithMultipleThreads(IndexAndCf indexAndCf, boolean toConsole,
Map<ColumnFamily<String, CompositeColumnName>, Map<String, List<IndexEntry>>> objsToCheck, CheckResult checkResult) throws ConnectionException {
//if waiting queue is full, wait a few seconds to avoid reject exception
while (executor.getQueue().size() >= THREAD_POOL_QUEUE_SIZE) {
try {
Thread.sleep(WAITING_TIME_FOR_QUEUE_FULL_MS);
} catch (InterruptedException e) {
//ignore
}
}
executor.submit(new Runnable() {
@Override
public void run() {
try {
processBatchIndexObjects(indexAndCf, toConsole, objsToCheck, checkResult);
} catch (ConnectionException e) {
_log.error("failed to check index:", e);
}
}
});
}
public Map<String, IndexAndCf> getAllIndices() {
// Map<Index_CF_Name, <DbIndex, ColumnFamily, Map<Class_Name, object-CF_Name>>>
Map<String, IndexAndCf> allIdxCfs = new TreeMap<>();
for (DataObjectType objType : TypeMap.getAllDoTypes()) {
Map<String, IndexAndCf> idxCfs = getIndicesOfCF(objType);
allIdxCfs.putAll(idxCfs);
}
return allIdxCfs;
}
public Map<String, IndexAndCf> getIndicesOfCF(DataObjectType objType) {
Map<String, IndexAndCf> idxCfs = new TreeMap<>();
Keyspace keyspace = dbClient.getKeyspace(objType.getDataObjectClass());
for (ColumnField field : objType.getColumnFields()) {
DbIndex index = field.getIndex();
if (index == null) {
continue;
}
IndexAndCf indexAndCf = new IndexAndCf(index.getClass(), field.getIndexCF(), keyspace);
String key = indexAndCf.generateKey();
IndexAndCf idxAndCf = idxCfs.get(key);
if (idxAndCf == null) {
idxAndCf = new IndexAndCf(index.getClass(), field.getIndexCF(), keyspace);
idxCfs.put(key, idxAndCf);
}
}
return idxCfs;
}
public Map<String, ColumnFamily<String, CompositeColumnName>> getDataObjectCFs() {
Map<String, ColumnFamily<String, CompositeColumnName>> objCfs = new TreeMap<>();
for (DataObjectType objType : TypeMap.getAllDoTypes()) {
String simpleClassName = objType.getDataObjectClass().getSimpleName();
ColumnFamily<String, CompositeColumnName> objCf = objCfs.get(simpleClassName);
if (objCf == null) {
objCfs.put(simpleClassName, objType.getCF());
}
}
return objCfs;
}
void logMessage(String msg, boolean isError, boolean toConsole) {
if (StringUtils.isEmpty(msg)) {
return;
}
if (isError) {
_log.error(msg);
if (toConsole) {
System.err.println(msg);
}
return;
}
_log.info(msg);
if (toConsole) {
System.out.println(msg);
}
}
/*
* This class records the Index Data's ColumnFamily and
* the related DbIndex type and it belongs to which Keyspace.
*/
public static class IndexAndCf<T extends CompositeIndexColumnName> implements Comparable {
private ColumnFamily<String, T> cf;
private Class<? extends DbIndex> indexType;
private Keyspace keyspace;
public IndexAndCf(Class<? extends DbIndex> indexType,
ColumnFamily<String, T> cf, Keyspace keyspace) {
this.indexType = indexType;
this.cf = cf;
this.keyspace = keyspace;
}
@Override
public String toString() {
return generateKey();
}
String generateKey() {
StringBuffer buffer = new StringBuffer();
buffer.append(keyspace.getKeyspaceName()).append("/")
.append(indexType.getSimpleName()).append("/")
.append(cf.getName());
return buffer.toString();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof IndexAndCf)) {
return false;
}
if (this == obj) {
return true;
}
IndexAndCf that = (IndexAndCf) obj;
if (cf != null ? !cf.equals(that.cf) : that.cf != null) {
return false;
}
if (indexType != null ? !indexType.equals(that.indexType)
: that.indexType != null) {
return false;
}
if (keyspace != null ? !keyspace.equals(that.keyspace)
: that.keyspace != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = cf != null ? cf.hashCode() : 0;
result = 31 * result + (indexType != null ? indexType.hashCode() : 0);
result = 31 * result + (keyspace != null ? keyspace.hashCode() : 0);
return result;
}
@Override
public int compareTo(Object object) {
IndexAndCf other = (IndexAndCf) object;
return this.generateKey().compareTo(other.generateKey());
}
}
public class IndexEntry {
private String indexKey;
private CompositeIndexColumnName columnName;
public IndexEntry(String indexKey, CompositeIndexColumnName columnName) {
this.indexKey = indexKey;
this.columnName = columnName;
}
public String getIndexKey() {
return indexKey;
}
public CompositeIndexColumnName getColumnName() {
return columnName;
}
}
private ObjectEntry extractObjectEntryFromIndex(String indexKey,
CompositeIndexColumnName name, Class<? extends DbIndex> type, boolean toConsole) {
// The className of a data object CF in a index record
String className;
// The id of the data object record in a index record
String objectId;
if (type.equals(AltIdDbIndex.class)) {
objectId = name.getTwo();
className = name.getOne();
} else if (type.equals(RelationDbIndex.class)) {
objectId = name.getTwo();
className = name.getOne();
} else if (type.equals(NamedRelationDbIndex.class)) {
objectId = name.getFour();
className = name.getOne();
} else if (type.equals(DecommissionedDbIndex.class)) {
objectId = name.getTwo();
className = indexKey;
} else if (type.equals(PermissionsDbIndex.class)) {
objectId = name.getTwo();
className = name.getOne();
} else if (type.equals(PrefixDbIndex.class)) {
objectId = name.getFour();
className = name.getOne();
} else if (type.equals(ScopedLabelDbIndex.class)) {
objectId = name.getFour();
className = name.getOne();
} else if (type.equals(AggregateDbIndex.class)) {
objectId = name.getTwo();
int firstColon = indexKey.indexOf(':');
className = firstColon == -1 ? indexKey : indexKey.substring(0, firstColon);
} else if (type.equals(ClassNameTimeSeriesDBIndex.class)) {
objectId = name.getThree();
className = name.getOne();
} else if (type.equals(TimeSeriesDbIndex.class)) {
objectId = name.getThree();
className = name.getOne();
} else {
String msg = String.format("Unsupported index type %s.", type);
logMessage(msg, false, toConsole);
return null;
}
return new ObjectEntry(className, objectId);
}
public static class ObjectEntry {
private String className;
private String objectId;
public ObjectEntry(String className, String objectId) {
this.className = className;
this.objectId = objectId;
}
public String getClassName() {
return className;
}
public String getObjectId() {
return objectId;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("ObjectEntry ClassName: ").append(className).append(" ObjectId: ").append(objectId);
return buffer.toString();
}
}
private boolean isColumnInIndex(Keyspace ks, ColumnFamily<String, CompositeIndexColumnName> indexCf, String indexKey, Object[] indexColumns)
throws ConnectionException {
StringBuilder cql = new StringBuilder("select * from ");
cql.append("\"").append(indexCf.getName())
.append("\" where key='").append(indexKey).append("' ");
for (int i = 0; i < indexColumns.length; i++) {
cql.append(" and column").append(i + 1).append("=");
if (indexColumns[i] instanceof String) {
cql.append("\'").append(indexColumns[i]).append("'");
} else {
cql.append(indexColumns[i]);
}
}
CqlStatement statement = ks.prepareCqlStatement();
CqlStatementResult result = statement.withCql(cql.toString()).execute().getResult();
Rows<String, CompositeIndexColumnName> rows = result.getRows(indexCf);
return !rows.isEmpty();
}
public <T extends CompositeIndexColumnName> boolean isIndexExists(Keyspace ks, ColumnFamily<String, T> indexCf, String indexKey, T column) throws ConnectionException {
try {
ks.prepareQuery(indexCf).getKey(indexKey)
.getColumn(column)
.execute().getResult();
return true;
} catch (NotFoundException e) {
return false;
}
}
public static String getIndexKey(ColumnField field, Column<CompositeColumnName> column, Row<String, CompositeColumnName> objRow) {
String indexKey = null;
DbIndex dbIndex = field.getIndex();
boolean indexByKey = field.isIndexByKey();
if (dbIndex instanceof AltIdDbIndex) {
indexKey = indexByKey ? column.getName().getTwo() : column.getStringValue();
} else if (dbIndex instanceof RelationDbIndex) {
indexKey = indexByKey ? column.getName().getTwo() : column.getStringValue();
} else if (dbIndex instanceof NamedRelationDbIndex) {
indexKey = NamedURI.fromString(column.getStringValue()).getURI().toString();
} else if (dbIndex instanceof DecommissionedDbIndex) {
indexKey = field.getDataObjectType().getSimpleName();
} else if (dbIndex instanceof PermissionsDbIndex) {
indexKey = column.getName().getTwo();
} else if (dbIndex instanceof PrefixDbIndex) {
indexKey = field.getPrefixIndexRowKey(column.getStringValue());
} else if (dbIndex instanceof ScopedLabelDbIndex) {
indexKey = field.getPrefixIndexRowKey(ScopedLabel.fromString(column.getStringValue()));
} else if (dbIndex instanceof ClassNameTimeSeriesDBIndex) {
indexKey = column.getStringValue();
} else if (dbIndex instanceof TimeSeriesDbIndex) {
if (field.getDataObjectType().equals(Order.class)) {
Order order = new Order();
DataObjectType doType = TypeMap.getDoType(Order.class);
doType.deserializeColumns(order, objRow, Lists.newArrayList(doType.getColumnField("tenant")), true);
indexKey = order.getTenant();
}
} else if (dbIndex instanceof AggregateDbIndex) {
// Not support this index type yet.
} else {
String msg = String.format("Unsupported index type %s.", dbIndex.getClass());
_log.warn(msg);
}
return indexKey;
}
public static Object[] getIndexColumns(ColumnField field, Column<CompositeColumnName> column, String rowKey) {
Object[] indexColumns = null;
DbIndex dbIndex = field.getIndex();
if (dbIndex instanceof AggregateDbIndex) {
// Not support this index type yet.
return indexColumns;
}
if (dbIndex instanceof NamedRelationDbIndex) {
indexColumns = new String[4];
indexColumns[0] = field.getDataObjectType().getSimpleName();
NamedURI namedURI = NamedURI.fromString(column.getStringValue());
String name = namedURI.getName();
indexColumns[1] = name.toLowerCase();
indexColumns[2] = name;
indexColumns[3] = rowKey;
} else if (dbIndex instanceof PrefixDbIndex) {
indexColumns = new String[4];
indexColumns[0] = field.getDataObjectType().getSimpleName();
indexColumns[1] = column.getStringValue().toLowerCase();
indexColumns[2] = column.getStringValue();
indexColumns[3] = rowKey;
} else if (dbIndex instanceof ScopedLabelDbIndex) {
indexColumns = new String[4];
indexColumns[0] = field.getDataObjectType().getSimpleName();
ScopedLabel label = ScopedLabel.fromString(column.getStringValue());
indexColumns[1] = label.getLabel().toLowerCase();
indexColumns[2] = label.getLabel();
indexColumns[3] = rowKey;
} else if (dbIndex instanceof DecommissionedDbIndex) {
indexColumns = new String[2];
Boolean val = column.getBooleanValue();
indexColumns[0] = val.toString();
indexColumns[1] = rowKey;
} else if (dbIndex instanceof ClassNameTimeSeriesDBIndex || dbIndex instanceof TimeSeriesDbIndex) {
indexColumns = new Object[3];
indexColumns[0] = field.getDataObjectType().getSimpleName();
indexColumns[1] = TimeUUIDUtils.getMicrosTimeFromUUID(column.getName().getTimeUUID());
indexColumns[2] = rowKey;
} else {
// For AltIdDbIndex, RelationDbIndex, PermissionsDbIndex
indexColumns = new String[2];
indexColumns[0] = field.getDataObjectType().getSimpleName();
indexColumns[1] = rowKey;
}
return indexColumns;
}
private String handleNullValue(String columnValue) {
return columnValue == null ? "" : columnValue;
}
public DbClientImpl getDbClient() {
return dbClient;
}
public void setDbClient(DbClientImpl dbClient) {
this.dbClient = dbClient;
}
protected boolean isDataObjectRemoved(Class<? extends DataObject> clazz, String key) {
DataObject dataObject = dbClient.queryObject(URI.create(key));
return dataObject == null || dataObject.getInactive();
}
private boolean isValidDataObjectKey(URI uri, final Class<? extends DataObject> type) {
return uri != null && URIUtil.isValid(uri) && URIUtil.isType(uri, type);
}
protected void initSchemaVersions() {
if (schemaVersionsTime == null) {
schemaVersionsTime = querySchemaVersions();
}
}
protected Map<Long, String> querySchemaVersions() {
Map<Long, String> result = new TreeMap<Long, String>();
ColumnFamily<String, String> CF_STANDARD1 =
new ColumnFamily<String, String>("SchemaRecord",
StringSerializer.get(), StringSerializer.get(), StringSerializer.get());
try {
OperationResult<CqlResult<String, String>> queryResult = dbClient.getLocalContext().getKeyspace().prepareQuery(CF_STANDARD1)
.withCql(CQL_QUERY_SCHEMA_VERSION_TIMESTAMP)
.execute();
for (Row<String, String> row : queryResult.getResult().getRows()) {
result.put(row.getColumns().getColumnByIndex(1).getLongValue(), row.getColumns().getColumnByIndex(0).getStringValue());
}
} catch (ConnectionException e) {
_log.error("Failed to query schema versions", e);
}
return result;
}
public String findDataCreatedInWhichDBVersion(UUID timeUUID) {
long createTime = 0;
try {
createTime = TimeUUIDUtils.getMicrosTimeFromUUID(timeUUID);
} catch (Exception e) {
//ignore
}
return findDataCreatedInWhichDBVersion(createTime);
}
public String findDataCreatedInWhichDBVersion(long createTime) {
//small data set, no need to binary search
long selectKey = 0;
for (Entry<Long, String> entry : schemaVersionsTime.entrySet()) {
if (createTime >= entry.getKey()) {
selectKey = entry.getKey();
}
}
return selectKey == 0 ? "Unknown" : schemaVersionsTime.get(selectKey);
}
public ThreadPoolExecutor getExecutor() {
return executor;
}
public void setDoubleConfirmed(boolean doubleConfirmed) {
this.doubleConfirmed = doubleConfirmed;
}
public static class CheckResult {
//The number of the corrupted rows
private AtomicInteger total = new AtomicInteger();
private Map<String, Integer> countOfVersion = Collections.synchronizedMap(new TreeMap<String, Integer>());
public int getTotal() {
return total.get();
}
public Map<String, Integer> getCountOfVersion() {
return countOfVersion;
}
public void increaseByVersion(String version) {
if (!countOfVersion.containsKey(version)) {
countOfVersion.put(version, 0);
}
countOfVersion.put(version, countOfVersion.get(version) + 1);
this.total.getAndIncrement();
}
@Override
public String toString() {
if (0 == getTotal()) {
return "\nNo corrupted rows found.";
}
StringBuilder builder = new StringBuilder();
builder.append("\nCorrupted rows by version: ");
int index = 1;
int max = countOfVersion.size();
for (Entry<String, Integer> entry : countOfVersion.entrySet()) {
builder.append(entry.getKey()).append("(").append(entry.getValue()).append(")");
if (index++ < max) {
builder.append(", ");
}
}
return builder.toString();
}
}
}