You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by sh...@apache.org on 2018/08/26 00:44:46 UTC
[34/50] [abbrv] hadoop git commit: HDDS-356. Support ColumnFamily
based RockDBStore and TableStore. Contributed by Anu Engineer.
HDDS-356. Support ColumnFamily based RockDBStore and TableStore.
Contributed by Anu Engineer.
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/b021249a
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/b021249a
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/b021249a
Branch: refs/heads/HDFS-12943
Commit: b021249ac84abe31c9d30d73ed483bea2acdbaab
Parents: af4b705
Author: Anu Engineer <ae...@apache.org>
Authored: Wed Aug 22 18:55:14 2018 -0700
Committer: Anu Engineer <ae...@apache.org>
Committed: Wed Aug 22 18:55:14 2018 -0700
----------------------------------------------------------------------
.../org/apache/hadoop/utils/db/DBStore.java | 93 +++++++
.../org/apache/hadoop/utils/db/RDBStore.java | 252 +++++++++++++++++++
.../hadoop/utils/db/RDBStoreIterator.java | 88 +++++++
.../org/apache/hadoop/utils/db/RDBTable.java | 173 +++++++++++++
.../java/org/apache/hadoop/utils/db/Table.java | 150 +++++++++++
.../apache/hadoop/utils/db/TableIterator.java | 50 ++++
.../apache/hadoop/utils/db/package-info.java | 22 ++
.../apache/hadoop/utils/db/TestRDBStore.java | 246 ++++++++++++++++++
.../hadoop/utils/db/TestRDBTableStore.java | 189 ++++++++++++++
.../apache/hadoop/utils/db/package-info.java | 22 ++
10 files changed, 1285 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBStore.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBStore.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBStore.java
new file mode 100644
index 0000000..a817f4f
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/DBStore.java
@@ -0,0 +1,93 @@
+/*
+ * 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.utils.db;
+
+import org.apache.hadoop.classification.InterfaceStability;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * The DBStore interface provides the ability to create Tables, which store
+ * a specific type of Key-Value pair. Some DB interfaces like LevelDB will not
+ * be able to do this. In those case a Table creation will map to a default
+ * store.
+ *
+ */
+@InterfaceStability.Evolving
+public interface DBStore extends AutoCloseable {
+
+ /**
+ * Gets an existing TableStore.
+ *
+ * @param name - Name of the TableStore to get
+ * @return - TableStore.
+ * @throws IOException on Failure
+ */
+ Table getTable(String name) throws IOException;
+
+ /**
+ * Lists the Known list of Tables in a DB.
+ *
+ * @return List of Tables, in case of Rocks DB and LevelDB we will return at
+ * least one entry called DEFAULT.
+ * @throws IOException on Failure
+ */
+ ArrayList<Table> listTables() throws IOException;
+
+ /**
+ * Compact the entire database.
+ *
+ * @throws IOException on Failure
+ */
+ void compactDB() throws IOException;
+
+ /**
+ * Moves a key from the Source Table to the destination Table.
+ *
+ * @param key - Key to move.
+ * @param source - Source Table.
+ * @param dest - Destination Table.
+ * @throws IOException on Failure
+ */
+ void move(byte[] key, Table source, Table dest) throws IOException;
+
+ /**
+ * Moves a key from the Source Table to the destination Table and updates the
+ * destination to the new value.
+ *
+ * @param key - Key to move.
+ * @param value - new value to write to the destination table.
+ * @param source - Source Table.
+ * @param dest - Destination Table.
+ * @throws IOException on Failure
+ */
+ void move(byte[] key, byte[] value, Table source, Table dest)
+ throws IOException;
+
+ /**
+ * Returns an estimated count of keys in this DB.
+ *
+ * @return long, estimate of keys in the DB.
+ */
+ long getEstimatedKeyCount() throws IOException;
+
+
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStore.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStore.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStore.java
new file mode 100644
index 0000000..c719d31
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStore.java
@@ -0,0 +1,252 @@
+/*
+ * 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.utils.db;
+
+import com.google.common.base.Preconditions;
+import org.apache.hadoop.hdfs.DFSUtil;
+import org.apache.hadoop.metrics2.util.MBeans;
+import org.apache.hadoop.utils.RocksDBStoreMBean;
+import org.apache.ratis.shaded.com.google.common.annotations.VisibleForTesting;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.ColumnFamilyOptions;
+import org.rocksdb.DBOptions;
+import org.rocksdb.RocksDB;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.WriteBatch;
+import org.rocksdb.WriteOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.management.ObjectName;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * RocksDB Store that supports creating Tables in DB.
+ */
+public class RDBStore implements DBStore {
+ private static final Logger LOG =
+ LoggerFactory.getLogger(RDBStore.class);
+ private final RocksDB db;
+ private final File dbLocation;
+ private final WriteOptions writeOptions;
+ private final DBOptions dbOptions;
+ private final Hashtable<String, ColumnFamilyHandle> handleTable;
+ private ObjectName statMBeanName;
+
+ public RDBStore(File dbFile, DBOptions options, List<String> families)
+ throws IOException {
+ Preconditions.checkNotNull(dbFile, "DB file location cannot be null");
+ Preconditions.checkNotNull(families);
+ Preconditions.checkArgument(families.size() > 0);
+ handleTable = new Hashtable<>();
+
+ final List<ColumnFamilyDescriptor> columnFamilyDescriptors =
+ new ArrayList<>();
+ final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
+
+ for (String family : families) {
+ columnFamilyDescriptors.add(
+ new ColumnFamilyDescriptor(family.getBytes(StandardCharsets.UTF_8),
+ new ColumnFamilyOptions()));
+ }
+
+ dbOptions = options;
+ dbLocation = dbFile;
+ // TODO: Read from the next Config.
+ writeOptions = new WriteOptions();
+
+ try {
+ db = RocksDB.open(dbOptions, dbLocation.getAbsolutePath(),
+ columnFamilyDescriptors, columnFamilyHandles);
+
+ for (int x = 0; x < columnFamilyHandles.size(); x++) {
+ handleTable.put(
+ DFSUtil.bytes2String(columnFamilyHandles.get(x).getName()),
+ columnFamilyHandles.get(x));
+ }
+
+ if (dbOptions.statistics() != null) {
+ Map<String, String> jmxProperties = new HashMap<>();
+ jmxProperties.put("dbName", dbFile.getName());
+ statMBeanName = MBeans.register("Ozone", "RocksDbStore", jmxProperties,
+ new RocksDBStoreMBean(dbOptions.statistics()));
+ if (statMBeanName == null) {
+ LOG.warn("jmx registration failed during RocksDB init, db path :{}",
+ dbFile.getAbsolutePath());
+ }
+ }
+
+ } catch (RocksDBException e) {
+ throw toIOException(
+ "Failed init RocksDB, db path : " + dbFile.getAbsolutePath(), e);
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("RocksDB successfully opened.");
+ LOG.debug("[Option] dbLocation= {}", dbLocation.getAbsolutePath());
+ LOG.debug("[Option] createIfMissing = {}", options.createIfMissing());
+ LOG.debug("[Option] maxOpenFiles= {}", options.maxOpenFiles());
+ }
+ }
+
+ public static IOException toIOException(String msg, RocksDBException e) {
+ String statusCode = e.getStatus() == null ? "N/A" :
+ e.getStatus().getCodeString();
+ String errMessage = e.getMessage() == null ? "Unknown error" :
+ e.getMessage();
+ String output = msg + "; status : " + statusCode
+ + "; message : " + errMessage;
+ return new IOException(output, e);
+ }
+
+ @Override
+ public void compactDB() throws IOException {
+ if (db != null) {
+ try {
+ db.compactRange();
+ } catch (RocksDBException e) {
+ throw toIOException("Failed to compact db", e);
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+
+ for (final ColumnFamilyHandle handle : handleTable.values()) {
+ handle.close();
+ }
+ if (dbOptions != null) {
+ dbOptions.close();
+ }
+ if (writeOptions != null) {
+ writeOptions.close();
+ }
+ if (statMBeanName != null) {
+ MBeans.unregister(statMBeanName);
+ statMBeanName = null;
+ }
+ if (db != null) {
+ db.close();
+ }
+ }
+
+ @Override
+ public void move(byte[] key, Table source, Table dest) throws IOException {
+ RDBTable sourceTable;
+ RDBTable destTable;
+ if (source instanceof RDBTable) {
+ sourceTable = (RDBTable) source;
+ } else {
+ LOG.error("Unexpected Table type. Expected RocksTable Store for Source.");
+ throw new IOException("Unexpected TableStore Type in source. Expected "
+ + "RocksDBTable.");
+ }
+
+ if (dest instanceof RDBTable) {
+ destTable = (RDBTable) dest;
+ } else {
+ LOG.error("Unexpected Table type. Expected RocksTable Store for Dest.");
+ throw new IOException("Unexpected TableStore Type in dest. Expected "
+ + "RocksDBTable.");
+ }
+ try (WriteBatch batch = new WriteBatch()) {
+ byte[] value = sourceTable.get(key);
+ batch.put(destTable.getHandle(), key, value);
+ batch.delete(sourceTable.getHandle(), key);
+ db.write(writeOptions, batch);
+ } catch (RocksDBException rockdbException) {
+ LOG.error("Move of key failed. Key:{}", DFSUtil.bytes2String(key));
+ throw toIOException("Unable to move key: " + DFSUtil.bytes2String(key),
+ rockdbException);
+ }
+ }
+
+ @Override
+ public void move(byte[] key, byte[] value, Table source,
+ Table dest) throws IOException {
+ RDBTable sourceTable;
+ RDBTable destTable;
+ if (source instanceof RDBTable) {
+ sourceTable = (RDBTable) source;
+ } else {
+ LOG.error("Unexpected Table type. Expected RocksTable Store for Source.");
+ throw new IOException("Unexpected TableStore Type in source. Expected "
+ + "RocksDBTable.");
+ }
+
+ if (dest instanceof RDBTable) {
+ destTable = (RDBTable) dest;
+ } else {
+ LOG.error("Unexpected Table type. Expected RocksTable Store for Dest.");
+ throw new IOException("Unexpected TableStore Type in dest. Expected "
+ + "RocksDBTable.");
+ }
+ try (WriteBatch batch = new WriteBatch()) {
+ batch.put(destTable.getHandle(), key, value);
+ batch.delete(sourceTable.getHandle(), key);
+ db.write(writeOptions, batch);
+ } catch (RocksDBException rockdbException) {
+ LOG.error("Move of key failed. Key:{}", DFSUtil.bytes2String(key));
+ throw toIOException("Unable to move key: " + DFSUtil.bytes2String(key),
+ rockdbException);
+ }
+ }
+
+ @Override
+ public long getEstimatedKeyCount() throws IOException {
+ try {
+ return Long.parseLong(db.getProperty("rocksdb.estimate-num-keys"));
+ } catch (RocksDBException e) {
+ throw toIOException("Unable to get the estimated count.", e);
+ }
+ }
+
+ @VisibleForTesting
+ protected ObjectName getStatMBeanName() {
+ return statMBeanName;
+ }
+
+ @Override
+ public Table getTable(String name) throws IOException {
+ ColumnFamilyHandle handle = handleTable.get(name);
+ if (handle == null) {
+ throw new IOException("No such table in this DB. TableName : " + name);
+ }
+ return new RDBTable(this.db, handle, this.writeOptions);
+ }
+
+ @Override
+ public ArrayList<Table> listTables() throws IOException {
+ ArrayList<Table> returnList = new ArrayList<>();
+ for (ColumnFamilyHandle handle: handleTable.values()) {
+ returnList.add(new RDBTable(db, handle, writeOptions));
+ }
+ return returnList;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStoreIterator.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStoreIterator.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStoreIterator.java
new file mode 100644
index 0000000..f1f2df6
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBStoreIterator.java
@@ -0,0 +1,88 @@
+/*
+ * 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.utils.db;
+
+import org.apache.hadoop.utils.db.Table.KeyValue;
+import org.rocksdb.RocksIterator;
+
+import java.io.IOException;
+import java.util.NoSuchElementException;
+import java.util.function.Consumer;
+
+/**
+ * RocksDB store iterator.
+ */
+public class RDBStoreIterator implements TableIterator<KeyValue> {
+
+ private RocksIterator rocksDBIterator;
+
+ public RDBStoreIterator(RocksIterator iterator) {
+ this.rocksDBIterator = iterator;
+ rocksDBIterator.seekToFirst();
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super KeyValue> action) {
+ while(hasNext()) {
+ action.accept(next());
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return rocksDBIterator.isValid();
+ }
+
+ @Override
+ public Table.KeyValue next() {
+ if (rocksDBIterator.isValid()) {
+ KeyValue value = KeyValue.create(rocksDBIterator.key(), rocksDBIterator
+ .value());
+ rocksDBIterator.next();
+ return value;
+ }
+ throw new NoSuchElementException("RocksDB Store has no more elements");
+ }
+
+ @Override
+ public void seekToFirst() {
+ rocksDBIterator.seekToFirst();
+ }
+
+ @Override
+ public void seekToLast() {
+ rocksDBIterator.seekToLast();
+ }
+
+ @Override
+ public KeyValue seek(byte[] key) {
+ rocksDBIterator.seek(key);
+ if (rocksDBIterator.isValid()) {
+ return KeyValue.create(rocksDBIterator.key(),
+ rocksDBIterator.value());
+ }
+ return null;
+ }
+
+ @Override
+ public void close() throws IOException {
+ rocksDBIterator.close();
+ }
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBTable.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBTable.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBTable.java
new file mode 100644
index 0000000..8cf6b35
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/RDBTable.java
@@ -0,0 +1,173 @@
+/*
+ * 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.utils.db;
+
+import org.apache.hadoop.hdfs.DFSUtil;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.ReadOptions;
+import org.rocksdb.RocksDB;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.WriteBatch;
+import org.rocksdb.WriteOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * RocksDB implementation of ozone metadata store.
+ */
+public class RDBTable implements Table {
+
+ private static final Logger LOG =
+ LoggerFactory.getLogger(RDBTable.class);
+
+ private final RocksDB db;
+ private final ColumnFamilyHandle handle;
+ private final WriteOptions writeOptions;
+
+ /**
+ * Constructs a TableStore.
+ *
+ * @param db - DBstore that we are using.
+ * @param handle - ColumnFamily Handle.
+ * @param writeOptions - RocksDB write Options.
+ */
+ public RDBTable(RocksDB db, ColumnFamilyHandle handle,
+ WriteOptions writeOptions) {
+ this.db = db;
+ this.handle = handle;
+ this.writeOptions = writeOptions;
+ }
+
+ /**
+ * Converts RocksDB exception to IOE.
+ * @param msg - Message to add to exception.
+ * @param e - Original Exception.
+ * @return IOE.
+ */
+ public static IOException toIOException(String msg, RocksDBException e) {
+ String statusCode = e.getStatus() == null ? "N/A" :
+ e.getStatus().getCodeString();
+ String errMessage = e.getMessage() == null ? "Unknown error" :
+ e.getMessage();
+ String output = msg + "; status : " + statusCode
+ + "; message : " + errMessage;
+ return new IOException(output, e);
+ }
+
+ /**
+ * Returns the Column family Handle.
+ *
+ * @return ColumnFamilyHandle.
+ */
+ @Override
+ public ColumnFamilyHandle getHandle() {
+ return handle;
+ }
+
+ @Override
+ public void put(byte[] key, byte[] value) throws IOException {
+ try {
+ db.put(handle, writeOptions, key, value);
+ } catch (RocksDBException e) {
+ LOG.error("Failed to write to DB. Key: {}", new String(key,
+ StandardCharsets.UTF_8));
+ throw toIOException("Failed to put key-value to metadata "
+ + "store", e);
+ }
+ }
+
+ @Override
+ public boolean isEmpty() throws IOException {
+ try (TableIterator<KeyValue> keyIter = iterator()) {
+ keyIter.seekToFirst();
+ return !keyIter.hasNext();
+ }
+ }
+
+ @Override
+ public byte[] get(byte[] key) throws IOException {
+ try {
+ return db.get(handle, key);
+ } catch (RocksDBException e) {
+ throw toIOException(
+ "Failed to get the value for the given key", e);
+ }
+ }
+
+ @Override
+ public void delete(byte[] key) throws IOException {
+ try {
+ db.delete(handle, key);
+ } catch (RocksDBException e) {
+ throw toIOException("Failed to delete the given key", e);
+ }
+ }
+
+ @Override
+ public void writeBatch(WriteBatch operation) throws IOException {
+ try {
+ db.write(writeOptions, operation);
+ } catch (RocksDBException e) {
+ throw toIOException("Batch write operation failed", e);
+ }
+ }
+
+// @Override
+// public void iterate(byte[] from, EntryConsumer consumer)
+// throws IOException {
+//
+// try (RocksIterator it = db.newIterator(handle)) {
+// if (from != null) {
+// it.seek(from);
+// } else {
+// it.seekToFirst();
+// }
+// while (it.isValid()) {
+// if (!consumer.consume(it.key(), it.value())) {
+// break;
+// }
+// it.next();
+// }
+// }
+// }
+
+ @Override
+ public TableIterator<KeyValue> iterator() {
+ ReadOptions readOptions = new ReadOptions();
+ return new RDBStoreIterator(db.newIterator(handle, readOptions));
+ }
+
+ @Override
+ public String getName() throws IOException {
+ try {
+ return DFSUtil.bytes2String(this.getHandle().getName());
+ } catch (RocksDBException rdbEx) {
+ throw toIOException("Unable to get the table name.", rdbEx);
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ // Nothing do for a Column Family.
+ }
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/Table.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/Table.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/Table.java
new file mode 100644
index 0000000..3942585
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/Table.java
@@ -0,0 +1,150 @@
+/*
+ * 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.utils.db;
+
+import org.apache.hadoop.classification.InterfaceStability;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.WriteBatch;
+
+import java.io.IOException;
+
+/**
+ * Interface for key-value store that stores ozone metadata. Ozone metadata is
+ * stored as key value pairs, both key and value are arbitrary byte arrays. Each
+ * Table Stores a certain kind of keys and values. This allows a DB to have
+ * different kind of tables.
+ */
+@InterfaceStability.Evolving
+public interface Table extends AutoCloseable {
+
+ /**
+ * Puts a key-value pair into the store.
+ *
+ * @param key metadata key
+ * @param value metadata value
+ */
+ void put(byte[] key, byte[] value) throws IOException;
+
+ /**
+ * @return true if the metadata store is empty.
+ * @throws IOException on Failure
+ */
+ boolean isEmpty() throws IOException;
+
+ /**
+ * Returns the value mapped to the given key in byte array or returns null
+ * if the key is not found.
+ *
+ * @param key metadata key
+ * @return value in byte array or null if the key is not found.
+ * @throws IOException on Failure
+ */
+ byte[] get(byte[] key) throws IOException;
+
+ /**
+ * Deletes a key from the metadata store.
+ *
+ * @param key metadata key
+ * @throws IOException on Failure
+ */
+ void delete(byte[] key) throws IOException;
+
+ /**
+ * Return the Column Family handle. TODO: This leaks an RockDB abstraction
+ * into Ozone code, cleanup later.
+ *
+ * @return ColumnFamilyHandle
+ */
+ ColumnFamilyHandle getHandle();
+
+ /**
+ * A batch of PUT, DELETE operations handled as a single atomic write.
+ *
+ * @throws IOException write fails
+ */
+ void writeBatch(WriteBatch operation) throws IOException;
+
+ /**
+ * Returns the iterator for this metadata store.
+ *
+ * @return MetaStoreIterator
+ */
+ TableIterator<KeyValue> iterator();
+
+ /**
+ * Returns the Name of this Table.
+ * @return - Table Name.
+ * @throws IOException on failure.
+ */
+ String getName() throws IOException;
+
+ /**
+ * Class used to represent the key and value pair of a db entry.
+ */
+ class KeyValue {
+
+ private final byte[] key;
+ private final byte[] value;
+
+ /**
+ * KeyValue Constructor, used to represent a key and value of a db entry.
+ *
+ * @param key - Key Bytes
+ * @param value - Value bytes
+ */
+ private KeyValue(byte[] key, byte[] value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * Create a KeyValue pair.
+ *
+ * @param key - Key Bytes
+ * @param value - Value bytes
+ * @return KeyValue object.
+ */
+ public static KeyValue create(byte[] key, byte[] value) {
+ return new KeyValue(key, value);
+ }
+
+ /**
+ * Return key.
+ *
+ * @return byte[]
+ */
+ public byte[] getKey() {
+ byte[] result = new byte[key.length];
+ System.arraycopy(key, 0, result, 0, key.length);
+ return result;
+ }
+
+ /**
+ * Return value.
+ *
+ * @return byte[]
+ */
+ public byte[] getValue() {
+ byte[] result = new byte[value.length];
+ System.arraycopy(value, 0, result, 0, value.length);
+ return result;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/TableIterator.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/TableIterator.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/TableIterator.java
new file mode 100644
index 0000000..83a8f3c
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/TableIterator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.utils.db;
+
+import java.io.Closeable;
+import java.util.Iterator;
+
+/**
+ * Iterator for MetaDataStore DB.
+ *
+ * @param <T>
+ */
+public interface TableIterator<T> extends Iterator<T>, Closeable {
+
+ /**
+ * seek to first entry.
+ */
+ void seekToFirst();
+
+ /**
+ * seek to last entry.
+ */
+ void seekToLast();
+
+ /**
+ * Seek to the specific key.
+ *
+ * @param key - Bytes that represent the key.
+ * @return T.
+ */
+ T seek(byte[] key);
+
+}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/package-info.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/package-info.java b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/package-info.java
new file mode 100644
index 0000000..17d676d
--- /dev/null
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/utils/db/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ *
+ */
+/**
+ * Database interfaces for Ozone.
+ */
+package org.apache.hadoop.utils.db;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBStore.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBStore.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBStore.java
new file mode 100644
index 0000000..94a650b
--- /dev/null
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBStore.java
@@ -0,0 +1,246 @@
+/*
+ * 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.utils.db;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.hadoop.hdfs.DFSUtil;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.rocksdb.DBOptions;
+import org.rocksdb.RocksDB;
+import org.rocksdb.Statistics;
+import org.rocksdb.StatsLevel;
+
+import javax.management.MBeanServer;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * RDBStore Tests.
+ */
+public class TestRDBStore {
+ private final List<String> families =
+ Arrays.asList(DFSUtil.bytes2String(RocksDB.DEFAULT_COLUMN_FAMILY),
+ "First", "Second", "Third",
+ "Fourth", "Fifth",
+ "Sixth");
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+ private RDBStore rdbStore = null;
+ private DBOptions options = null;
+
+ @Before
+ public void setUp() throws Exception {
+ options = new DBOptions();
+ options.setCreateIfMissing(true);
+ options.setCreateMissingColumnFamilies(true);
+
+ Statistics statistics = new Statistics();
+ statistics.setStatsLevel(StatsLevel.ALL);
+ options = options.setStatistics(statistics);
+ rdbStore = new RDBStore(folder.newFolder(), options, families);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (rdbStore != null) {
+ rdbStore.close();
+ }
+ }
+
+ @Test
+ public void compactDB() throws Exception {
+ try (RDBStore newStore =
+ new RDBStore(folder.newFolder(), options, families)) {
+ Assert.assertNotNull("DB Store cannot be null", newStore);
+ try (Table firstTable = newStore.getTable(families.get(1))) {
+ Assert.assertNotNull("Table cannot be null", firstTable);
+ for (int x = 0; x < 100; x++) {
+ byte[] key =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ firstTable.put(key, value);
+ }
+ }
+ // This test does not assert anything if there is any error this test
+ // will throw and fail.
+ newStore.compactDB();
+ }
+ }
+
+ @Test
+ public void close() throws Exception {
+ RDBStore newStore =
+ new RDBStore(folder.newFolder(), options, families);
+ Assert.assertNotNull("DBStore cannot be null", newStore);
+ // This test does not assert anything if there is any error this test
+ // will throw and fail.
+ newStore.close();
+ }
+
+ @Test
+ public void moveKey() throws Exception {
+ byte[] key =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+
+ try (Table firstTable = rdbStore.getTable(families.get(1))) {
+ firstTable.put(key, value);
+ try (Table secondTable = rdbStore.getTable(families.get(2))) {
+ rdbStore.move(key, firstTable, secondTable);
+ byte[] newvalue = secondTable.get(key);
+ // Make sure we have value in the second table
+ Assert.assertNotNull(newvalue);
+ //and it is same as what we wrote to the FirstTable
+ Assert.assertArrayEquals(value, newvalue);
+ }
+ // After move this key must not exist in the first table.
+ Assert.assertNull(firstTable.get(key));
+ }
+ }
+
+ @Test
+ public void moveWithValue() throws Exception {
+ byte[] key =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+
+ byte[] nextValue =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ try (Table firstTable = rdbStore.getTable(families.get(1))) {
+ firstTable.put(key, value);
+ try (Table secondTable = rdbStore.getTable(families.get(2))) {
+ rdbStore.move(key, nextValue, firstTable, secondTable);
+ byte[] newvalue = secondTable.get(key);
+ // Make sure we have value in the second table
+ Assert.assertNotNull(newvalue);
+ //and it is not same as what we wrote to the FirstTable, and equals
+ // the new value.
+ Assert.assertArrayEquals(nextValue, nextValue);
+ }
+ }
+
+ }
+
+ @Test
+ public void getEstimatedKeyCount() throws Exception {
+ try (RDBStore newStore =
+ new RDBStore(folder.newFolder(), options, families)) {
+ Assert.assertNotNull("DB Store cannot be null", newStore);
+ // Write 100 keys to the first table.
+ try (Table firstTable = newStore.getTable(families.get(1))) {
+ Assert.assertNotNull("Table cannot be null", firstTable);
+ for (int x = 0; x < 100; x++) {
+ byte[] key =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ firstTable.put(key, value);
+ }
+ }
+
+ // Write 100 keys to the secondTable table.
+ try (Table secondTable = newStore.getTable(families.get(2))) {
+ Assert.assertNotNull("Table cannot be null", secondTable);
+ for (int x = 0; x < 100; x++) {
+ byte[] key =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ secondTable.put(key, value);
+ }
+ }
+ // Let us make sure that our estimate is not off by 10%
+ Assert.assertTrue(newStore.getEstimatedKeyCount() > 180
+ || newStore.getEstimatedKeyCount() < 220);
+ }
+ }
+
+ @Test
+ public void getStatMBeanName() throws Exception {
+
+ try (Table firstTable = rdbStore.getTable(families.get(1))) {
+ for (int y = 0; y < 100; y++) {
+ byte[] key =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ firstTable.put(key, value);
+ }
+ }
+ MBeanServer platformMBeanServer =
+ ManagementFactory.getPlatformMBeanServer();
+ Thread.sleep(2000);
+
+ Object keysWritten = platformMBeanServer
+ .getAttribute(rdbStore.getStatMBeanName(), "NUMBER_KEYS_WRITTEN");
+
+ Assert.assertTrue(((Long) keysWritten) >= 99L);
+
+ Object dbWriteAverage = platformMBeanServer
+ .getAttribute(rdbStore.getStatMBeanName(), "DB_WRITE_AVERAGE");
+ Assert.assertTrue((double) dbWriteAverage > 0);
+ }
+
+ @Test
+ public void getTable() throws Exception {
+ for (String tableName : families) {
+ try (Table table = rdbStore.getTable(tableName)) {
+ Assert.assertNotNull(tableName + "is null", table);
+ }
+ }
+ thrown.expect(IOException.class);
+ rdbStore.getTable("ATableWithNoName");
+ }
+
+ @Test
+ public void listTables() throws Exception {
+ List<Table> tableList = rdbStore.listTables();
+ Assert.assertNotNull("Table list cannot be null", tableList);
+ Map<String, Table> hashTable = new HashMap<>();
+
+ for (Table t : tableList) {
+ hashTable.put(t.getName(), t);
+ }
+
+ int count = families.size();
+ // Assert that we have all the tables in the list and no more.
+ for (String name : families) {
+ Assert.assertTrue(hashTable.containsKey(name));
+ count--;
+ }
+ Assert.assertEquals(0, count);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBTableStore.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBTableStore.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBTableStore.java
new file mode 100644
index 0000000..677a1f9
--- /dev/null
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/TestRDBTableStore.java
@@ -0,0 +1,189 @@
+/*
+ * 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.utils.db;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.hadoop.hdfs.DFSUtil;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.rocksdb.DBOptions;
+import org.rocksdb.RocksDB;
+import org.rocksdb.Statistics;
+import org.rocksdb.StatsLevel;
+import org.rocksdb.WriteBatch;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Tests for RocksDBTable Store.
+ */
+public class TestRDBTableStore {
+ private static int count = 0;
+ private final List<String> families =
+ Arrays.asList(DFSUtil.bytes2String(RocksDB.DEFAULT_COLUMN_FAMILY),
+ "First", "Second", "Third",
+ "Fourth", "Fifth",
+ "Sixth");
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+ private RDBStore rdbStore = null;
+ private DBOptions options = null;
+
+ @Before
+ public void setUp() throws Exception {
+ options = new DBOptions();
+ options.setCreateIfMissing(true);
+ options.setCreateMissingColumnFamilies(true);
+
+ Statistics statistics = new Statistics();
+ statistics.setStatsLevel(StatsLevel.ALL);
+ options = options.setStatistics(statistics);
+ rdbStore = new RDBStore(folder.newFolder(), options, families);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (rdbStore != null) {
+ rdbStore.close();
+ }
+ }
+
+ @Test
+ public void toIOException() {
+ }
+
+ @Test
+ public void getHandle() throws Exception {
+ try (Table testTable = rdbStore.getTable("First")) {
+ Assert.assertNotNull(testTable);
+ Assert.assertNotNull(testTable.getHandle());
+ }
+ }
+
+ @Test
+ public void putGetAndEmpty() throws Exception {
+ try (Table testTable = rdbStore.getTable("First")) {
+ byte[] key =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ testTable.put(key, value);
+ Assert.assertFalse(testTable.isEmpty());
+ byte[] readValue = testTable.get(key);
+ Assert.assertArrayEquals(value, readValue);
+ }
+ try (Table secondTable = rdbStore.getTable("Second")) {
+ Assert.assertTrue(secondTable.isEmpty());
+ }
+ }
+
+ @Test
+ public void delete() throws Exception {
+ List<byte[]> deletedKeys = new LinkedList<>();
+ List<byte[]> validKeys = new LinkedList<>();
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ for (int x = 0; x < 100; x++) {
+ deletedKeys.add(
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8));
+ }
+
+ for (int x = 0; x < 100; x++) {
+ validKeys.add(
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8));
+ }
+
+ // Write all the keys and delete the keys scheduled for delete.
+ //Assert we find only expected keys in the Table.
+ try (Table testTable = rdbStore.getTable("Fourth")) {
+ for (int x = 0; x < deletedKeys.size(); x++) {
+ testTable.put(deletedKeys.get(x), value);
+ testTable.delete(deletedKeys.get(x));
+ }
+
+ for (int x = 0; x < validKeys.size(); x++) {
+ testTable.put(validKeys.get(x), value);
+ }
+
+ for (int x = 0; x < validKeys.size(); x++) {
+ Assert.assertNotNull(testTable.get(validKeys.get(0)));
+ }
+
+ for (int x = 0; x < deletedKeys.size(); x++) {
+ Assert.assertNull(testTable.get(deletedKeys.get(0)));
+ }
+ }
+ }
+
+ @Test
+ public void writeBatch() throws Exception {
+ WriteBatch batch = new WriteBatch();
+ try (Table testTable = rdbStore.getTable("Fifth")) {
+ byte[] key =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ batch.put(testTable.getHandle(), key, value);
+ testTable.writeBatch(batch);
+ Assert.assertNotNull(testTable.get(key));
+ }
+ batch.close();
+ }
+
+ private static boolean consume(Table.KeyValue keyValue) {
+ count++;
+ Assert.assertNotNull(keyValue.getKey());
+ return true;
+ }
+
+ @Test
+ public void forEachAndIterator() throws Exception {
+ final int iterCount = 100;
+ try (Table testTable = rdbStore.getTable("Sixth")) {
+ for (int x = 0; x < iterCount; x++) {
+ byte[] key =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ byte[] value =
+ RandomStringUtils.random(10).getBytes(StandardCharsets.UTF_8);
+ testTable.put(key, value);
+ }
+ int localCount = 0;
+ try (TableIterator<Table.KeyValue> iter = testTable.iterator()) {
+ while (iter.hasNext()) {
+ Table.KeyValue keyValue = iter.next();
+ localCount++;
+ }
+
+ Assert.assertEquals(iterCount, localCount);
+ iter.seekToFirst();
+ iter.forEachRemaining(TestRDBTableStore::consume);
+ Assert.assertEquals(iterCount, count);
+
+ }
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/hadoop/blob/b021249a/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/package-info.java
----------------------------------------------------------------------
diff --git a/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/package-info.java b/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/package-info.java
new file mode 100644
index 0000000..f06855e
--- /dev/null
+++ b/hadoop-hdds/common/src/test/java/org/apache/hadoop/utils/db/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ *
+ */
+/**
+ * Tests for the DB Utilities.
+ */
+package org.apache.hadoop.utils.db;
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org