HDFS-11698. Ozone: SCM: CLI: Add Debug command. Contributed by Chen Liang.
This commit is contained in:
parent
a28557ac03
commit
d9be9a8c30
|
@ -217,6 +217,11 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<artifactId>jctools-core</artifactId>
|
<artifactId>jctools-core</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xerial</groupId>
|
||||||
|
<artifactId>sqlite-jdbc</artifactId>
|
||||||
|
<version>3.8.7</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -0,0 +1,288 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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.apache.hadoop.ozone.scm.cli;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.BasicParser;
|
||||||
|
import org.apache.commons.cli.CommandLine;
|
||||||
|
import org.apache.commons.cli.Option;
|
||||||
|
import org.apache.commons.cli.OptionBuilder;
|
||||||
|
import org.apache.commons.cli.Options;
|
||||||
|
import org.apache.commons.cli.ParseException;
|
||||||
|
import org.apache.hadoop.conf.Configured;
|
||||||
|
import org.apache.hadoop.hdfs.ozone.protocol.proto.ContainerProtos.Pipeline;
|
||||||
|
import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos;
|
||||||
|
import org.apache.hadoop.util.Tool;
|
||||||
|
import org.apache.hadoop.util.ToolRunner;
|
||||||
|
import org.apache.hadoop.utils.LevelDBStore;
|
||||||
|
import org.iq80.leveldb.DBIterator;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.ozone.OzoneConsts.CONTAINER_DB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the CLI that can be use to convert a levelDB into a sqlite DB file.
|
||||||
|
*/
|
||||||
|
public class SQLCLI extends Configured implements Tool {
|
||||||
|
|
||||||
|
private Options options;
|
||||||
|
private BasicParser parser;
|
||||||
|
private final Charset encoding = Charset.forName("UTF-8");
|
||||||
|
|
||||||
|
// for container.db
|
||||||
|
private static final String CREATE_CONTAINER_INFO =
|
||||||
|
"CREATE TABLE containerInfo (" +
|
||||||
|
"containerName TEXT PRIMARY KEY NOT NULL , " +
|
||||||
|
"leaderUUID TEXT NOT NULL)";
|
||||||
|
private static final String CREATE_CONTAINER_MACHINE =
|
||||||
|
"CREATE TABLE containerMembers (" +
|
||||||
|
"containerName INTEGER NOT NULL, " +
|
||||||
|
"datanodeUUID INTEGER NOT NULL," +
|
||||||
|
"PRIMARY KEY(containerName, datanodeUUID));";
|
||||||
|
private static final String CREATE_DATANODE_INFO =
|
||||||
|
"CREATE TABLE datanodeInfo (" +
|
||||||
|
"hostName TEXT NOT NULL, " +
|
||||||
|
"datanodeUUId TEXT PRIMARY KEY NOT NULL," +
|
||||||
|
"ipAddr TEXT, " +
|
||||||
|
"xferPort INTEGER," +
|
||||||
|
"infoPort INTEGER," +
|
||||||
|
"ipcPort INTEGER," +
|
||||||
|
"infoSecurePort INTEGER," +
|
||||||
|
"containerPort INTEGER NOT NULL);";
|
||||||
|
private static final String INSERT_CONTAINER_INFO =
|
||||||
|
"INSERT INTO containerInfo (containerName, leaderUUID) " +
|
||||||
|
"VALUES (\"%s\", \"%s\")";
|
||||||
|
private static final String INSERT_DATANODE_INFO =
|
||||||
|
"INSERT INTO datanodeInfo (hostname, datanodeUUid, ipAddr, xferPort, " +
|
||||||
|
"infoPort, ipcPort, infoSecurePort, containerPort) " +
|
||||||
|
"VALUES (\"%s\", \"%s\", \"%s\", %d, %d, %d, %d, %d)";
|
||||||
|
private static final String INSERT_CONTAINER_MEMBERS =
|
||||||
|
"INSERT INTO containerMembers (containerName, datanodeUUID) " +
|
||||||
|
"VALUES (\"%s\", \"%s\")";
|
||||||
|
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
LoggerFactory.getLogger(SQLCLI.class);
|
||||||
|
|
||||||
|
public SQLCLI() {
|
||||||
|
this.options = getOptions();
|
||||||
|
this.parser = new BasicParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("static-access")
|
||||||
|
private Options getOptions() {
|
||||||
|
Options allOptions = new Options();
|
||||||
|
Option dbPathOption = OptionBuilder
|
||||||
|
.withArgName("levelDB path")
|
||||||
|
.withLongOpt("dbPath")
|
||||||
|
.hasArgs(1)
|
||||||
|
.withDescription("specify levelDB path")
|
||||||
|
.create("p");
|
||||||
|
allOptions.addOption(dbPathOption);
|
||||||
|
|
||||||
|
Option outPathOption = OptionBuilder
|
||||||
|
.withArgName("output path")
|
||||||
|
.withLongOpt("outPath")
|
||||||
|
.hasArgs(1)
|
||||||
|
.withDescription("specify output path")
|
||||||
|
.create("o");
|
||||||
|
allOptions.addOption(outPathOption);
|
||||||
|
return allOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int run(String[] args) throws Exception {
|
||||||
|
CommandLine commandLine = parseArgs(args);
|
||||||
|
if (!commandLine.hasOption("p") || !commandLine.hasOption("o")) {
|
||||||
|
LOG.error("Require dbPath option(-p) AND outPath option (-o)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
String value = commandLine.getOptionValue("p");
|
||||||
|
LOG.info("levelDB path {}", value);
|
||||||
|
// the value is supposed to be an absolute path to a container file
|
||||||
|
Path dbPath = Paths.get(value);
|
||||||
|
if (!Files.exists(dbPath)) {
|
||||||
|
LOG.error("DB path not exist:{}", dbPath);
|
||||||
|
}
|
||||||
|
Path parentPath = dbPath.getParent();
|
||||||
|
Path dbName = dbPath.getFileName();
|
||||||
|
if (parentPath == null || dbName == null) {
|
||||||
|
LOG.error("Error processing db path {}", dbPath);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = commandLine.getOptionValue("o");
|
||||||
|
Path outPath = Paths.get(value);
|
||||||
|
if (outPath == null || outPath.getParent() == null) {
|
||||||
|
LOG.error("Error processing output path {}", outPath);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!Files.exists(outPath.getParent())) {
|
||||||
|
Files.createDirectories(outPath.getParent());
|
||||||
|
}
|
||||||
|
LOG.info("Parent path [{}] db name [{}]", parentPath, dbName);
|
||||||
|
if (dbName.toString().equals(CONTAINER_DB)) {
|
||||||
|
LOG.info("Converting container DB");
|
||||||
|
convertContainerDB(dbPath, outPath);
|
||||||
|
} else {
|
||||||
|
LOG.error("Unrecognized db name {}", dbName);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Connection connectDB(String dbPath) throws Exception {
|
||||||
|
Class.forName("org.sqlite.JDBC");
|
||||||
|
String connectPath =
|
||||||
|
String.format("jdbc:sqlite:%s", dbPath);
|
||||||
|
return DriverManager.getConnection(connectPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeDB(Connection conn) throws SQLException {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeSQL(Connection conn, String sql) throws SQLException {
|
||||||
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.executeUpdate(sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert container.db to sqlite. The schema of sql db:
|
||||||
|
* three tables, containerId, containerMachines, datanodeInfo
|
||||||
|
* (* for primary key)
|
||||||
|
*
|
||||||
|
* containerInfo:
|
||||||
|
* ----------------------------------------------
|
||||||
|
* container name* | container lead datanode uuid
|
||||||
|
* ----------------------------------------------
|
||||||
|
*
|
||||||
|
* containerMembers:
|
||||||
|
* --------------------------------
|
||||||
|
* container name* | datanodeUUid*
|
||||||
|
* --------------------------------
|
||||||
|
*
|
||||||
|
* datanodeInfo:
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
* hostname | datanodeUUid* | xferPort | infoPort | ipcPort
|
||||||
|
* ---------------------------------------------------------
|
||||||
|
*
|
||||||
|
* --------------------------------
|
||||||
|
* | infoSecurePort | containerPort
|
||||||
|
* --------------------------------
|
||||||
|
*
|
||||||
|
* @param dbPath path to container db.
|
||||||
|
* @throws IOException throws exception.
|
||||||
|
*/
|
||||||
|
private void convertContainerDB(Path dbPath, Path outPath)
|
||||||
|
throws Exception {
|
||||||
|
LOG.info("Create tables for sql container db.");
|
||||||
|
File dbFile = dbPath.toFile();
|
||||||
|
org.iq80.leveldb.Options dbOptions = new org.iq80.leveldb.Options();
|
||||||
|
LevelDBStore dbStore = new LevelDBStore(dbFile, dbOptions);
|
||||||
|
|
||||||
|
Connection conn = connectDB(outPath.toString());
|
||||||
|
executeSQL(conn, CREATE_CONTAINER_INFO);
|
||||||
|
executeSQL(conn, CREATE_CONTAINER_MACHINE);
|
||||||
|
executeSQL(conn, CREATE_DATANODE_INFO);
|
||||||
|
|
||||||
|
DBIterator iter = dbStore.getIterator();
|
||||||
|
iter.seekToFirst();
|
||||||
|
HashSet<String> uuidChecked = new HashSet<>();
|
||||||
|
while(iter.hasNext()) {
|
||||||
|
Map.Entry<byte[], byte[]> entry = iter.next();
|
||||||
|
String containerName = new String(entry.getKey(), encoding);
|
||||||
|
Pipeline pipeline = Pipeline.parseFrom(entry.getValue());
|
||||||
|
insertContainerDB(conn, containerName, pipeline, uuidChecked);
|
||||||
|
}
|
||||||
|
closeDB(conn);
|
||||||
|
dbStore.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert into the sqlite DB of container.db.
|
||||||
|
* @param conn the connection to the sqlite DB.
|
||||||
|
* @param containerName the name of the container.
|
||||||
|
* @param pipeline the actual container pipeline object.
|
||||||
|
* @param uuidChecked the uuid that has been already inserted.
|
||||||
|
* @throws SQLException throws exception.
|
||||||
|
*/
|
||||||
|
private void insertContainerDB(Connection conn, String containerName,
|
||||||
|
Pipeline pipeline, Set<String> uuidChecked) throws SQLException {
|
||||||
|
LOG.info("Insert to sql container db, for container {}", containerName);
|
||||||
|
String insertContainerInfo = String.format(
|
||||||
|
INSERT_CONTAINER_INFO, containerName, pipeline.getLeaderID());
|
||||||
|
executeSQL(conn, insertContainerInfo);
|
||||||
|
|
||||||
|
for (HdfsProtos.DatanodeIDProto dnID : pipeline.getMembersList()) {
|
||||||
|
String uuid = dnID.getDatanodeUuid();
|
||||||
|
if (!uuidChecked.contains(uuid)) {
|
||||||
|
// we may also not use this checked set, but catch exception instead
|
||||||
|
// but this seems a bit cleaner.
|
||||||
|
String ipAddr = dnID.getIpAddr();
|
||||||
|
String hostName = dnID.getHostName();
|
||||||
|
int xferPort = dnID.hasXferPort() ? dnID.getXferPort() : 0;
|
||||||
|
int infoPort = dnID.hasInfoPort() ? dnID.getInfoPort() : 0;
|
||||||
|
int securePort =
|
||||||
|
dnID.hasInfoSecurePort() ? dnID.getInfoSecurePort() : 0;
|
||||||
|
int ipcPort = dnID.hasIpcPort() ? dnID.getIpcPort() : 0;
|
||||||
|
int containerPort = dnID.getContainerPort();
|
||||||
|
String insertMachineInfo = String.format(
|
||||||
|
INSERT_DATANODE_INFO, hostName, uuid, ipAddr, xferPort, infoPort,
|
||||||
|
ipcPort, securePort, containerPort);
|
||||||
|
executeSQL(conn, insertMachineInfo);
|
||||||
|
uuidChecked.add(uuid);
|
||||||
|
}
|
||||||
|
String insertContainerMembers = String.format(
|
||||||
|
INSERT_CONTAINER_MEMBERS, containerName, uuid);
|
||||||
|
executeSQL(conn, insertContainerMembers);
|
||||||
|
}
|
||||||
|
LOG.info("Insertion completed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandLine parseArgs(String[] argv)
|
||||||
|
throws ParseException {
|
||||||
|
return parser.parse(options, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Tool shell = new SQLCLI();
|
||||||
|
int res = 0;
|
||||||
|
try {
|
||||||
|
ToolRunner.run(shell, args);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error(ex.toString());
|
||||||
|
res = 1;
|
||||||
|
}
|
||||||
|
System.exit(res);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you 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.apache.hadoop.ozone.scm;
|
||||||
|
|
||||||
|
import org.apache.hadoop.io.IOUtils;
|
||||||
|
import org.apache.hadoop.ozone.MiniOzoneCluster;
|
||||||
|
import org.apache.hadoop.ozone.OzoneConfigKeys;
|
||||||
|
import org.apache.hadoop.ozone.OzoneConfiguration;
|
||||||
|
import org.apache.hadoop.ozone.OzoneConsts;
|
||||||
|
import org.apache.hadoop.ozone.scm.cli.SQLCLI;
|
||||||
|
import org.apache.hadoop.scm.protocolPB.StorageContainerLocationProtocolClientSideTranslatorPB;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.ozone.OzoneConsts.CONTAINER_DB;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class tests the CLI that transforms container into SQLite DB files.
|
||||||
|
*/
|
||||||
|
public class TestContainerSQLCli {
|
||||||
|
private static SQLCLI cli;
|
||||||
|
|
||||||
|
private static MiniOzoneCluster cluster;
|
||||||
|
private static OzoneConfiguration conf;
|
||||||
|
private static StorageContainerLocationProtocolClientSideTranslatorPB
|
||||||
|
storageContainerLocationClient;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void init() throws Exception {
|
||||||
|
long datanodeCapacities = 3 * OzoneConsts.TB;
|
||||||
|
conf = new OzoneConfiguration();
|
||||||
|
cluster = new MiniOzoneCluster.Builder(conf).numDataNodes(1)
|
||||||
|
.storageCapacities(new long[] {datanodeCapacities, datanodeCapacities})
|
||||||
|
.setHandlerType("distributed").build();
|
||||||
|
storageContainerLocationClient =
|
||||||
|
cluster.createStorageContainerLocationClient();
|
||||||
|
cluster.waitForHeartbeatProcessed();
|
||||||
|
|
||||||
|
// create two containers to be retrieved later.
|
||||||
|
storageContainerLocationClient.allocateContainer(
|
||||||
|
"container0");
|
||||||
|
storageContainerLocationClient.allocateContainer(
|
||||||
|
"container1");
|
||||||
|
|
||||||
|
cluster.shutdown();
|
||||||
|
cli = new SQLCLI();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void shutdown() throws InterruptedException {
|
||||||
|
IOUtils.cleanup(null, storageContainerLocationClient, cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConvertContainerDB() throws Exception {
|
||||||
|
String dbOutPath = cluster.getDataDirectory() + "/out_sql.db";
|
||||||
|
// TODO : the following will fail due to empty Datanode list, need to fix.
|
||||||
|
//String dnUUID = cluster.getDataNodes().get(0).getDatanodeUuid();
|
||||||
|
String dbRootPath = conf.get(OzoneConfigKeys.OZONE_CONTAINER_METADATA_DIRS);
|
||||||
|
String dbPath = dbRootPath + "/" + CONTAINER_DB;
|
||||||
|
String[] args = {"-p", dbPath, "-o", dbOutPath};
|
||||||
|
Connection conn;
|
||||||
|
String sql;
|
||||||
|
ResultSet rs;
|
||||||
|
|
||||||
|
cli.run(args);
|
||||||
|
|
||||||
|
//verify the sqlite db
|
||||||
|
// only checks the container names are as expected. Because other fields
|
||||||
|
// such as datanode UUID are generated randomly each time
|
||||||
|
conn = connectDB(dbOutPath);
|
||||||
|
sql = "SELECT * FROM containerInfo";
|
||||||
|
rs = executeQuery(conn, sql);
|
||||||
|
ArrayList<String> containerNames = new ArrayList<>();
|
||||||
|
while (rs.next()) {
|
||||||
|
containerNames.add(rs.getString("containerName"));
|
||||||
|
//assertEquals(dnUUID, rs.getString("leaderUUID"));
|
||||||
|
}
|
||||||
|
assertTrue(containerNames.size() == 2 &&
|
||||||
|
containerNames.contains("container0") &&
|
||||||
|
containerNames.contains("container1"));
|
||||||
|
|
||||||
|
sql = "SELECT * FROM containerMembers";
|
||||||
|
rs = executeQuery(conn, sql);
|
||||||
|
containerNames = new ArrayList<>();
|
||||||
|
while (rs.next()) {
|
||||||
|
containerNames.add(rs.getString("containerName"));
|
||||||
|
//assertEquals(dnUUID, rs.getString("datanodeUUID"));
|
||||||
|
}
|
||||||
|
assertTrue(containerNames.size() == 2 &&
|
||||||
|
containerNames.contains("container0") &&
|
||||||
|
containerNames.contains("container1"));
|
||||||
|
|
||||||
|
sql = "SELECT * FROM datanodeInfo";
|
||||||
|
rs = executeQuery(conn, sql);
|
||||||
|
int count = 0;
|
||||||
|
while (rs.next()) {
|
||||||
|
assertEquals("127.0.0.1", rs.getString("ipAddr"));
|
||||||
|
//assertEquals(dnUUID, rs.getString("datanodeUUID"));
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
assertEquals(1, count);
|
||||||
|
Files.delete(Paths.get(dbOutPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResultSet executeQuery(Connection conn, String sql)
|
||||||
|
throws SQLException {
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
return stmt.executeQuery(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Connection connectDB(String dbPath) throws Exception {
|
||||||
|
Class.forName("org.sqlite.JDBC");
|
||||||
|
String connectPath =
|
||||||
|
String.format("jdbc:sqlite:%s", dbPath);
|
||||||
|
return DriverManager.getConnection(connectPath);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue