/* * Copyright (c) 2008-2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.db.client.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.CancellationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.coordinator.client.model.Constants; import com.emc.storageos.coordinator.client.model.DbConsistencyStatus; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.db.client.impl.DbConsistencyCheckerHelper.CheckResult; import com.emc.storageos.db.client.impl.DbConsistencyCheckerHelper.IndexAndCf; import com.netflix.astyanax.connectionpool.exceptions.ConnectionException; public class DbConsistencyChecker { private static final Logger log = LoggerFactory.getLogger(DbConsistencyChecker.class); private CoordinatorClient coordinator; private DbConsistencyCheckerHelper helper; private int totalCount; // Print out the result to console directly and don't save the status in ZK, used such as triggered by dbutils private boolean toConsole; public DbConsistencyChecker() { } public DbConsistencyChecker(DbConsistencyCheckerHelper helper, boolean toConsole) { this.helper = helper; this.toConsole = toConsole; } public int check() throws ConnectionException { init(); int corruptedCount = 0; CheckType checkType = getCheckTypeFromZK(); log.info("db consistency check type:{}", checkType); switch (checkType) { case OBJECT_ID: corruptedCount += checkObjectId(); setNextCheckType(); case OBJECT_INDICES: corruptedCount += checkObjectIndices(); setNextCheckType(); case INDEX_OBJECTS: corruptedCount += checkIndexObjects(); } return corruptedCount; } public void persistStatus(DbConsistencyStatus status) { this.coordinator.persistRuntimeState(Constants.DB_CONSISTENCY_STATUS, status); } public DbConsistencyStatus getStatusFromZk() { if (toConsole) { return new DbConsistencyStatus(); } return this.coordinator.queryRuntimeState(Constants.DB_CONSISTENCY_STATUS, DbConsistencyStatus.class); } private void init() { if (toConsole) { return; } int cfCount = TypeMap.getAllDoTypes().size(); int indexCount = helper.getAllIndices().values().size(); this.totalCount = indexCount + cfCount * 2; log.info(String.format("cfCount=%d indexCount=%d totalCount=%d", cfCount, indexCount, this.totalCount)); } private void setNextCheckType() { if (toConsole) { return; } CheckType checkType = getCheckTypeFromZK(); if(!checkType.hasNext()){ return; } DbConsistencyStatus status = getStatusFromZk(); status.update(checkType.getNext().name(), null); persistStatus(status); } /** * 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 the corrupted rows in data CFs */ private int checkObjectId() { CheckType checkType = CheckType.OBJECT_ID; helper.logMessage(DbConsistencyCheckerHelper.MSG_OBJECT_ID_START, false, toConsole); DbConsistencyStatus status = getStatusFromZk(); Collection<DataObjectType> resumeDataCfs = resumeFromWorkingPoint(checkType, status.getWorkingPoint()); int totalIllegalCount = 0; for (DataObjectType dataCf : resumeDataCfs) { int illegalCount = helper.checkDataObject(dataCf, toConsole); status = getStatusFromZk(); if (!toConsole && isCancelled(status)) { cancel(status); } if (!toConsole) { status.update(this.totalCount, checkType.name(), dataCf.getCF().getName(), illegalCount); persistStatus(status); } totalIllegalCount += illegalCount; } String msg = String.format(DbConsistencyCheckerHelper.MSG_OBJECT_ID_END, resumeDataCfs.size(), totalIllegalCount); helper.logMessage(msg, false, toConsole); return totalIllegalCount; } /** * Scan all the data object records, to find out the object record is existing * but the related index is missing. * * @return The number of corrupted data * @throws ConnectionException */ private int checkObjectIndices() throws ConnectionException { CheckType checkType = CheckType.OBJECT_INDICES; helper.logMessage(DbConsistencyCheckerHelper.MSG_OBJECT_INDICES_START, false, toConsole); DbConsistencyStatus status = getStatusFromZk(); Collection<DataObjectType> resumeDataCfs = resumeFromWorkingPoint(checkType, status.getWorkingPoint()); CheckResult checkResult = new CheckResult(); for (DataObjectType dataCf : resumeDataCfs) { helper.checkCFIndices(dataCf, toConsole, checkResult); status = getStatusFromZk(); if (!toConsole && isCancelled(status)) { cancel(status); } if (!toConsole) { status.update(this.totalCount, checkType.name(), dataCf.getCF().getName(), checkResult.getTotal()); persistStatus(status); } } DbCheckerFileWriter.close(); String msg = String.format(DbConsistencyCheckerHelper.MSG_OBJECT_INDICES_END, resumeDataCfs.size(), checkResult.getTotal()); helper.logMessage(checkResult.toString(), false, toConsole); helper.logMessage(msg, false, toConsole); return checkResult.getTotal(); } /** * 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 the number of the corrupted rows in the index CFs * @throws ConnectionException */ private int checkIndexObjects() throws ConnectionException { CheckType checkType = CheckType.INDEX_OBJECTS; helper.logMessage(DbConsistencyCheckerHelper.MSG_INDEX_OBJECTS_START, false, toConsole); DbConsistencyStatus status = getStatusFromZk(); Collection<IndexAndCf> resumeIdxCfs = resumeFromWorkingPoint(checkType, status.getWorkingPoint()); CheckResult checkResult = new CheckResult(); for (IndexAndCf indexAndCf : resumeIdxCfs) { helper.checkIndexingCF(indexAndCf, toConsole, checkResult); status = getStatusFromZk(); if (!toConsole && isCancelled(status)) { cancel(status); } if (!toConsole) { status.update(this.totalCount, checkType.name(), indexAndCf.generateKey(), checkResult.getTotal()); persistStatus(status); } } DbCheckerFileWriter.close(); String msg = String.format(DbConsistencyCheckerHelper.MSG_INDEX_OBJECTS_END, resumeIdxCfs.size(), checkResult.getTotal()); helper.logMessage(checkResult.toString(), false, toConsole); helper.logMessage(msg, false, toConsole); return checkResult.getTotal(); } private Collection resumeFromWorkingPoint(CheckType checkType, String workingPoint) { Collection sortedCfs; if (checkType == CheckType.INDEX_OBJECTS) { Collection<IndexAndCf> idxCfs = helper.getAllIndices().values(); sortedCfs = sortIndexCfs(idxCfs); } else { // Currently, other cases are related to DataObjectType Collection<DataObjectType> allDoTypes = TypeMap.getAllDoTypes(); sortedCfs = sortDataObjectCfs(allDoTypes); } if (toConsole || workingPoint == null) { return sortedCfs; } boolean found = false; List resumeCfs = new ArrayList<>(); for (Object cfEntry : sortedCfs) { String cfWorkingPoint; if (checkType == CheckType.INDEX_OBJECTS) { IndexAndCf idxCf = (IndexAndCf) cfEntry; cfWorkingPoint = idxCf.generateKey(); } else { DataObjectType dataCf = (DataObjectType) cfEntry; cfWorkingPoint = dataCf.getCF().getName(); } if (workingPoint.equals(cfWorkingPoint)) { found = true; } if (found) { resumeCfs.add(cfEntry); } } return found ? resumeCfs : sortedCfs; } private Collection<DataObjectType> sortDataObjectCfs(Collection<DataObjectType> allDoTypes) { List<DataObjectType> types = new ArrayList<DataObjectType>(allDoTypes); Collections.sort(types, new Comparator<DataObjectType>() { @Override public int compare(DataObjectType type, DataObjectType anotherType) { return type.getCF().getName().compareTo(anotherType.getCF().getName()); } }); return Collections.unmodifiableCollection(types); } @SuppressWarnings("unchecked") private Collection<IndexAndCf> sortIndexCfs(Collection<IndexAndCf> idxCfs) { List<IndexAndCf> list = new ArrayList<IndexAndCf>(idxCfs); Collections.sort(list); return Collections.unmodifiableCollection(list); } private void cancel(DbConsistencyStatus status) { helper.logMessage("db consistency check is canceled", false, false); throw new CancellationException("db consistency has been cancelled"); } private boolean isCancelled(DbConsistencyStatus status) { return status.isCancelled(); } private enum CheckType { OBJECT_ID("OBJECT_INDICES"), OBJECT_INDICES("INDEX_OBJECTS"), INDEX_OBJECTS(null); private String next; CheckType(String next) { this.next = next; } public CheckType getNext() { return valueOf(next); } public boolean hasNext() { return this.next != null; } } private CheckType getCheckTypeFromZK() { CheckType defaultType = CheckType.OBJECT_ID; if (toConsole) { return defaultType; } DbConsistencyStatus status = getStatusFromZk(); helper.logMessage(String.format("status %s in zk", status.toString()), false, false); CheckType checkType; try { checkType = CheckType.valueOf(status.getCheckType()); } catch (Exception e) { checkType = defaultType; } return checkType; } public CoordinatorClient getCoordinator() { return coordinator; } public void setCoordinator(CoordinatorClient coordinator) { this.coordinator = coordinator; } public boolean isToConsole() { return toConsole; } public void setToConsole(boolean toConsole) { this.toConsole = toConsole; } public DbConsistencyCheckerHelper getHelper() { return helper; } public void setHelper(DbConsistencyCheckerHelper helper) { this.helper = helper; } }