/*
* Copyright 2013 Eediom 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 org.araqne.logdb.query.command;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.araqne.logdb.QueryCommand;
import org.araqne.logdb.QueryTask;
import org.araqne.logdb.Row;
import org.araqne.logstorage.Crypto;
import org.araqne.logstorage.LogFileServiceRegistry;
import org.araqne.logstorage.LogStorage;
import org.araqne.logstorage.LogTableRegistry;
import org.araqne.logstorage.TableSchema;
import org.araqne.logstorage.file.LogBlock;
import org.araqne.logstorage.file.LogBlockCursor;
import org.araqne.logstorage.file.LogFileReader;
import org.araqne.logstorage.file.LogFileServiceV2;
import org.araqne.storage.api.FilePath;
import org.araqne.storage.crypto.LogCryptoException;
import org.araqne.storage.crypto.LogCryptoService;
import org.araqne.storage.crypto.MacBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CheckTable extends QueryCommand {
private final Logger logger = LoggerFactory.getLogger(CheckTable.class);
private IntegrityCheckTask mainTask = new IntegrityCheckTask();
private Set<String> tableNames;
private Date from;
private Date to;
private boolean trace;
// for toString query generation
private String tableToken;
private LogTableRegistry tableRegistry;
private LogStorage storage;
private LogFileServiceRegistry fileServiceRegistry;
private LogCryptoService cryptoService;
public CheckTable(Set<String> tableNames, Date from, Date to, boolean trace, String tableToken,
LogTableRegistry tableRegistry, LogStorage storage, LogFileServiceRegistry fileSerivceRegistry, LogCryptoService cryptoService) {
this.tableNames = tableNames;
this.from = from;
this.to = to;
this.trace = trace;
this.tableToken = tableToken;
this.tableRegistry = tableRegistry;
this.storage = storage;
this.fileServiceRegistry = fileSerivceRegistry;
this.cryptoService = cryptoService;
}
@Override
public String getName() {
return "checktable";
}
@Override
public QueryTask getMainTask() {
return mainTask;
}
public Set<String> getTableNames() {
return tableNames;
}
public Date getFrom() {
return from;
}
public Date getTo() {
return to;
}
@Override
public void onPush(Row m) {
}
@Override
public boolean isReducer() {
return false;
}
private void checkTable(String tableName) {
TableSchema schema = tableRegistry.getTableSchema(tableName, true);
Map<String, String> metadata = schema.getMetadata();
String type = schema.getPrimaryStorage().getType();
FilePath dir = storage.getTableDirectory(tableName);
for (Date day : storage.getLogDates(tableName)) {
if (getStatus() == Status.End)
break;
if (from != null && day.before(from))
continue;
if (to != null && day.after(to))
continue;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String dateText = dateFormat.format(day);
FilePath indexPath = dir.newFilePath(dateText + ".idx");
FilePath dataPath = dir.newFilePath(dateText + ".dat");
FilePath keyPath = dir.newFilePath(dateText + ".key");
if (!keyPath.exists())
continue;
LogFileReader reader = null;
LogBlockCursor cursor = null;
int lastValidBlockId = 0;
try {
reader = fileServiceRegistry.newReader(tableName, type, new LogFileServiceV2.Option(schema.getPrimaryStorage(),
metadata, tableName, dir, indexPath, dataPath, keyPath));
cursor = reader.getBlockCursor();
while (cursor.hasNext() && getStatus() != Status.End) {
LogBlock lb = cursor.next();
Map<String, Object> data = lb.getData();
Map<String, Object> m = new HashMap<String, Object>();
byte[] d = (byte[]) data.get("data");
byte[] signature = (byte[]) data.get("signature");
if (d != null && signature == null)
continue;
String digest = (String) data.get("digest");
byte[] digestKey = (byte[]) data.get("digest_key");
MacBuilder mb = cryptoService.newMacBuilder(digest, digestKey);
byte[] hash = null;
try {
if (d != null)
hash = mb.digest(d, 0, d.length);
} catch (Exception e) {
}
lastValidBlockId = (Integer) data.get("block_id");
boolean valid = hash != null && Arrays.equals(hash, signature);
if (valid && !trace)
continue;
m.put("table", tableName);
m.put("day", day);
m.put("block_id", data.get("block_id"));
m.put("signature", signature);
m.put("hash", hash);
if (d == null)
m.put("msg", "corrupted");
else
m.put("msg", valid ? "valid" : "modified");
pushPipe(new Row(m));
}
} catch (IOException e) {
Map<String, Object> m = new HashMap<String, Object>();
m.put("table", tableName);
m.put("day", day);
m.put("last_block_id", lastValidBlockId);
m.put("msg", "corrupted");
pushPipe(new Row(m));
logger.trace("araqne logdb: cannot read block metadata", e);
} catch (LogCryptoException e) {
Map<String, Object> m = new HashMap<String, Object>();
m.put("table", tableName);
m.put("day", day);
m.put("last_block_id", lastValidBlockId);
m.put("msg", "corrupted");
pushPipe(new Row(m));
logger.trace("araqne logdb: cannot calculate block digest", e);
} finally {
if (cursor != null) {
try {
cursor.close();
} catch (IOException e) {
}
}
if (reader != null) {
reader.close();
}
}
}
}
@Override
public String toString() {
String fromOption = "";
String toOption = "";
String traceOption = "";
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
if (from != null)
fromOption = " from=" + df.format(from);
if (to != null)
toOption = " to=" + df.format(to);
if (trace)
traceOption = " trace=t";
String tables = tableToken;
if (!tables.isEmpty())
tables = " " + tables;
return "checktable" + fromOption + toOption + traceOption + tables;
}
private class IntegrityCheckTask extends QueryTask {
@Override
public void run() {
try {
for (String tableName : tableNames) {
if (getStatus() == TaskStatus.CANCELED)
break;
try {
checkTable(tableName);
} catch (UnsupportedOperationException e) {
// ignore unsupported reader
}
}
} catch (Throwable t) {
logger.error("araqne logdb: table error", t);
}
}
}
}