From d5a2830777fb1b3c5e8d319f1928c30013fb04b1 Mon Sep 17 00:00:00 2001 From: Enis Soztutar Date: Wed, 11 Feb 2015 16:31:27 -0800 Subject: [PATCH 001/329] HBASE-12991 Use HBase 1.0 interfaces in hbase-rest (Solomon Duskis) --- .../apache/hadoop/hbase/rest/RESTServlet.java | 4 +- .../hadoop/hbase/rest/SchemaResource.java | 27 +++++---- .../hadoop/hbase/rest/TableResource.java | 3 +- .../hbase/rest/client/RemoteHTable.java | 56 ++++++------------- .../hadoop/hbase/rest/RowResourceBase.java | 1 - .../hadoop/hbase/rest/TestGzipFilter.java | 1 - .../hbase/rest/TestScannersWithFilters.java | 1 - .../hadoop/hbase/rest/TestStatusResource.java | 1 - .../hadoop/hbase/rest/TestTableResource.java | 54 ++++++++++++------ 9 files changed, 71 insertions(+), 77 deletions(-) diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServlet.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServlet.java index bb93bc8f930..0ecaf5a87d4 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServlet.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServlet.java @@ -22,7 +22,7 @@ import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.classification.InterfaceAudience; -import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.filter.ParseFilter; import org.apache.hadoop.hbase.security.UserProvider; @@ -101,7 +101,7 @@ public class RESTServlet implements Constants { } } - HBaseAdmin getAdmin() throws IOException { + Admin getAdmin() throws IOException { return connectionCache.getAdmin(); } diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/SchemaResource.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/SchemaResource.java index 45dd9ee7af8..9826b67a99e 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/SchemaResource.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/SchemaResource.java @@ -37,18 +37,17 @@ import javax.xml.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.TableExistsException; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableNotEnabledException; import org.apache.hadoop.hbase.TableNotFoundException; -import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.rest.model.ColumnSchemaModel; import org.apache.hadoop.hbase.rest.model.TableSchemaModel; -import org.apache.hadoop.hbase.util.Bytes; @InterfaceAudience.Private public class SchemaResource extends ResourceBase { @@ -103,15 +102,15 @@ public class SchemaResource extends ResourceBase { } } - private Response replace(final byte[] name, final TableSchemaModel model, - final UriInfo uriInfo, final HBaseAdmin admin) { + private Response replace(final TableName name, final TableSchemaModel model, + final UriInfo uriInfo, final Admin admin) { if (servlet.isReadOnly()) { return Response.status(Response.Status.FORBIDDEN) .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF) .build(); } try { - HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(name)); + HTableDescriptor htd = new HTableDescriptor(name); for (Map.Entry e: model.getAny().entrySet()) { htd.setValue(e.getKey().getLocalPart(), e.getValue().toString()); } @@ -143,8 +142,8 @@ public class SchemaResource extends ResourceBase { } } - private Response update(final byte[] name, final TableSchemaModel model, - final UriInfo uriInfo, final HBaseAdmin admin) { + private Response update(final TableName name, final TableSchemaModel model, + final UriInfo uriInfo, final Admin admin) { if (servlet.isReadOnly()) { return Response.status(Response.Status.FORBIDDEN) .type(MIMETYPE_TEXT).entity("Forbidden" + CRLF) @@ -170,7 +169,7 @@ public class SchemaResource extends ResourceBase { .type(MIMETYPE_TEXT).entity("Unavailable" + CRLF) .build(); } finally { - admin.enableTable(tableResource.getName()); + admin.enableTable(TableName.valueOf(tableResource.getName())); } servlet.getMetrics().incrementSucessfulPutRequests(1); return Response.ok().build(); @@ -183,8 +182,8 @@ public class SchemaResource extends ResourceBase { private Response update(final TableSchemaModel model, final boolean replace, final UriInfo uriInfo) { try { - byte[] name = Bytes.toBytes(tableResource.getName()); - HBaseAdmin admin = servlet.getAdmin(); + TableName name = TableName.valueOf(tableResource.getName()); + Admin admin = servlet.getAdmin(); if (replace || !admin.tableExists(name)) { return replace(name, model, uriInfo, admin); } else { @@ -233,11 +232,11 @@ public class SchemaResource extends ResourceBase { .entity("Forbidden" + CRLF).build(); } try { - HBaseAdmin admin = servlet.getAdmin(); + Admin admin = servlet.getAdmin(); try { - admin.disableTable(tableResource.getName()); + admin.disableTable(TableName.valueOf(tableResource.getName())); } catch (TableNotEnabledException e) { /* this is what we want anyway */ } - admin.deleteTable(tableResource.getName()); + admin.deleteTable(TableName.valueOf(tableResource.getName())); servlet.getMetrics().incrementSucessfulDeleteRequests(1); return Response.ok().build(); } catch (Exception e) { diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/TableResource.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/TableResource.java index caf14319fa3..556425ff69b 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/TableResource.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/TableResource.java @@ -34,6 +34,7 @@ import javax.ws.rs.core.UriInfo; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.Table; @@ -69,7 +70,7 @@ public class TableResource extends ResourceBase { * @throws IOException */ boolean exists() throws IOException { - return servlet.getAdmin().tableExists(table); + return servlet.getAdmin().tableExists(TableName.valueOf(table)); } @Path("exists") diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java index 65bf509f9f1..eb5f50697d2 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java @@ -19,14 +19,18 @@ package org.apache.hadoop.hbase.rest.client; -import com.google.protobuf.Descriptors; -import com.google.protobuf.Message; -import com.google.protobuf.Service; -import com.google.protobuf.ServiceException; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.classification.InterfaceAudience; -import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; @@ -35,11 +39,12 @@ import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.hbase.client.Append; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Durability; import org.apache.hadoop.hbase.client.Get; -import org.apache.hadoop.hbase.client.HTableInterface; import org.apache.hadoop.hbase.client.Increment; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; @@ -47,6 +52,7 @@ import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Row; import org.apache.hadoop.hbase.client.RowMutations; import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.client.coprocessor.Batch; import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback; import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; @@ -61,22 +67,17 @@ import org.apache.hadoop.hbase.rest.model.TableSchemaModel; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.util.StringUtils; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; +import com.google.protobuf.Descriptors; +import com.google.protobuf.Message; +import com.google.protobuf.Service; +import com.google.protobuf.ServiceException; /** * HTable interface to remote tables accessed via REST gateway */ @InterfaceAudience.Public @InterfaceStability.Stable -public class RemoteHTable implements HTableInterface { +public class RemoteHTable implements Table { private static final Log LOG = LogFactory.getLog(RemoteHTable.class); @@ -805,21 +806,6 @@ public class RemoteHTable implements HTableInterface { throw new IOException("atomicMutation not supported"); } - @Override - public void setAutoFlush(boolean autoFlush) { - throw new UnsupportedOperationException("setAutoFlush not implemented"); - } - - @Override - public void setAutoFlush(boolean autoFlush, boolean clearBufferOnFail) { - throw new UnsupportedOperationException("setAutoFlush not implemented"); - } - - @Override - public void setAutoFlushTo(boolean autoFlush) { - throw new UnsupportedOperationException("setAutoFlushTo not implemented"); - } - @Override public long getWriteBufferSize() { throw new UnsupportedOperationException("getWriteBufferSize not implemented"); @@ -830,12 +816,6 @@ public class RemoteHTable implements HTableInterface { throw new IOException("setWriteBufferSize not supported"); } - @Override - public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, - long amount, boolean writeToWAL) throws IOException { - throw new IOException("incrementColumnValue not supported"); - } - @Override public Map batchCoprocessorService( Descriptors.MethodDescriptor method, Message request, diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/RowResourceBase.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/RowResourceBase.java index 876b089d40c..0e74b46b4e6 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/RowResourceBase.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/RowResourceBase.java @@ -35,7 +35,6 @@ import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Admin; -import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.rest.client.Client; import org.apache.hadoop.hbase.rest.client.Cluster; import org.apache.hadoop.hbase.rest.client.Response; diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGzipFilter.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGzipFilter.java index e4a322a3d75..42d355d8e03 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGzipFilter.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestGzipFilter.java @@ -34,7 +34,6 @@ import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Get; -import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.rest.client.Client; diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestScannersWithFilters.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestScannersWithFilters.java index 5bd8fc842b8..3acddc1aa30 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestScannersWithFilters.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestScannersWithFilters.java @@ -35,7 +35,6 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.*; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Delete; -import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.Durability; diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestStatusResource.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestStatusResource.java index 00c2049936e..5fdc631a183 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestStatusResource.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestStatusResource.java @@ -30,7 +30,6 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.Waiter; -import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.rest.client.Client; import org.apache.hadoop.hbase.rest.client.Cluster; import org.apache.hadoop.hbase.rest.client.Response; diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestTableResource.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestTableResource.java index 7cbb2a290b8..b0b8fef407d 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestTableResource.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestTableResource.java @@ -19,36 +19,46 @@ package org.apache.hadoop.hbase.rest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.InetSocketAddress; +import java.util.ArrayList; import java.util.Iterator; -import java.util.Map; +import java.util.List; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.*; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.HRegionLocation; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Admin; -import org.apache.hadoop.hbase.client.HTable; -import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.Durability; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.RegionLocator; +import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.rest.client.Client; import org.apache.hadoop.hbase.rest.client.Cluster; import org.apache.hadoop.hbase.rest.client.Response; -import org.apache.hadoop.hbase.rest.model.TableModel; import org.apache.hadoop.hbase.rest.model.TableInfoModel; import org.apache.hadoop.hbase.rest.model.TableListModel; +import org.apache.hadoop.hbase.rest.model.TableModel; import org.apache.hadoop.hbase.rest.model.TableRegionModel; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.testclassification.RestTests; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.util.StringUtils; - -import static org.junit.Assert.*; - import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -61,7 +71,7 @@ public class TestTableResource { private static TableName TABLE = TableName.valueOf("TestTableResource"); private static String COLUMN_FAMILY = "test"; private static String COLUMN = COLUMN_FAMILY + ":qualifier"; - private static Map regionMap; + private static List regionMap; private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private static final HBaseRESTTestingUtility REST_TEST_UTIL = @@ -87,9 +97,9 @@ public class TestTableResource { HTableDescriptor htd = new HTableDescriptor(TABLE); htd.addFamily(new HColumnDescriptor(COLUMN_FAMILY)); admin.createTable(htd); - HTable table = (HTable) TEST_UTIL.getConnection().getTable(TABLE); byte[] k = new byte[3]; byte [][] famAndQf = KeyValue.parseColumn(Bytes.toBytes(COLUMN)); + List puts = new ArrayList<>(); for (byte b1 = 'a'; b1 < 'z'; b1++) { for (byte b2 = 'a'; b2 < 'z'; b2++) { for (byte b3 = 'a'; b3 < 'z'; b3++) { @@ -99,13 +109,19 @@ public class TestTableResource { Put put = new Put(k); put.setDurability(Durability.SKIP_WAL); put.add(famAndQf[0], famAndQf[1], k); - table.put(put); + puts.add(put); } } } - table.flushCommits(); + Connection connection = TEST_UTIL.getConnection(); + + Table table = connection.getTable(TABLE); + table.put(puts); + table.close(); // get the initial layout (should just be one region) - Map m = table.getRegionLocations(); + + RegionLocator regionLocator = connection.getRegionLocator(TABLE); + List m = regionLocator.getAllRegionLocations(); assertEquals(m.size(), 1); // tell the master to split the table admin.split(TABLE); @@ -119,14 +135,14 @@ public class TestTableResource { LOG.warn(StringUtils.stringifyException(e)); } // check again - m = table.getRegionLocations(); + m = regionLocator.getAllRegionLocations(); } // should have two regions now assertEquals(m.size(), 2); regionMap = m; LOG.info("regions: " + regionMap); - table.close(); + regionLocator.close(); } @AfterClass @@ -156,15 +172,17 @@ public class TestTableResource { while (regions.hasNext()) { TableRegionModel region = regions.next(); boolean found = false; - for (Map.Entry e: regionMap.entrySet()) { - HRegionInfo hri = e.getKey(); + for (HRegionLocation e: regionMap) { + HRegionInfo hri = e.getRegionInfo(); String hriRegionName = hri.getRegionNameAsString(); String regionName = region.getName(); if (hriRegionName.equals(regionName)) { found = true; byte[] startKey = hri.getStartKey(); byte[] endKey = hri.getEndKey(); - InetSocketAddress sa = new InetSocketAddress(e.getValue().getHostname(), e.getValue().getPort()); + ServerName serverName = e.getServerName(); + InetSocketAddress sa = + new InetSocketAddress(serverName.getHostname(), serverName.getPort()); String location = sa.getHostName() + ":" + Integer.valueOf(sa.getPort()); assertEquals(hri.getRegionId(), region.getId()); From e83444e845d80582e031f75eab9db4d339d619ab Mon Sep 17 00:00:00 2001 From: Enis Soztutar Date: Wed, 11 Feb 2015 16:51:12 -0800 Subject: [PATCH 002/329] HBASE-12920 hadoopqa should compile with different hadoop versions --- dev-support/test-patch.properties | 4 ++++ dev-support/test-patch.sh | 34 +++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/dev-support/test-patch.properties b/dev-support/test-patch.properties index 2995f8e5228..192a3ed616b 100644 --- a/dev-support/test-patch.properties +++ b/dev-support/test-patch.properties @@ -28,3 +28,7 @@ MAX_LINE_LENGTH=100 # All supported branches for testing with precommit build # branch-1.x should apprear before branch-1 since the latter is a prefix BRANCH_NAMES="0.94 0.98 branch-1.0 branch-1 master" + +# All supported Hadoop versions that we want to test the compilation with +HADOOP2_VERSIONS="2.4.1 2.5.2 2.6.0" +HADOOP3_VERSIONS="3.0.0-SNAPSHOT" diff --git a/dev-support/test-patch.sh b/dev-support/test-patch.sh index 0754503528d..117a872a799 100755 --- a/dev-support/test-patch.sh +++ b/dev-support/test-patch.sh @@ -377,17 +377,21 @@ checkTests () { ### Check there are no compilation errors, passing a file to be parsed. checkCompilationErrors() { local file=$1 + hadoopVersion="" + if [ "$#" -ne 1 ]; then + hadoopVersion="with Hadoop version $2" + fi COMPILATION_ERROR=false eval $(awk '/ERROR/ {print "COMPILATION_ERROR=true"}' $file) if $COMPILATION_ERROR ; then ERRORS=$($AWK '/ERROR/ { print $0 }' $file) echo "======================================================================" - echo "There are compilation errors." + echo "There are compilation errors $hadoopVersion." echo "======================================================================" echo "$ERRORS" JIRA_COMMENT="$JIRA_COMMENT - {color:red}-1 javac{color}. The patch appears to cause mvn compile goal to fail. + {color:red}-1 javac{color}. The patch appears to cause mvn compile goal to fail $hadoopVersion. Compilation errors resume: $ERRORS @@ -504,6 +508,30 @@ $JIRA_COMMENT_FOOTER" return 0 } +checkBuildWithHadoopVersions() { + echo "" + echo "" + echo "======================================================================" + echo "======================================================================" + echo " Building with all supported Hadoop versions ." + echo "======================================================================" + echo "======================================================================" + echo "" + echo "" + export MAVEN_OPTS="${MAVEN_OPTS}" + for HADOOP2_VERSION in $HADOOP2_VERSIONS ; do + echo "$MVN clean install -DskipTests -D${PROJECT_NAME}PatchProcess -Dhadoop-two.version=$HADOOP2_VERSION > $PATCH_DIR/patchJavacWithHadoop-$HADOOP2_VERSION.txt 2>&1" + $MVN clean install -DskipTests -D${PROJECT_NAME}PatchProcess -Dhadoop-two.version=$HADOOP2_VERSION > $PATCH_DIR/patchJavacWithHadoop-$HADOOP2_VERSION.txt 2>&1 + checkCompilationErrors $PATCH_DIR/patchJavacWithHadoop-$HADOOP2_VERSION.txt $HADOOP2_VERSION + done + + # TODO: add Hadoop3 versions and compilation here when we get the hadoop.profile=3.0 working + + JIRA_COMMENT="$JIRA_COMMENT + {color:green}+1 hadoop versions{color}. The patch compiles with all supported hadoop versions ($HADOOP2_VERSIONS)" + return 0 +} + ############################################################################### ### Check there are no changes in the number of Javac warnings checkJavacWarnings () { @@ -956,6 +984,8 @@ fi checkAntiPatterns (( RESULT = RESULT + $? )) +checkBuildWithHadoopVersions +(( RESULT = RESULT + $? )) checkJavacWarnings (( RESULT = RESULT + $? )) checkProtocErrors From b51f5dc120d322786eb09905359e6d4143bd190e Mon Sep 17 00:00:00 2001 From: Misty Stanley-Jones Date: Thu, 12 Feb 2015 14:10:32 +1000 Subject: [PATCH 003/329] HBASE-12168 Document Rest gateway SPNEGO-based authentication for client --- src/main/asciidoc/_chapters/security.adoc | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/asciidoc/_chapters/security.adoc b/src/main/asciidoc/_chapters/security.adoc index 9cffbdb605e..072f2510833 100644 --- a/src/main/asciidoc/_chapters/security.adoc +++ b/src/main/asciidoc/_chapters/security.adoc @@ -270,8 +270,6 @@ Add the following to the `hbase-site.xml` file for every REST gateway: Substitute the appropriate credential and keytab for _$USER_ and _$KEYTAB_ respectively. The REST gateway will authenticate with HBase using the supplied credential. -No authentication will be performed by the REST gateway itself. -All client access via the REST gateway will use the REST gateway's credential and have its privilege. In order to use the REST API principal to interact with HBase, it is also necessary to add the `hbase.rest.kerberos.principal` to the `_acl_` table. For example, to give the REST API principal, `rest_server`, administrative access, a command such as this one will suffice: @@ -283,8 +281,30 @@ grant 'rest_server', 'RWCA' For more information about ACLs, please see the <> section -It should be possible for clients to authenticate with the HBase cluster through the REST gateway in a pass-through manner via SPNEGO HTTP authentication. -This is future work. +HBase REST gateway supports link:http://hadoop.apache.org/docs/stable/hadoop-auth/index.html[SPNEGO HTTP authentication] for client access to the gateway. +To enable REST gateway Kerberos authentication for client access, add the following to the `hbase-site.xml` file for every REST gateway. + +[source,xml] +---- + + hbase.rest.authentication.type + kerberos + + + hbase.rest.authentication.kerberos.principal + HTTP/_HOST@HADOOP.LOCALDOMAIN + + + hbase.rest.authentication.kerberos.keytab + $KEYTAB + +---- + +Substitute the keytab for HTTP for _$KEYTAB_. + +HBase REST gateway supports different 'hbase.rest.authentication.type': simple, kerberos. +You can also implement a custom authentication by implemening Hadoop AuthenticationHandler, then specify the full class name as 'hbase.rest.authentication.type' value. +For more information, refer to link:http://hadoop.apache.org/docs/stable/hadoop-auth/index.html[SPNEGO HTTP authentication]. [[security.rest.gateway]] === REST Gateway Impersonation Configuration From 8aa4eeb1c87104710fb55c1f0a2bf48644a2735d Mon Sep 17 00:00:00 2001 From: stack Date: Wed, 11 Feb 2015 20:46:41 -0800 Subject: [PATCH 004/329] HBASE-13007 Fix the test timeouts being caused by ChoreService (Jonathan Lawlor); ADDENDUM --- .../org/apache/hadoop/hbase/ChoreService.java | 2 +- .../apache/hadoop/hbase/ScheduledChore.java | 45 +++++++----- .../apache/hadoop/hbase/TestChoreService.java | 71 +------------------ 3 files changed, 32 insertions(+), 86 deletions(-) diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/ChoreService.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/ChoreService.java index 5e01c39bbd4..f5658415e54 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/ChoreService.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/ChoreService.java @@ -161,7 +161,7 @@ public class ChoreService implements ChoreServicer { @Override public synchronized void cancelChore(ScheduledChore chore) { - cancelChore(chore, false); + cancelChore(chore, true); } @Override diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/ScheduledChore.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/ScheduledChore.java index ccedcc76bce..16d080b4692 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/ScheduledChore.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/ScheduledChore.java @@ -164,26 +164,19 @@ public abstract class ScheduledChore implements Runnable { this.timeUnit = unit; } - synchronized void resetState() { - timeOfLastRun = -1; - timeOfThisRun = -1; - initialChoreComplete = false; - } - /** * @see java.lang.Thread#run() */ @Override - public synchronized void run() { - timeOfLastRun = timeOfThisRun; - timeOfThisRun = System.currentTimeMillis(); + public void run() { + updateTimeTrackingBeforeRun(); if (missedStartTime() && isScheduled()) { - choreServicer.onChoreMissedStartTime(this); + onChoreMissedStartTime(); if (LOG.isInfoEnabled()) LOG.info("Chore: " + getName() + " missed its start time"); - } else if (stopper.isStopped() || choreServicer == null || !isScheduled()) { - cancel(); + } else if (stopper.isStopped() || !isScheduled()) { + cancel(false); cleanup(); - LOG.info("Chore: " + getName() + " was stopped"); + if (LOG.isInfoEnabled()) LOG.info("Chore: " + getName() + " was stopped"); } else { try { if (!initialChoreComplete) { @@ -192,15 +185,33 @@ public abstract class ScheduledChore implements Runnable { chore(); } } catch (Throwable t) { - LOG.error("Caught error", t); + if (LOG.isErrorEnabled()) LOG.error("Caught error", t); if (this.stopper.isStopped()) { - cancel(); + cancel(false); cleanup(); } } } } + /** + * Update our time tracking members. Called at the start of an execution of this chore's run() + * method so that a correct decision can be made as to whether or not we missed the start time + */ + private synchronized void updateTimeTrackingBeforeRun() { + timeOfLastRun = timeOfThisRun; + timeOfThisRun = System.currentTimeMillis(); + } + + /** + * Notify the ChoreService that this chore has missed its start time. Allows the ChoreService to + * make the decision as to whether or not it would be worthwhile to increase the number of core + * pool threads + */ + private synchronized void onChoreMissedStartTime() { + if (choreServicer != null) choreServicer.onChoreMissedStartTime(this); + } + /** * @return How long has it been since this chore last run. Useful for checking if the chore has * missed its scheduled start time by too large of a margin @@ -248,7 +259,7 @@ public abstract class ScheduledChore implements Runnable { } public synchronized void cancel() { - cancel(false); + cancel(true); } public synchronized void cancel(boolean mayInterruptIfRunning) { @@ -317,7 +328,7 @@ public abstract class ScheduledChore implements Runnable { * Override to run a task before we start looping. * @return true if initial chore was successful */ - protected synchronized boolean initialChore() { + protected boolean initialChore() { // Default does nothing return true; } diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/TestChoreService.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/TestChoreService.java index 811d4d9564c..3529e9403b8 100644 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/TestChoreService.java +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/TestChoreService.java @@ -423,7 +423,7 @@ public class TestChoreService { shutdownService(service); } - @Test (timeout=20000) + @Test(timeout = 30000) public void testCorePoolDecrease() throws InterruptedException { final int initialCorePoolSize = 3; ChoreService service = new ChoreService("testCorePoolDecrease", initialCorePoolSize); @@ -456,6 +456,8 @@ public class TestChoreService { service.getNumberOfScheduledChores(), service.getCorePoolSize()); assertEquals(service.getNumberOfChoresMissingStartTime(), 5); + // Now we begin to cancel the chores that caused an increase in the core thread pool of the + // ChoreService. These cancellations should cause a decrease in the core thread pool. slowChore5.cancel(); Thread.sleep(chorePeriod * 10); assertEquals(Math.max(ChoreService.MIN_CORE_POOL_SIZE, service.getNumberOfScheduledChores()), @@ -486,44 +488,6 @@ public class TestChoreService { service.getCorePoolSize()); assertEquals(service.getNumberOfChoresMissingStartTime(), 0); - slowChore1.resetState(); - service.scheduleChore(slowChore1); - Thread.sleep(chorePeriod * 10); - assertEquals(Math.max(ChoreService.MIN_CORE_POOL_SIZE, service.getNumberOfScheduledChores()), - service.getCorePoolSize()); - assertEquals(service.getNumberOfChoresMissingStartTime(), 1); - - slowChore2.resetState(); - service.scheduleChore(slowChore2); - Thread.sleep(chorePeriod * 10); - assertEquals(Math.max(ChoreService.MIN_CORE_POOL_SIZE, service.getNumberOfScheduledChores()), - service.getCorePoolSize()); - assertEquals(service.getNumberOfChoresMissingStartTime(), 2); - - DoNothingChore fastChore1 = new DoNothingChore("fastChore1", chorePeriod); - service.scheduleChore(fastChore1); - Thread.sleep(chorePeriod * 10); - assertEquals(service.getNumberOfChoresMissingStartTime(), 2); - assertEquals("Should increase", 3, service.getCorePoolSize()); - - DoNothingChore fastChore2 = new DoNothingChore("fastChore2", chorePeriod); - service.scheduleChore(fastChore2); - Thread.sleep(chorePeriod * 10); - assertEquals(service.getNumberOfChoresMissingStartTime(), 2); - assertEquals("Should increase", 3, service.getCorePoolSize()); - - DoNothingChore fastChore3 = new DoNothingChore("fastChore3", chorePeriod); - service.scheduleChore(fastChore3); - Thread.sleep(chorePeriod * 10); - assertEquals(service.getNumberOfChoresMissingStartTime(), 2); - assertEquals("Should not change", 3, service.getCorePoolSize()); - - DoNothingChore fastChore4 = new DoNothingChore("fastChore4", chorePeriod); - service.scheduleChore(fastChore4); - Thread.sleep(chorePeriod * 10); - assertEquals(service.getNumberOfChoresMissingStartTime(), 2); - assertEquals("Should not change", 3, service.getCorePoolSize()); - shutdownService(service); } @@ -657,35 +621,6 @@ public class TestChoreService { shutdownService(service); } - @Test (timeout=20000) - public void testScheduledChoreReset() throws InterruptedException { - final int period = 100; - ChoreService service = new ChoreService("testScheduledChoreReset"); - ScheduledChore chore = new DoNothingChore("sampleChore", period); - - // TRUE - assertTrue(!chore.isInitialChoreComplete()); - assertTrue(chore.getTimeOfLastRun() == -1); - assertTrue(chore.getTimeOfThisRun() == -1); - - service.scheduleChore(chore); - Thread.sleep(5 * period); - - // FALSE - assertFalse(!chore.isInitialChoreComplete()); - assertFalse(chore.getTimeOfLastRun() == -1); - assertFalse(chore.getTimeOfThisRun() == -1); - - chore.resetState(); - - // TRUE - assertTrue(!chore.isInitialChoreComplete()); - assertTrue(chore.getTimeOfLastRun() == -1); - assertTrue(chore.getTimeOfThisRun() == -1); - - shutdownService(service); - } - @Test (timeout=20000) public void testChangingChoreServices() throws InterruptedException { final int period = 100; From b7f6a45803d6b56a2ff56ebcac6a78aee100b409 Mon Sep 17 00:00:00 2001 From: Misty Stanley-Jones Date: Tue, 10 Feb 2015 14:28:06 +1000 Subject: [PATCH 005/329] HBASE-11910 Document preemptive Call Me Maybe results --- src/main/asciidoc/_chapters/performance.adoc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/asciidoc/_chapters/performance.adoc b/src/main/asciidoc/_chapters/performance.adoc index 48b647ce563..2155d52b6c2 100644 --- a/src/main/asciidoc/_chapters/performance.adoc +++ b/src/main/asciidoc/_chapters/performance.adoc @@ -100,6 +100,17 @@ Using 10Gbe links between racks will greatly increase performance, and assuming Are all the network interfaces functioning correctly? Are you sure? See the Troubleshooting Case Study in <>. +[[perf.network.call_me_maybe]] +=== Network Consistency and Partition Tolerance +The link:http://en.wikipedia.org/wiki/CAP_theorem[CAP Theorem] states that a distributed system can maintain two out of the following three charateristics: +- *C*onsistency -- all nodes see the same data. +- *A*vailability -- every request receives a response about whether it succeeded or failed. +- *P*artition tolerance -- the system continues to operate even if some of its components become unavailable to the others. + +HBase favors consistency and partition tolerance, where a decision has to be made. Coda Hale explains why partition tolerance is so important, in http://codahale.com/you-cant-sacrifice-partition-tolerance/. + +Robert Yokota used an automated testing framework called link:https://aphyr.com/tags/jepsen[Jepson] to test HBase's partition tolerance in the face of network partitions, using techniques modeled after Aphyr's link:https://aphyr.com/posts/281-call-me-maybe-carly-rae-jepsen-and-the-perils-of-network-partitions[Call Me Maybe] series. The results, available as a link:http://eng.yammer.com/call-me-maybe-hbase/[blog post] and an link:http://eng.yammer.com/call-me-maybe-hbase-addendum/[addendum], show that HBase performs correctly. + [[jvm]] == Java From 02759f2d8c58092421f6b34f3585256baaf44f9d Mon Sep 17 00:00:00 2001 From: Bhupendra Date: Thu, 12 Feb 2015 15:36:07 +0530 Subject: [PATCH 006/329] HBASE-13026: Wrong error message in case incorrect snapshot name OR Incorrect table name Signed-off-by: Matteo Bertozzi --- .../src/main/java/org/apache/hadoop/hbase/TableName.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java index c560a4352e6..51671579c9c 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java @@ -180,10 +180,11 @@ public final class TableName implements Comparable { } if (qualifierName[start] == '.' || qualifierName[start] == '-') { - throw new IllegalArgumentException("Illegal first character <" + qualifierName[0] + - "> at 0. Namespaces can only start with alphanumeric " + + throw new IllegalArgumentException("Illegal first character <" + qualifierName[start] + + "> at 0. " + (isSnapshot ? "Snapshot" : "User-space table") + + " qualifiers can only start with 'alphanumeric " + "characters': i.e. [a-zA-Z_0-9]: " + - Bytes.toString(qualifierName)); + Bytes.toString(qualifierName, start, end)); } for (int i = start; i < end; i++) { if (Character.isLetterOrDigit(qualifierName[i]) || @@ -194,7 +195,7 @@ public final class TableName implements Comparable { } throw new IllegalArgumentException("Illegal character code:" + qualifierName[i] + ", <" + (char) qualifierName[i] + "> at " + i + - ". " + (isSnapshot ? "snapshot" : "User-space table") + + ". " + (isSnapshot ? "Snapshot" : "User-space table") + " qualifiers can only contain " + "'alphanumeric characters': i.e. [a-zA-Z_0-9-.]: " + Bytes.toString(qualifierName, start, end)); From fc2e849cd35ffef0cf91b80d42c983702e456199 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Thu, 12 Feb 2015 10:13:38 -0800 Subject: [PATCH 007/329] HBASE-13008 Better default for hbase.regionserver.regionSplitLimit parameter (Srikanth Srungarapu) --- hbase-common/src/main/resources/hbase-default.xml | 9 +++++++++ .../hbase/regionserver/CompactSplitThread.java | 12 ++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/hbase-common/src/main/resources/hbase-default.xml b/hbase-common/src/main/resources/hbase-default.xml index 2985685efce..6dcd80d0fa0 100644 --- a/hbase-common/src/main/resources/hbase-default.xml +++ b/hbase-common/src/main/resources/hbase-default.xml @@ -333,6 +333,15 @@ possible configurations would overwhelm and obscure the important. DelimitedKeyPrefixRegionSplitPolicy, KeyPrefixRegionSplitPolicy etc. + + hbase.regionserver.regionSplitLimit + 1000 + + Limit for the number of regions after which no more region splitting should take place. + This is not hard limit for the number of regions but acts as a guideline for the regionserver + to stop splitting after a certain limit. Default is set to 1000. + + diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/CompactSplitThread.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/CompactSplitThread.java index 3e5003661f3..e891d007e6d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/CompactSplitThread.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/CompactSplitThread.java @@ -75,6 +75,10 @@ public class CompactSplitThread implements CompactionRequestor, PropagatingConfi // Configuration keys for merge threads public final static String MERGE_THREADS = "hbase.regionserver.thread.merge"; public final static int MERGE_THREADS_DEFAULT = 1; + + public static final String REGION_SERVER_REGION_SPLIT_LIMIT = + "hbase.regionserver.regionSplitLimit"; + public static final int DEFAULT_REGION_SERVER_REGION_SPLIT_LIMIT= 1000; private final HRegionServer server; private final Configuration conf; @@ -98,8 +102,8 @@ public class CompactSplitThread implements CompactionRequestor, PropagatingConfi super(); this.server = server; this.conf = server.getConfiguration(); - this.regionSplitLimit = conf.getInt("hbase.regionserver.regionSplitLimit", - Integer.MAX_VALUE); + this.regionSplitLimit = conf.getInt(REGION_SERVER_REGION_SPLIT_LIMIT, + DEFAULT_REGION_SERVER_REGION_SPLIT_LIMIT); int largeThreads = Math.max(1, conf.getInt( LARGE_COMPACTION_THREADS, LARGE_COMPACTION_THREADS_DEFAULT)); @@ -427,6 +431,10 @@ public class CompactSplitThread implements CompactionRequestor, PropagatingConfi } private boolean shouldSplitRegion() { + if(server.getNumberOfOnlineRegions() > 0.9*regionSplitLimit) { + LOG.warn("Total number of regions is approaching the upper limit " + regionSplitLimit + ". " + + "Please consider taking a look at http://hbase.apache.org/book.html#ops.regionmgt"); + } return (regionSplitLimit > server.getNumberOfOnlineRegions()); } From e0160d69379401a7381ddaa478b53c9833379fe2 Mon Sep 17 00:00:00 2001 From: tedyu Date: Thu, 12 Feb 2015 14:02:54 -0800 Subject: [PATCH 008/329] HBASE-13029 Table state should be deleted from META as a last operation in DeleteTableHandler (Andrey Stepachev) --- .../hadoop/hbase/master/handler/DeleteTableHandler.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/handler/DeleteTableHandler.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/handler/DeleteTableHandler.java index 092a17d84c1..7fcda15e726 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/handler/DeleteTableHandler.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/handler/DeleteTableHandler.java @@ -128,12 +128,13 @@ public class DeleteTableHandler extends TableEventHandler { LOG.debug("Removing '" + tableName + "' from region states."); am.getRegionStates().tableDeleted(tableName); - // 5. If entry for this table states, remove it. + + // 5.Clean any remaining rows for this table. + cleanAnyRemainingRows(); + + // 6. If entry for this table states, remove it. LOG.debug("Marking '" + tableName + "' as deleted."); am.getTableStateManager().setDeletedTable(tableName); - - // 6.Clean any remaining rows for this table. - cleanAnyRemainingRows(); } /** From 16ed345191f5b0977ff34d80b34da700c41b624e Mon Sep 17 00:00:00 2001 From: Ashish Singhi Date: Thu, 12 Feb 2015 14:50:01 -0800 Subject: [PATCH 009/329] HBASE-9531 a command line (hbase shell) interface to retreive the replication metrics and show replication lag Signed-off-by: Andrew Purtell --- .../org/apache/hadoop/hbase/ServerLoad.java | 27 +- .../hadoop/hbase/protobuf/ProtobufUtil.java | 24 + .../replication/ReplicationLoadSink.java | 36 + .../replication/ReplicationLoadSource.java | 53 + .../MetricsReplicationSinkSource.java | 1 + .../MetricsReplicationSourceSource.java | 1 + .../MetricsReplicationGlobalSourceSource.java | 5 + .../MetricsReplicationSinkSourceImpl.java | 5 + .../MetricsReplicationSourceSourceImpl.java | 5 + .../generated/ClusterStatusProtos.java | 2335 ++++++++++++++++- .../src/main/protobuf/ClusterStatus.proto | 23 + .../hbase/regionserver/HRegionServer.java | 17 + .../regionserver/ReplicationService.java | 8 +- .../replication/regionserver/MetricsSink.java | 17 + .../regionserver/MetricsSource.java | 34 + .../replication/regionserver/Replication.java | 32 +- .../regionserver/ReplicationLoad.java | 151 ++ .../regionserver/ReplicationSink.java | 8 + .../regionserver/ReplicationSource.java | 8 + .../TestReplicationSmallTests.java | 45 + hbase-shell/src/main/ruby/hbase/admin.rb | 42 +- .../src/main/ruby/shell/commands/status.rb | 9 +- hbase-shell/src/test/ruby/hbase/admin_test.rb | 12 + hbase-shell/src/test/ruby/test_helper.rb | 4 + 24 files changed, 2870 insertions(+), 32 deletions(-) create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/replication/ReplicationLoadSink.java create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/replication/ReplicationLoadSource.java create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationLoad.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerLoad.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerLoad.java index 9141659b514..7813b4aa65c 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerLoad.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ServerLoad.java @@ -28,8 +28,11 @@ import java.util.TreeSet; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; +import org.apache.hadoop.hbase.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos; import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.Coprocessor; +import org.apache.hadoop.hbase.replication.ReplicationLoadSink; +import org.apache.hadoop.hbase.replication.ReplicationLoadSource; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Strings; @@ -52,7 +55,7 @@ public class ServerLoad { private int totalStaticBloomSizeKB = 0; private long totalCompactingKVs = 0; private long currentCompactedKVs = 0; - + public ServerLoad(ClusterStatusProtos.ServerLoad serverLoad) { this.serverLoad = serverLoad; for (ClusterStatusProtos.RegionLoad rl: serverLoad.getRegionLoadsList()) { @@ -70,7 +73,7 @@ public class ServerLoad { totalCompactingKVs += rl.getTotalCompactingKVs(); currentCompactedKVs += rl.getCurrentCompactedKVs(); } - + } // NOTE: Function name cannot start with "get" because then an OpenDataException is thrown because @@ -177,6 +180,26 @@ public class ServerLoad { return serverLoad.getInfoServerPort(); } + /** + * Call directly from client such as hbase shell + * @return the list of ReplicationLoadSource + */ + public List getReplicationLoadSourceList() { + return ProtobufUtil.toReplicationLoadSourceList(serverLoad.getReplLoadSourceList()); + } + + /** + * Call directly from client such as hbase shell + * @return ReplicationLoadSink + */ + public ReplicationLoadSink getReplicationLoadSink() { + if (serverLoad.hasReplLoadSink()) { + return ProtobufUtil.toReplicationLoadSink(serverLoad.getReplLoadSink()); + } else { + return null; + } + } + /** * Originally, this method factored in the effect of requests going to the * server as well. However, this does not interact very well with the current diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java index caae1bbbaec..43e91d23550 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java @@ -104,6 +104,7 @@ import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.Col import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.DeleteType; import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType; import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanRequest; +import org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos; import org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.RegionLoad; import org.apache.hadoop.hbase.protobuf.generated.ComparatorProtos; import org.apache.hadoop.hbase.protobuf.generated.FilterProtos; @@ -130,6 +131,8 @@ import org.apache.hadoop.hbase.protobuf.generated.WALProtos.StoreDescriptor; import org.apache.hadoop.hbase.quotas.QuotaScope; import org.apache.hadoop.hbase.quotas.QuotaType; import org.apache.hadoop.hbase.quotas.ThrottleType; +import org.apache.hadoop.hbase.replication.ReplicationLoadSink; +import org.apache.hadoop.hbase.replication.ReplicationLoadSource; import org.apache.hadoop.hbase.security.access.Permission; import org.apache.hadoop.hbase.security.access.TablePermission; import org.apache.hadoop.hbase.security.access.UserPermission; @@ -2994,4 +2997,25 @@ public final class ProtobufUtil { return desc.build(); } + + public static ReplicationLoadSink toReplicationLoadSink( + ClusterStatusProtos.ReplicationLoadSink cls) { + return new ReplicationLoadSink(cls.getAgeOfLastAppliedOp(), cls.getTimeStampsOfLastAppliedOp()); + } + + public static ReplicationLoadSource toReplicationLoadSource( + ClusterStatusProtos.ReplicationLoadSource cls) { + return new ReplicationLoadSource(cls.getPeerID(), cls.getAgeOfLastShippedOp(), + cls.getSizeOfLogQueue(), cls.getTimeStampOfLastShippedOp(), cls.getReplicationLag()); + } + + public static List toReplicationLoadSourceList( + List clsList) { + ArrayList rlsList = new ArrayList(); + for (ClusterStatusProtos.ReplicationLoadSource cls : clsList) { + rlsList.add(toReplicationLoadSource(cls)); + } + return rlsList; + } + } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/replication/ReplicationLoadSink.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/replication/ReplicationLoadSink.java new file mode 100644 index 00000000000..63fe3349585 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/replication/ReplicationLoadSink.java @@ -0,0 +1,36 @@ +/** + * 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.hbase.replication; + +import org.apache.hadoop.hbase.classification.InterfaceAudience; + +/** + * A HBase ReplicationLoad to present MetricsSink information + */ +@InterfaceAudience.Private +public class ReplicationLoadSink { + private long ageOfLastAppliedOp; + private long timeStampsOfLastAppliedOp; + + public ReplicationLoadSink(long age, long timeStamp) { + this.ageOfLastAppliedOp = age; + this.timeStampsOfLastAppliedOp = timeStamp; + } + + public long getAgeOfLastAppliedOp() { + return this.ageOfLastAppliedOp; + } + + public long getTimeStampsOfLastAppliedOp() { + return this.timeStampsOfLastAppliedOp; + } + +} \ No newline at end of file diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/replication/ReplicationLoadSource.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/replication/ReplicationLoadSource.java new file mode 100644 index 00000000000..bfd15990be1 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/replication/ReplicationLoadSource.java @@ -0,0 +1,53 @@ +/** + * 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.hbase.replication; + +import org.apache.hadoop.hbase.classification.InterfaceAudience; + +/** + * A HBase ReplicationLoad to present MetricsSource information + */ +@InterfaceAudience.Private +public class ReplicationLoadSource { + private String peerID; + private long ageOfLastShippedOp; + private int sizeOfLogQueue; + private long timeStampOfLastShippedOp; + private long replicationLag; + + public ReplicationLoadSource(String id, long age, int size, long timeStamp, long lag) { + this.peerID = id; + this.ageOfLastShippedOp = age; + this.sizeOfLogQueue = size; + this.timeStampOfLastShippedOp = timeStamp; + this.replicationLag = lag; + } + + public String getPeerID() { + return this.peerID; + } + + public long getAgeOfLastShippedOp() { + return this.ageOfLastShippedOp; + } + + public long getSizeOfLogQueue() { + return this.sizeOfLogQueue; + } + + public long getTimeStampOfLastShippedOp() { + return this.timeStampOfLastShippedOp; + } + + public long getReplicationLag() { + return this.replicationLag; + } +} \ No newline at end of file diff --git a/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSinkSource.java b/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSinkSource.java index 805dfcaf537..698a59a2acb 100644 --- a/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSinkSource.java +++ b/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSinkSource.java @@ -26,4 +26,5 @@ public interface MetricsReplicationSinkSource { void setLastAppliedOpAge(long age); void incrAppliedBatches(long batches); void incrAppliedOps(long batchsize); + long getLastAppliedOpAge(); } diff --git a/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSourceSource.java b/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSourceSource.java index 66d265a90ba..fecf191a063 100644 --- a/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSourceSource.java +++ b/hbase-hadoop-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSourceSource.java @@ -43,4 +43,5 @@ public interface MetricsReplicationSourceSource { void incrLogReadInBytes(long size); void incrLogReadInEdits(long size); void clear(); + long getLastShippedAge(); } diff --git a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationGlobalSourceSource.java b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationGlobalSourceSource.java index a210171577c..6dace107f99 100644 --- a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationGlobalSourceSource.java +++ b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationGlobalSourceSource.java @@ -95,4 +95,9 @@ public class MetricsReplicationGlobalSourceSource implements MetricsReplicationS @Override public void clear() { } + + @Override + public long getLastShippedAge() { + return ageOfLastShippedOpGauge.value(); + } } diff --git a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSinkSourceImpl.java b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSinkSourceImpl.java index 3025e3e7724..14212ba0869 100644 --- a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSinkSourceImpl.java +++ b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSinkSourceImpl.java @@ -44,4 +44,9 @@ public class MetricsReplicationSinkSourceImpl implements MetricsReplicationSinkS @Override public void incrAppliedOps(long batchsize) { opsCounter.incr(batchsize); } + + @Override + public long getLastAppliedOpAge() { + return ageGauge.value(); + } } diff --git a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSourceSourceImpl.java b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSourceSourceImpl.java index 89ef4de920a..1422e7e1cd3 100644 --- a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSourceSourceImpl.java +++ b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsReplicationSourceSourceImpl.java @@ -125,4 +125,9 @@ public class MetricsReplicationSourceSourceImpl implements MetricsReplicationSou rms.removeMetric(logEditsFilteredKey); } + + @Override + public long getLastShippedAge() { + return ageOfLastShippedOpGauge.value(); + } } diff --git a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/ClusterStatusProtos.java b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/ClusterStatusProtos.java index 6dc48fa12db..0d69d7a1adb 100644 --- a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/ClusterStatusProtos.java +++ b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/ClusterStatusProtos.java @@ -4438,6 +4438,1455 @@ public final class ClusterStatusProtos { // @@protoc_insertion_point(class_scope:RegionLoad) } + public interface ReplicationLoadSinkOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required uint64 ageOfLastAppliedOp = 1; + /** + * required uint64 ageOfLastAppliedOp = 1; + */ + boolean hasAgeOfLastAppliedOp(); + /** + * required uint64 ageOfLastAppliedOp = 1; + */ + long getAgeOfLastAppliedOp(); + + // required uint64 timeStampsOfLastAppliedOp = 2; + /** + * required uint64 timeStampsOfLastAppliedOp = 2; + */ + boolean hasTimeStampsOfLastAppliedOp(); + /** + * required uint64 timeStampsOfLastAppliedOp = 2; + */ + long getTimeStampsOfLastAppliedOp(); + } + /** + * Protobuf type {@code ReplicationLoadSink} + */ + public static final class ReplicationLoadSink extends + com.google.protobuf.GeneratedMessage + implements ReplicationLoadSinkOrBuilder { + // Use ReplicationLoadSink.newBuilder() to construct. + private ReplicationLoadSink(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private ReplicationLoadSink(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final ReplicationLoadSink defaultInstance; + public static ReplicationLoadSink getDefaultInstance() { + return defaultInstance; + } + + public ReplicationLoadSink getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ReplicationLoadSink( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + ageOfLastAppliedOp_ = input.readUInt64(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + timeStampsOfLastAppliedOp_ = input.readUInt64(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSink_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSink_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.class, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public ReplicationLoadSink parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ReplicationLoadSink(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // required uint64 ageOfLastAppliedOp = 1; + public static final int AGEOFLASTAPPLIEDOP_FIELD_NUMBER = 1; + private long ageOfLastAppliedOp_; + /** + * required uint64 ageOfLastAppliedOp = 1; + */ + public boolean hasAgeOfLastAppliedOp() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required uint64 ageOfLastAppliedOp = 1; + */ + public long getAgeOfLastAppliedOp() { + return ageOfLastAppliedOp_; + } + + // required uint64 timeStampsOfLastAppliedOp = 2; + public static final int TIMESTAMPSOFLASTAPPLIEDOP_FIELD_NUMBER = 2; + private long timeStampsOfLastAppliedOp_; + /** + * required uint64 timeStampsOfLastAppliedOp = 2; + */ + public boolean hasTimeStampsOfLastAppliedOp() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required uint64 timeStampsOfLastAppliedOp = 2; + */ + public long getTimeStampsOfLastAppliedOp() { + return timeStampsOfLastAppliedOp_; + } + + private void initFields() { + ageOfLastAppliedOp_ = 0L; + timeStampsOfLastAppliedOp_ = 0L; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasAgeOfLastAppliedOp()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasTimeStampsOfLastAppliedOp()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeUInt64(1, ageOfLastAppliedOp_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeUInt64(2, timeStampsOfLastAppliedOp_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(1, ageOfLastAppliedOp_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(2, timeStampsOfLastAppliedOp_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink)) { + return super.equals(obj); + } + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink other = (org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink) obj; + + boolean result = true; + result = result && (hasAgeOfLastAppliedOp() == other.hasAgeOfLastAppliedOp()); + if (hasAgeOfLastAppliedOp()) { + result = result && (getAgeOfLastAppliedOp() + == other.getAgeOfLastAppliedOp()); + } + result = result && (hasTimeStampsOfLastAppliedOp() == other.hasTimeStampsOfLastAppliedOp()); + if (hasTimeStampsOfLastAppliedOp()) { + result = result && (getTimeStampsOfLastAppliedOp() + == other.getTimeStampsOfLastAppliedOp()); + } + result = result && + getUnknownFields().equals(other.getUnknownFields()); + return result; + } + + private int memoizedHashCode = 0; + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptorForType().hashCode(); + if (hasAgeOfLastAppliedOp()) { + hash = (37 * hash) + AGEOFLASTAPPLIEDOP_FIELD_NUMBER; + hash = (53 * hash) + hashLong(getAgeOfLastAppliedOp()); + } + if (hasTimeStampsOfLastAppliedOp()) { + hash = (37 * hash) + TIMESTAMPSOFLASTAPPLIEDOP_FIELD_NUMBER; + hash = (53 * hash) + hashLong(getTimeStampsOfLastAppliedOp()); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ReplicationLoadSink} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSinkOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSink_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSink_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.class, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.Builder.class); + } + + // Construct using org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + ageOfLastAppliedOp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + timeStampsOfLastAppliedOp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSink_descriptor; + } + + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink getDefaultInstanceForType() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.getDefaultInstance(); + } + + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink build() { + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink buildPartial() { + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink result = new org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.ageOfLastAppliedOp_ = ageOfLastAppliedOp_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.timeStampsOfLastAppliedOp_ = timeStampsOfLastAppliedOp_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink) { + return mergeFrom((org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink other) { + if (other == org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.getDefaultInstance()) return this; + if (other.hasAgeOfLastAppliedOp()) { + setAgeOfLastAppliedOp(other.getAgeOfLastAppliedOp()); + } + if (other.hasTimeStampsOfLastAppliedOp()) { + setTimeStampsOfLastAppliedOp(other.getTimeStampsOfLastAppliedOp()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasAgeOfLastAppliedOp()) { + + return false; + } + if (!hasTimeStampsOfLastAppliedOp()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required uint64 ageOfLastAppliedOp = 1; + private long ageOfLastAppliedOp_ ; + /** + * required uint64 ageOfLastAppliedOp = 1; + */ + public boolean hasAgeOfLastAppliedOp() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required uint64 ageOfLastAppliedOp = 1; + */ + public long getAgeOfLastAppliedOp() { + return ageOfLastAppliedOp_; + } + /** + * required uint64 ageOfLastAppliedOp = 1; + */ + public Builder setAgeOfLastAppliedOp(long value) { + bitField0_ |= 0x00000001; + ageOfLastAppliedOp_ = value; + onChanged(); + return this; + } + /** + * required uint64 ageOfLastAppliedOp = 1; + */ + public Builder clearAgeOfLastAppliedOp() { + bitField0_ = (bitField0_ & ~0x00000001); + ageOfLastAppliedOp_ = 0L; + onChanged(); + return this; + } + + // required uint64 timeStampsOfLastAppliedOp = 2; + private long timeStampsOfLastAppliedOp_ ; + /** + * required uint64 timeStampsOfLastAppliedOp = 2; + */ + public boolean hasTimeStampsOfLastAppliedOp() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required uint64 timeStampsOfLastAppliedOp = 2; + */ + public long getTimeStampsOfLastAppliedOp() { + return timeStampsOfLastAppliedOp_; + } + /** + * required uint64 timeStampsOfLastAppliedOp = 2; + */ + public Builder setTimeStampsOfLastAppliedOp(long value) { + bitField0_ |= 0x00000002; + timeStampsOfLastAppliedOp_ = value; + onChanged(); + return this; + } + /** + * required uint64 timeStampsOfLastAppliedOp = 2; + */ + public Builder clearTimeStampsOfLastAppliedOp() { + bitField0_ = (bitField0_ & ~0x00000002); + timeStampsOfLastAppliedOp_ = 0L; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:ReplicationLoadSink) + } + + static { + defaultInstance = new ReplicationLoadSink(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:ReplicationLoadSink) + } + + public interface ReplicationLoadSourceOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required string peerID = 1; + /** + * required string peerID = 1; + */ + boolean hasPeerID(); + /** + * required string peerID = 1; + */ + java.lang.String getPeerID(); + /** + * required string peerID = 1; + */ + com.google.protobuf.ByteString + getPeerIDBytes(); + + // required uint64 ageOfLastShippedOp = 2; + /** + * required uint64 ageOfLastShippedOp = 2; + */ + boolean hasAgeOfLastShippedOp(); + /** + * required uint64 ageOfLastShippedOp = 2; + */ + long getAgeOfLastShippedOp(); + + // required uint32 sizeOfLogQueue = 3; + /** + * required uint32 sizeOfLogQueue = 3; + */ + boolean hasSizeOfLogQueue(); + /** + * required uint32 sizeOfLogQueue = 3; + */ + int getSizeOfLogQueue(); + + // required uint64 timeStampOfLastShippedOp = 4; + /** + * required uint64 timeStampOfLastShippedOp = 4; + */ + boolean hasTimeStampOfLastShippedOp(); + /** + * required uint64 timeStampOfLastShippedOp = 4; + */ + long getTimeStampOfLastShippedOp(); + + // required uint64 replicationLag = 5; + /** + * required uint64 replicationLag = 5; + */ + boolean hasReplicationLag(); + /** + * required uint64 replicationLag = 5; + */ + long getReplicationLag(); + } + /** + * Protobuf type {@code ReplicationLoadSource} + */ + public static final class ReplicationLoadSource extends + com.google.protobuf.GeneratedMessage + implements ReplicationLoadSourceOrBuilder { + // Use ReplicationLoadSource.newBuilder() to construct. + private ReplicationLoadSource(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private ReplicationLoadSource(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final ReplicationLoadSource defaultInstance; + public static ReplicationLoadSource getDefaultInstance() { + return defaultInstance; + } + + public ReplicationLoadSource getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ReplicationLoadSource( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + peerID_ = input.readBytes(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + ageOfLastShippedOp_ = input.readUInt64(); + break; + } + case 24: { + bitField0_ |= 0x00000004; + sizeOfLogQueue_ = input.readUInt32(); + break; + } + case 32: { + bitField0_ |= 0x00000008; + timeStampOfLastShippedOp_ = input.readUInt64(); + break; + } + case 40: { + bitField0_ |= 0x00000010; + replicationLag_ = input.readUInt64(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSource_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSource_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.class, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public ReplicationLoadSource parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ReplicationLoadSource(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // required string peerID = 1; + public static final int PEERID_FIELD_NUMBER = 1; + private java.lang.Object peerID_; + /** + * required string peerID = 1; + */ + public boolean hasPeerID() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required string peerID = 1; + */ + public java.lang.String getPeerID() { + java.lang.Object ref = peerID_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + peerID_ = s; + } + return s; + } + } + /** + * required string peerID = 1; + */ + public com.google.protobuf.ByteString + getPeerIDBytes() { + java.lang.Object ref = peerID_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + peerID_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // required uint64 ageOfLastShippedOp = 2; + public static final int AGEOFLASTSHIPPEDOP_FIELD_NUMBER = 2; + private long ageOfLastShippedOp_; + /** + * required uint64 ageOfLastShippedOp = 2; + */ + public boolean hasAgeOfLastShippedOp() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required uint64 ageOfLastShippedOp = 2; + */ + public long getAgeOfLastShippedOp() { + return ageOfLastShippedOp_; + } + + // required uint32 sizeOfLogQueue = 3; + public static final int SIZEOFLOGQUEUE_FIELD_NUMBER = 3; + private int sizeOfLogQueue_; + /** + * required uint32 sizeOfLogQueue = 3; + */ + public boolean hasSizeOfLogQueue() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * required uint32 sizeOfLogQueue = 3; + */ + public int getSizeOfLogQueue() { + return sizeOfLogQueue_; + } + + // required uint64 timeStampOfLastShippedOp = 4; + public static final int TIMESTAMPOFLASTSHIPPEDOP_FIELD_NUMBER = 4; + private long timeStampOfLastShippedOp_; + /** + * required uint64 timeStampOfLastShippedOp = 4; + */ + public boolean hasTimeStampOfLastShippedOp() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * required uint64 timeStampOfLastShippedOp = 4; + */ + public long getTimeStampOfLastShippedOp() { + return timeStampOfLastShippedOp_; + } + + // required uint64 replicationLag = 5; + public static final int REPLICATIONLAG_FIELD_NUMBER = 5; + private long replicationLag_; + /** + * required uint64 replicationLag = 5; + */ + public boolean hasReplicationLag() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * required uint64 replicationLag = 5; + */ + public long getReplicationLag() { + return replicationLag_; + } + + private void initFields() { + peerID_ = ""; + ageOfLastShippedOp_ = 0L; + sizeOfLogQueue_ = 0; + timeStampOfLastShippedOp_ = 0L; + replicationLag_ = 0L; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasPeerID()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasAgeOfLastShippedOp()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasSizeOfLogQueue()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasTimeStampOfLastShippedOp()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasReplicationLag()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, getPeerIDBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeUInt64(2, ageOfLastShippedOp_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeUInt32(3, sizeOfLogQueue_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeUInt64(4, timeStampOfLastShippedOp_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeUInt64(5, replicationLag_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, getPeerIDBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(2, ageOfLastShippedOp_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(3, sizeOfLogQueue_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(4, timeStampOfLastShippedOp_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(5, replicationLag_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource)) { + return super.equals(obj); + } + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource other = (org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource) obj; + + boolean result = true; + result = result && (hasPeerID() == other.hasPeerID()); + if (hasPeerID()) { + result = result && getPeerID() + .equals(other.getPeerID()); + } + result = result && (hasAgeOfLastShippedOp() == other.hasAgeOfLastShippedOp()); + if (hasAgeOfLastShippedOp()) { + result = result && (getAgeOfLastShippedOp() + == other.getAgeOfLastShippedOp()); + } + result = result && (hasSizeOfLogQueue() == other.hasSizeOfLogQueue()); + if (hasSizeOfLogQueue()) { + result = result && (getSizeOfLogQueue() + == other.getSizeOfLogQueue()); + } + result = result && (hasTimeStampOfLastShippedOp() == other.hasTimeStampOfLastShippedOp()); + if (hasTimeStampOfLastShippedOp()) { + result = result && (getTimeStampOfLastShippedOp() + == other.getTimeStampOfLastShippedOp()); + } + result = result && (hasReplicationLag() == other.hasReplicationLag()); + if (hasReplicationLag()) { + result = result && (getReplicationLag() + == other.getReplicationLag()); + } + result = result && + getUnknownFields().equals(other.getUnknownFields()); + return result; + } + + private int memoizedHashCode = 0; + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptorForType().hashCode(); + if (hasPeerID()) { + hash = (37 * hash) + PEERID_FIELD_NUMBER; + hash = (53 * hash) + getPeerID().hashCode(); + } + if (hasAgeOfLastShippedOp()) { + hash = (37 * hash) + AGEOFLASTSHIPPEDOP_FIELD_NUMBER; + hash = (53 * hash) + hashLong(getAgeOfLastShippedOp()); + } + if (hasSizeOfLogQueue()) { + hash = (37 * hash) + SIZEOFLOGQUEUE_FIELD_NUMBER; + hash = (53 * hash) + getSizeOfLogQueue(); + } + if (hasTimeStampOfLastShippedOp()) { + hash = (37 * hash) + TIMESTAMPOFLASTSHIPPEDOP_FIELD_NUMBER; + hash = (53 * hash) + hashLong(getTimeStampOfLastShippedOp()); + } + if (hasReplicationLag()) { + hash = (37 * hash) + REPLICATIONLAG_FIELD_NUMBER; + hash = (53 * hash) + hashLong(getReplicationLag()); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ReplicationLoadSource} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSourceOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSource_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSource_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.class, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder.class); + } + + // Construct using org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + peerID_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + ageOfLastShippedOp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000002); + sizeOfLogQueue_ = 0; + bitField0_ = (bitField0_ & ~0x00000004); + timeStampOfLastShippedOp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000008); + replicationLag_ = 0L; + bitField0_ = (bitField0_ & ~0x00000010); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.internal_static_ReplicationLoadSource_descriptor; + } + + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource getDefaultInstanceForType() { + return org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.getDefaultInstance(); + } + + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource build() { + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource buildPartial() { + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource result = new org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.peerID_ = peerID_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.ageOfLastShippedOp_ = ageOfLastShippedOp_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.sizeOfLogQueue_ = sizeOfLogQueue_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.timeStampOfLastShippedOp_ = timeStampOfLastShippedOp_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.replicationLag_ = replicationLag_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource) { + return mergeFrom((org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource other) { + if (other == org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.getDefaultInstance()) return this; + if (other.hasPeerID()) { + bitField0_ |= 0x00000001; + peerID_ = other.peerID_; + onChanged(); + } + if (other.hasAgeOfLastShippedOp()) { + setAgeOfLastShippedOp(other.getAgeOfLastShippedOp()); + } + if (other.hasSizeOfLogQueue()) { + setSizeOfLogQueue(other.getSizeOfLogQueue()); + } + if (other.hasTimeStampOfLastShippedOp()) { + setTimeStampOfLastShippedOp(other.getTimeStampOfLastShippedOp()); + } + if (other.hasReplicationLag()) { + setReplicationLag(other.getReplicationLag()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasPeerID()) { + + return false; + } + if (!hasAgeOfLastShippedOp()) { + + return false; + } + if (!hasSizeOfLogQueue()) { + + return false; + } + if (!hasTimeStampOfLastShippedOp()) { + + return false; + } + if (!hasReplicationLag()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required string peerID = 1; + private java.lang.Object peerID_ = ""; + /** + * required string peerID = 1; + */ + public boolean hasPeerID() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required string peerID = 1; + */ + public java.lang.String getPeerID() { + java.lang.Object ref = peerID_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + peerID_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * required string peerID = 1; + */ + public com.google.protobuf.ByteString + getPeerIDBytes() { + java.lang.Object ref = peerID_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + peerID_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * required string peerID = 1; + */ + public Builder setPeerID( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + peerID_ = value; + onChanged(); + return this; + } + /** + * required string peerID = 1; + */ + public Builder clearPeerID() { + bitField0_ = (bitField0_ & ~0x00000001); + peerID_ = getDefaultInstance().getPeerID(); + onChanged(); + return this; + } + /** + * required string peerID = 1; + */ + public Builder setPeerIDBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + peerID_ = value; + onChanged(); + return this; + } + + // required uint64 ageOfLastShippedOp = 2; + private long ageOfLastShippedOp_ ; + /** + * required uint64 ageOfLastShippedOp = 2; + */ + public boolean hasAgeOfLastShippedOp() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required uint64 ageOfLastShippedOp = 2; + */ + public long getAgeOfLastShippedOp() { + return ageOfLastShippedOp_; + } + /** + * required uint64 ageOfLastShippedOp = 2; + */ + public Builder setAgeOfLastShippedOp(long value) { + bitField0_ |= 0x00000002; + ageOfLastShippedOp_ = value; + onChanged(); + return this; + } + /** + * required uint64 ageOfLastShippedOp = 2; + */ + public Builder clearAgeOfLastShippedOp() { + bitField0_ = (bitField0_ & ~0x00000002); + ageOfLastShippedOp_ = 0L; + onChanged(); + return this; + } + + // required uint32 sizeOfLogQueue = 3; + private int sizeOfLogQueue_ ; + /** + * required uint32 sizeOfLogQueue = 3; + */ + public boolean hasSizeOfLogQueue() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * required uint32 sizeOfLogQueue = 3; + */ + public int getSizeOfLogQueue() { + return sizeOfLogQueue_; + } + /** + * required uint32 sizeOfLogQueue = 3; + */ + public Builder setSizeOfLogQueue(int value) { + bitField0_ |= 0x00000004; + sizeOfLogQueue_ = value; + onChanged(); + return this; + } + /** + * required uint32 sizeOfLogQueue = 3; + */ + public Builder clearSizeOfLogQueue() { + bitField0_ = (bitField0_ & ~0x00000004); + sizeOfLogQueue_ = 0; + onChanged(); + return this; + } + + // required uint64 timeStampOfLastShippedOp = 4; + private long timeStampOfLastShippedOp_ ; + /** + * required uint64 timeStampOfLastShippedOp = 4; + */ + public boolean hasTimeStampOfLastShippedOp() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * required uint64 timeStampOfLastShippedOp = 4; + */ + public long getTimeStampOfLastShippedOp() { + return timeStampOfLastShippedOp_; + } + /** + * required uint64 timeStampOfLastShippedOp = 4; + */ + public Builder setTimeStampOfLastShippedOp(long value) { + bitField0_ |= 0x00000008; + timeStampOfLastShippedOp_ = value; + onChanged(); + return this; + } + /** + * required uint64 timeStampOfLastShippedOp = 4; + */ + public Builder clearTimeStampOfLastShippedOp() { + bitField0_ = (bitField0_ & ~0x00000008); + timeStampOfLastShippedOp_ = 0L; + onChanged(); + return this; + } + + // required uint64 replicationLag = 5; + private long replicationLag_ ; + /** + * required uint64 replicationLag = 5; + */ + public boolean hasReplicationLag() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * required uint64 replicationLag = 5; + */ + public long getReplicationLag() { + return replicationLag_; + } + /** + * required uint64 replicationLag = 5; + */ + public Builder setReplicationLag(long value) { + bitField0_ |= 0x00000010; + replicationLag_ = value; + onChanged(); + return this; + } + /** + * required uint64 replicationLag = 5; + */ + public Builder clearReplicationLag() { + bitField0_ = (bitField0_ & ~0x00000010); + replicationLag_ = 0L; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:ReplicationLoadSource) + } + + static { + defaultInstance = new ReplicationLoadSource(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:ReplicationLoadSource) + } + public interface ServerLoadOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -4685,6 +6134,85 @@ public final class ClusterStatusProtos { * */ int getInfoServerPort(); + + // repeated .ReplicationLoadSource replLoadSource = 10; + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + java.util.List + getReplLoadSourceList(); + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource getReplLoadSource(int index); + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + int getReplLoadSourceCount(); + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + java.util.List + getReplLoadSourceOrBuilderList(); + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSourceOrBuilder getReplLoadSourceOrBuilder( + int index); + + // optional .ReplicationLoadSink replLoadSink = 11; + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+     **
+     * The replicationLoadSink for the replication Sink status of this region server.
+     * 
+ */ + boolean hasReplLoadSink(); + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+     **
+     * The replicationLoadSink for the replication Sink status of this region server.
+     * 
+ */ + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink getReplLoadSink(); + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+     **
+     * The replicationLoadSink for the replication Sink status of this region server.
+     * 
+ */ + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSinkOrBuilder getReplLoadSinkOrBuilder(); } /** * Protobuf type {@code ServerLoad} @@ -4788,6 +6316,27 @@ public final class ClusterStatusProtos { infoServerPort_ = input.readUInt32(); break; } + case 82: { + if (!((mutable_bitField0_ & 0x00000200) == 0x00000200)) { + replLoadSource_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000200; + } + replLoadSource_.add(input.readMessage(org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.PARSER, extensionRegistry)); + break; + } + case 90: { + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.Builder subBuilder = null; + if (((bitField0_ & 0x00000080) == 0x00000080)) { + subBuilder = replLoadSink_.toBuilder(); + } + replLoadSink_ = input.readMessage(org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(replLoadSink_); + replLoadSink_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000080; + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -4802,6 +6351,9 @@ public final class ClusterStatusProtos { if (((mutable_bitField0_ & 0x00000020) == 0x00000020)) { coprocessors_ = java.util.Collections.unmodifiableList(coprocessors_); } + if (((mutable_bitField0_ & 0x00000200) == 0x00000200)) { + replLoadSource_ = java.util.Collections.unmodifiableList(replLoadSource_); + } this.unknownFields = unknownFields.build(); makeExtensionsImmutable(); } @@ -5143,6 +6695,104 @@ public final class ClusterStatusProtos { return infoServerPort_; } + // repeated .ReplicationLoadSource replLoadSource = 10; + public static final int REPLLOADSOURCE_FIELD_NUMBER = 10; + private java.util.List replLoadSource_; + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + public java.util.List getReplLoadSourceList() { + return replLoadSource_; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + public java.util.List + getReplLoadSourceOrBuilderList() { + return replLoadSource_; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + public int getReplLoadSourceCount() { + return replLoadSource_.size(); + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource getReplLoadSource(int index) { + return replLoadSource_.get(index); + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+     **
+     * The replicationLoadSource for the replication Source status of this region server.
+     * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSourceOrBuilder getReplLoadSourceOrBuilder( + int index) { + return replLoadSource_.get(index); + } + + // optional .ReplicationLoadSink replLoadSink = 11; + public static final int REPLLOADSINK_FIELD_NUMBER = 11; + private org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink replLoadSink_; + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+     **
+     * The replicationLoadSink for the replication Sink status of this region server.
+     * 
+ */ + public boolean hasReplLoadSink() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+     **
+     * The replicationLoadSink for the replication Sink status of this region server.
+     * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink getReplLoadSink() { + return replLoadSink_; + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+     **
+     * The replicationLoadSink for the replication Sink status of this region server.
+     * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSinkOrBuilder getReplLoadSinkOrBuilder() { + return replLoadSink_; + } + private void initFields() { numberOfRequests_ = 0; totalNumberOfRequests_ = 0; @@ -5153,6 +6803,8 @@ public final class ClusterStatusProtos { reportStartTime_ = 0L; reportEndTime_ = 0L; infoServerPort_ = 0; + replLoadSource_ = java.util.Collections.emptyList(); + replLoadSink_ = org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -5171,6 +6823,18 @@ public final class ClusterStatusProtos { return false; } } + for (int i = 0; i < getReplLoadSourceCount(); i++) { + if (!getReplLoadSource(i).isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } + if (hasReplLoadSink()) { + if (!getReplLoadSink().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } memoizedIsInitialized = 1; return true; } @@ -5205,6 +6869,12 @@ public final class ClusterStatusProtos { if (((bitField0_ & 0x00000040) == 0x00000040)) { output.writeUInt32(9, infoServerPort_); } + for (int i = 0; i < replLoadSource_.size(); i++) { + output.writeMessage(10, replLoadSource_.get(i)); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + output.writeMessage(11, replLoadSink_); + } getUnknownFields().writeTo(output); } @@ -5250,6 +6920,14 @@ public final class ClusterStatusProtos { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(9, infoServerPort_); } + for (int i = 0; i < replLoadSource_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(10, replLoadSource_.get(i)); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(11, replLoadSink_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -5312,6 +6990,13 @@ public final class ClusterStatusProtos { result = result && (getInfoServerPort() == other.getInfoServerPort()); } + result = result && getReplLoadSourceList() + .equals(other.getReplLoadSourceList()); + result = result && (hasReplLoadSink() == other.hasReplLoadSink()); + if (hasReplLoadSink()) { + result = result && getReplLoadSink() + .equals(other.getReplLoadSink()); + } result = result && getUnknownFields().equals(other.getUnknownFields()); return result; @@ -5361,6 +7046,14 @@ public final class ClusterStatusProtos { hash = (37 * hash) + INFO_SERVER_PORT_FIELD_NUMBER; hash = (53 * hash) + getInfoServerPort(); } + if (getReplLoadSourceCount() > 0) { + hash = (37 * hash) + REPLLOADSOURCE_FIELD_NUMBER; + hash = (53 * hash) + getReplLoadSourceList().hashCode(); + } + if (hasReplLoadSink()) { + hash = (37 * hash) + REPLLOADSINK_FIELD_NUMBER; + hash = (53 * hash) + getReplLoadSink().hashCode(); + } hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -5464,6 +7157,8 @@ public final class ClusterStatusProtos { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { getRegionLoadsFieldBuilder(); getCoprocessorsFieldBuilder(); + getReplLoadSourceFieldBuilder(); + getReplLoadSinkFieldBuilder(); } } private static Builder create() { @@ -5498,6 +7193,18 @@ public final class ClusterStatusProtos { bitField0_ = (bitField0_ & ~0x00000080); infoServerPort_ = 0; bitField0_ = (bitField0_ & ~0x00000100); + if (replLoadSourceBuilder_ == null) { + replLoadSource_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000200); + } else { + replLoadSourceBuilder_.clear(); + } + if (replLoadSinkBuilder_ == null) { + replLoadSink_ = org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.getDefaultInstance(); + } else { + replLoadSinkBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000400); return this; } @@ -5572,6 +7279,23 @@ public final class ClusterStatusProtos { to_bitField0_ |= 0x00000040; } result.infoServerPort_ = infoServerPort_; + if (replLoadSourceBuilder_ == null) { + if (((bitField0_ & 0x00000200) == 0x00000200)) { + replLoadSource_ = java.util.Collections.unmodifiableList(replLoadSource_); + bitField0_ = (bitField0_ & ~0x00000200); + } + result.replLoadSource_ = replLoadSource_; + } else { + result.replLoadSource_ = replLoadSourceBuilder_.build(); + } + if (((from_bitField0_ & 0x00000400) == 0x00000400)) { + to_bitField0_ |= 0x00000080; + } + if (replLoadSinkBuilder_ == null) { + result.replLoadSink_ = replLoadSink_; + } else { + result.replLoadSink_ = replLoadSinkBuilder_.build(); + } result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -5661,6 +7385,35 @@ public final class ClusterStatusProtos { if (other.hasInfoServerPort()) { setInfoServerPort(other.getInfoServerPort()); } + if (replLoadSourceBuilder_ == null) { + if (!other.replLoadSource_.isEmpty()) { + if (replLoadSource_.isEmpty()) { + replLoadSource_ = other.replLoadSource_; + bitField0_ = (bitField0_ & ~0x00000200); + } else { + ensureReplLoadSourceIsMutable(); + replLoadSource_.addAll(other.replLoadSource_); + } + onChanged(); + } + } else { + if (!other.replLoadSource_.isEmpty()) { + if (replLoadSourceBuilder_.isEmpty()) { + replLoadSourceBuilder_.dispose(); + replLoadSourceBuilder_ = null; + replLoadSource_ = other.replLoadSource_; + bitField0_ = (bitField0_ & ~0x00000200); + replLoadSourceBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getReplLoadSourceFieldBuilder() : null; + } else { + replLoadSourceBuilder_.addAllMessages(other.replLoadSource_); + } + } + } + if (other.hasReplLoadSink()) { + mergeReplLoadSink(other.getReplLoadSink()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -5678,6 +7431,18 @@ public final class ClusterStatusProtos { return false; } } + for (int i = 0; i < getReplLoadSourceCount(); i++) { + if (!getReplLoadSource(i).isInitialized()) { + + return false; + } + } + if (hasReplLoadSink()) { + if (!getReplLoadSink().isInitialized()) { + + return false; + } + } return true; } @@ -6749,6 +8514,498 @@ public final class ClusterStatusProtos { return this; } + // repeated .ReplicationLoadSource replLoadSource = 10; + private java.util.List replLoadSource_ = + java.util.Collections.emptyList(); + private void ensureReplLoadSourceIsMutable() { + if (!((bitField0_ & 0x00000200) == 0x00000200)) { + replLoadSource_ = new java.util.ArrayList(replLoadSource_); + bitField0_ |= 0x00000200; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSourceOrBuilder> replLoadSourceBuilder_; + + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public java.util.List getReplLoadSourceList() { + if (replLoadSourceBuilder_ == null) { + return java.util.Collections.unmodifiableList(replLoadSource_); + } else { + return replLoadSourceBuilder_.getMessageList(); + } + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public int getReplLoadSourceCount() { + if (replLoadSourceBuilder_ == null) { + return replLoadSource_.size(); + } else { + return replLoadSourceBuilder_.getCount(); + } + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource getReplLoadSource(int index) { + if (replLoadSourceBuilder_ == null) { + return replLoadSource_.get(index); + } else { + return replLoadSourceBuilder_.getMessage(index); + } + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public Builder setReplLoadSource( + int index, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource value) { + if (replLoadSourceBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureReplLoadSourceIsMutable(); + replLoadSource_.set(index, value); + onChanged(); + } else { + replLoadSourceBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public Builder setReplLoadSource( + int index, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder builderForValue) { + if (replLoadSourceBuilder_ == null) { + ensureReplLoadSourceIsMutable(); + replLoadSource_.set(index, builderForValue.build()); + onChanged(); + } else { + replLoadSourceBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public Builder addReplLoadSource(org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource value) { + if (replLoadSourceBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureReplLoadSourceIsMutable(); + replLoadSource_.add(value); + onChanged(); + } else { + replLoadSourceBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public Builder addReplLoadSource( + int index, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource value) { + if (replLoadSourceBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureReplLoadSourceIsMutable(); + replLoadSource_.add(index, value); + onChanged(); + } else { + replLoadSourceBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public Builder addReplLoadSource( + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder builderForValue) { + if (replLoadSourceBuilder_ == null) { + ensureReplLoadSourceIsMutable(); + replLoadSource_.add(builderForValue.build()); + onChanged(); + } else { + replLoadSourceBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public Builder addReplLoadSource( + int index, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder builderForValue) { + if (replLoadSourceBuilder_ == null) { + ensureReplLoadSourceIsMutable(); + replLoadSource_.add(index, builderForValue.build()); + onChanged(); + } else { + replLoadSourceBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public Builder addAllReplLoadSource( + java.lang.Iterable values) { + if (replLoadSourceBuilder_ == null) { + ensureReplLoadSourceIsMutable(); + super.addAll(values, replLoadSource_); + onChanged(); + } else { + replLoadSourceBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public Builder clearReplLoadSource() { + if (replLoadSourceBuilder_ == null) { + replLoadSource_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000200); + onChanged(); + } else { + replLoadSourceBuilder_.clear(); + } + return this; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public Builder removeReplLoadSource(int index) { + if (replLoadSourceBuilder_ == null) { + ensureReplLoadSourceIsMutable(); + replLoadSource_.remove(index); + onChanged(); + } else { + replLoadSourceBuilder_.remove(index); + } + return this; + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder getReplLoadSourceBuilder( + int index) { + return getReplLoadSourceFieldBuilder().getBuilder(index); + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSourceOrBuilder getReplLoadSourceOrBuilder( + int index) { + if (replLoadSourceBuilder_ == null) { + return replLoadSource_.get(index); } else { + return replLoadSourceBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public java.util.List + getReplLoadSourceOrBuilderList() { + if (replLoadSourceBuilder_ != null) { + return replLoadSourceBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(replLoadSource_); + } + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder addReplLoadSourceBuilder() { + return getReplLoadSourceFieldBuilder().addBuilder( + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.getDefaultInstance()); + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder addReplLoadSourceBuilder( + int index) { + return getReplLoadSourceFieldBuilder().addBuilder( + index, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.getDefaultInstance()); + } + /** + * repeated .ReplicationLoadSource replLoadSource = 10; + * + *
+       **
+       * The replicationLoadSource for the replication Source status of this region server.
+       * 
+ */ + public java.util.List + getReplLoadSourceBuilderList() { + return getReplLoadSourceFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSourceOrBuilder> + getReplLoadSourceFieldBuilder() { + if (replLoadSourceBuilder_ == null) { + replLoadSourceBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSource.Builder, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSourceOrBuilder>( + replLoadSource_, + ((bitField0_ & 0x00000200) == 0x00000200), + getParentForChildren(), + isClean()); + replLoadSource_ = null; + } + return replLoadSourceBuilder_; + } + + // optional .ReplicationLoadSink replLoadSink = 11; + private org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink replLoadSink_ = org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.Builder, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSinkOrBuilder> replLoadSinkBuilder_; + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+       **
+       * The replicationLoadSink for the replication Sink status of this region server.
+       * 
+ */ + public boolean hasReplLoadSink() { + return ((bitField0_ & 0x00000400) == 0x00000400); + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+       **
+       * The replicationLoadSink for the replication Sink status of this region server.
+       * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink getReplLoadSink() { + if (replLoadSinkBuilder_ == null) { + return replLoadSink_; + } else { + return replLoadSinkBuilder_.getMessage(); + } + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+       **
+       * The replicationLoadSink for the replication Sink status of this region server.
+       * 
+ */ + public Builder setReplLoadSink(org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink value) { + if (replLoadSinkBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + replLoadSink_ = value; + onChanged(); + } else { + replLoadSinkBuilder_.setMessage(value); + } + bitField0_ |= 0x00000400; + return this; + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+       **
+       * The replicationLoadSink for the replication Sink status of this region server.
+       * 
+ */ + public Builder setReplLoadSink( + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.Builder builderForValue) { + if (replLoadSinkBuilder_ == null) { + replLoadSink_ = builderForValue.build(); + onChanged(); + } else { + replLoadSinkBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000400; + return this; + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+       **
+       * The replicationLoadSink for the replication Sink status of this region server.
+       * 
+ */ + public Builder mergeReplLoadSink(org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink value) { + if (replLoadSinkBuilder_ == null) { + if (((bitField0_ & 0x00000400) == 0x00000400) && + replLoadSink_ != org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.getDefaultInstance()) { + replLoadSink_ = + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.newBuilder(replLoadSink_).mergeFrom(value).buildPartial(); + } else { + replLoadSink_ = value; + } + onChanged(); + } else { + replLoadSinkBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000400; + return this; + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+       **
+       * The replicationLoadSink for the replication Sink status of this region server.
+       * 
+ */ + public Builder clearReplLoadSink() { + if (replLoadSinkBuilder_ == null) { + replLoadSink_ = org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.getDefaultInstance(); + onChanged(); + } else { + replLoadSinkBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000400); + return this; + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+       **
+       * The replicationLoadSink for the replication Sink status of this region server.
+       * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.Builder getReplLoadSinkBuilder() { + bitField0_ |= 0x00000400; + onChanged(); + return getReplLoadSinkFieldBuilder().getBuilder(); + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+       **
+       * The replicationLoadSink for the replication Sink status of this region server.
+       * 
+ */ + public org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSinkOrBuilder getReplLoadSinkOrBuilder() { + if (replLoadSinkBuilder_ != null) { + return replLoadSinkBuilder_.getMessageOrBuilder(); + } else { + return replLoadSink_; + } + } + /** + * optional .ReplicationLoadSink replLoadSink = 11; + * + *
+       **
+       * The replicationLoadSink for the replication Sink status of this region server.
+       * 
+ */ + private com.google.protobuf.SingleFieldBuilder< + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.Builder, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSinkOrBuilder> + getReplLoadSinkFieldBuilder() { + if (replLoadSinkBuilder_ == null) { + replLoadSinkBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSink.Builder, org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos.ReplicationLoadSinkOrBuilder>( + replLoadSink_, + getParentForChildren(), + isClean()); + replLoadSink_ = null; + } + return replLoadSinkBuilder_; + } + // @@protoc_insertion_point(builder_scope:ServerLoad) } @@ -10526,6 +12783,16 @@ public final class ClusterStatusProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_RegionLoad_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_ReplicationLoadSink_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_ReplicationLoadSink_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_ReplicationLoadSource_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_ReplicationLoadSource_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_ServerLoad_descriptor; private static @@ -10575,26 +12842,34 @@ public final class ClusterStatusProtos { "(\r\022\"\n\032total_static_bloom_size_KB\030\016 \001(\r\022\034" + "\n\024complete_sequence_id\030\017 \001(\004\022\025\n\rdata_loc" + "ality\030\020 \001(\002\022#\n\030last_major_compaction_ts\030" + - "\021 \001(\004:\0010\"\212\002\n\nServerLoad\022\032\n\022number_of_req" + - "uests\030\001 \001(\r\022 \n\030total_number_of_requests\030" + - "\002 \001(\r\022\024\n\014used_heap_MB\030\003 \001(\r\022\023\n\013max_heap_" + - "MB\030\004 \001(\r\022!\n\014region_loads\030\005 \003(\0132\013.RegionL" + - "oad\022\"\n\014coprocessors\030\006 \003(\0132\014.Coprocessor\022", - "\031\n\021report_start_time\030\007 \001(\004\022\027\n\017report_end" + - "_time\030\010 \001(\004\022\030\n\020info_server_port\030\t \001(\r\"O\n" + - "\016LiveServerInfo\022\033\n\006server\030\001 \002(\0132\013.Server" + - "Name\022 \n\013server_load\030\002 \002(\0132\013.ServerLoad\"\340" + - "\002\n\rClusterStatus\022/\n\rhbase_version\030\001 \001(\0132" + - "\030.HBaseVersionFileContent\022%\n\014live_server" + - "s\030\002 \003(\0132\017.LiveServerInfo\022!\n\014dead_servers" + - "\030\003 \003(\0132\013.ServerName\0222\n\025regions_in_transi" + - "tion\030\004 \003(\0132\023.RegionInTransition\022\036\n\nclust" + - "er_id\030\005 \001(\0132\n.ClusterId\022)\n\023master_coproc", - "essors\030\006 \003(\0132\014.Coprocessor\022\033\n\006master\030\007 \001" + - "(\0132\013.ServerName\022#\n\016backup_masters\030\010 \003(\0132" + - "\013.ServerName\022\023\n\013balancer_on\030\t \001(\010BF\n*org" + - ".apache.hadoop.hbase.protobuf.generatedB" + - "\023ClusterStatusProtosH\001\240\001\001" + "\021 \001(\004:\0010\"T\n\023ReplicationLoadSink\022\032\n\022ageOf" + + "LastAppliedOp\030\001 \002(\004\022!\n\031timeStampsOfLastA" + + "ppliedOp\030\002 \002(\004\"\225\001\n\025ReplicationLoadSource" + + "\022\016\n\006peerID\030\001 \002(\t\022\032\n\022ageOfLastShippedOp\030\002" + + " \002(\004\022\026\n\016sizeOfLogQueue\030\003 \002(\r\022 \n\030timeStam", + "pOfLastShippedOp\030\004 \002(\004\022\026\n\016replicationLag" + + "\030\005 \002(\004\"\346\002\n\nServerLoad\022\032\n\022number_of_reque" + + "sts\030\001 \001(\r\022 \n\030total_number_of_requests\030\002 " + + "\001(\r\022\024\n\014used_heap_MB\030\003 \001(\r\022\023\n\013max_heap_MB" + + "\030\004 \001(\r\022!\n\014region_loads\030\005 \003(\0132\013.RegionLoa" + + "d\022\"\n\014coprocessors\030\006 \003(\0132\014.Coprocessor\022\031\n" + + "\021report_start_time\030\007 \001(\004\022\027\n\017report_end_t" + + "ime\030\010 \001(\004\022\030\n\020info_server_port\030\t \001(\r\022.\n\016r" + + "eplLoadSource\030\n \003(\0132\026.ReplicationLoadSou" + + "rce\022*\n\014replLoadSink\030\013 \001(\0132\024.ReplicationL", + "oadSink\"O\n\016LiveServerInfo\022\033\n\006server\030\001 \002(" + + "\0132\013.ServerName\022 \n\013server_load\030\002 \002(\0132\013.Se" + + "rverLoad\"\340\002\n\rClusterStatus\022/\n\rhbase_vers" + + "ion\030\001 \001(\0132\030.HBaseVersionFileContent\022%\n\014l" + + "ive_servers\030\002 \003(\0132\017.LiveServerInfo\022!\n\014de" + + "ad_servers\030\003 \003(\0132\013.ServerName\0222\n\025regions" + + "_in_transition\030\004 \003(\0132\023.RegionInTransitio" + + "n\022\036\n\ncluster_id\030\005 \001(\0132\n.ClusterId\022)\n\023mas" + + "ter_coprocessors\030\006 \003(\0132\014.Coprocessor\022\033\n\006" + + "master\030\007 \001(\0132\013.ServerName\022#\n\016backup_mast", + "ers\030\010 \003(\0132\013.ServerName\022\023\n\013balancer_on\030\t " + + "\001(\010BF\n*org.apache.hadoop.hbase.protobuf." + + "generatedB\023ClusterStatusProtosH\001\240\001\001" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -10619,20 +12894,32 @@ public final class ClusterStatusProtos { com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_RegionLoad_descriptor, new java.lang.String[] { "RegionSpecifier", "Stores", "Storefiles", "StoreUncompressedSizeMB", "StorefileSizeMB", "MemstoreSizeMB", "StorefileIndexSizeMB", "ReadRequestsCount", "WriteRequestsCount", "TotalCompactingKVs", "CurrentCompactedKVs", "RootIndexSizeKB", "TotalStaticIndexSizeKB", "TotalStaticBloomSizeKB", "CompleteSequenceId", "DataLocality", "LastMajorCompactionTs", }); - internal_static_ServerLoad_descriptor = + internal_static_ReplicationLoadSink_descriptor = getDescriptor().getMessageTypes().get(3); + internal_static_ReplicationLoadSink_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_ReplicationLoadSink_descriptor, + new java.lang.String[] { "AgeOfLastAppliedOp", "TimeStampsOfLastAppliedOp", }); + internal_static_ReplicationLoadSource_descriptor = + getDescriptor().getMessageTypes().get(4); + internal_static_ReplicationLoadSource_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_ReplicationLoadSource_descriptor, + new java.lang.String[] { "PeerID", "AgeOfLastShippedOp", "SizeOfLogQueue", "TimeStampOfLastShippedOp", "ReplicationLag", }); + internal_static_ServerLoad_descriptor = + getDescriptor().getMessageTypes().get(5); internal_static_ServerLoad_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_ServerLoad_descriptor, - new java.lang.String[] { "NumberOfRequests", "TotalNumberOfRequests", "UsedHeapMB", "MaxHeapMB", "RegionLoads", "Coprocessors", "ReportStartTime", "ReportEndTime", "InfoServerPort", }); + new java.lang.String[] { "NumberOfRequests", "TotalNumberOfRequests", "UsedHeapMB", "MaxHeapMB", "RegionLoads", "Coprocessors", "ReportStartTime", "ReportEndTime", "InfoServerPort", "ReplLoadSource", "ReplLoadSink", }); internal_static_LiveServerInfo_descriptor = - getDescriptor().getMessageTypes().get(4); + getDescriptor().getMessageTypes().get(6); internal_static_LiveServerInfo_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_LiveServerInfo_descriptor, new java.lang.String[] { "Server", "ServerLoad", }); internal_static_ClusterStatus_descriptor = - getDescriptor().getMessageTypes().get(5); + getDescriptor().getMessageTypes().get(7); internal_static_ClusterStatus_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_ClusterStatus_descriptor, diff --git a/hbase-protocol/src/main/protobuf/ClusterStatus.proto b/hbase-protocol/src/main/protobuf/ClusterStatus.proto index 2b2d9eb8ac0..bb531cc914e 100644 --- a/hbase-protocol/src/main/protobuf/ClusterStatus.proto +++ b/hbase-protocol/src/main/protobuf/ClusterStatus.proto @@ -119,6 +119,19 @@ message RegionLoad { /* Server-level protobufs */ +message ReplicationLoadSink { + required uint64 ageOfLastAppliedOp = 1; + required uint64 timeStampsOfLastAppliedOp = 2; +} + +message ReplicationLoadSource { + required string peerID = 1; + required uint64 ageOfLastShippedOp = 2; + required uint32 sizeOfLogQueue = 3; + required uint64 timeStampOfLastShippedOp = 4; + required uint64 replicationLag = 5; +} + message ServerLoad { /** Number of requests since last report. */ optional uint32 number_of_requests = 1; @@ -160,6 +173,16 @@ message ServerLoad { * The port number that this region server is hosing an info server on. */ optional uint32 info_server_port = 9; + + /** + * The replicationLoadSource for the replication Source status of this region server. + */ + repeated ReplicationLoadSource replLoadSource = 10; + + /** + * The replicationLoadSink for the replication Sink status of this region server. + */ + optional ReplicationLoadSink replLoadSink = 11; } message LiveServerInfo { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java index c170a65df12..4574a01f94a 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java @@ -130,6 +130,7 @@ import org.apache.hadoop.hbase.regionserver.handler.CloseMetaHandler; import org.apache.hadoop.hbase.regionserver.handler.CloseRegionHandler; import org.apache.hadoop.hbase.regionserver.wal.MetricsWAL; import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener; +import org.apache.hadoop.hbase.replication.regionserver.ReplicationLoad; import org.apache.hadoop.hbase.security.UserProvider; import org.apache.hadoop.hbase.trace.SpanReceiverHost; import org.apache.hadoop.hbase.util.Addressing; @@ -1147,6 +1148,22 @@ public class HRegionServer extends HasThread implements } else { serverLoad.setInfoServerPort(-1); } + + // for the replicationLoad purpose. Only need to get from one service + // either source or sink will get the same info + ReplicationSourceService rsources = getReplicationSourceService(); + + if (rsources != null) { + // always refresh first to get the latest value + ReplicationLoad rLoad = rsources.refreshAndGetReplicationLoad(); + if (rLoad != null) { + serverLoad.setReplLoadSink(rLoad.getReplicationLoadSink()); + for (ClusterStatusProtos.ReplicationLoadSource rLS : rLoad.getReplicationLoadSourceList()) { + serverLoad.addReplLoadSource(rLS); + } + } + } + return serverLoad.build(); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReplicationService.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReplicationService.java index 92ac8236c72..25a27a90534 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReplicationService.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReplicationService.java @@ -22,11 +22,12 @@ import java.io.IOException; import org.apache.hadoop.hbase.Server; import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.replication.regionserver.ReplicationLoad; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; /** - * Gateway to Cluster Replication. + * Gateway to Cluster Replication. * Used by {@link org.apache.hadoop.hbase.regionserver.HRegionServer}. * One such application is a cross-datacenter * replication service that can keep two hbase clusters in sync. @@ -52,4 +53,9 @@ public interface ReplicationService { * Stops replication service. */ void stopReplicationService(); + + /** + * Refresh and Get ReplicationLoad + */ + public ReplicationLoad refreshAndGetReplicationLoad(); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsSink.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsSink.java index 0c9d0169124..37dc1dd4e4e 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsSink.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsSink.java @@ -71,4 +71,21 @@ public class MetricsSink { mss.incrAppliedOps(batchSize); } + /** + * Get the Age of Last Applied Op + * @return ageOfLastAppliedOp + */ + public long getAgeOfLastAppliedOp() { + return mss.getLastAppliedOpAge(); + } + + /** + * Get the TimeStampOfLastAppliedOp. If no replication Op applied yet, the value is the timestamp + * at which hbase instance starts + * @return timeStampsOfLastAppliedOp; + */ + public long getTimeStampOfLastAppliedOp() { + return this.lastTimestampForAge; + } + } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsSource.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsSource.java index a734b9ce07f..21296a011e8 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsSource.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/MetricsSource.java @@ -36,6 +36,7 @@ public class MetricsSource { private long lastTimestamp = 0; private int lastQueueSize = 0; + private String id; private final MetricsReplicationSourceSource singleSourceSource; private final MetricsReplicationSourceSource globalSourceSource; @@ -46,6 +47,7 @@ public class MetricsSource { * @param id Name of the source this class is monitoring */ public MetricsSource(String id) { + this.id = id; singleSourceSource = CompatibilitySingletonFactory.getInstance(MetricsReplicationSourceFactory.class) .getSource(id); @@ -143,4 +145,36 @@ public class MetricsSource { globalSourceSource.decrSizeOfLogQueue(lastQueueSize); lastQueueSize = 0; } + + /** + * Get AgeOfLastShippedOp + * @return AgeOfLastShippedOp + */ + public Long getAgeOfLastShippedOp() { + return singleSourceSource.getLastShippedAge(); + } + + /** + * Get the sizeOfLogQueue + * @return sizeOfLogQueue + */ + public int getSizeOfLogQueue() { + return this.lastQueueSize; + } + + /** + * Get the timeStampsOfLastShippedOp + * @return lastTimestampForAge + */ + public long getTimeStampOfLastShippedOp() { + return lastTimestamp; + } + + /** + * Get the slave peer ID + * @return peerID + */ + public String getPeerID() { + return id; + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/Replication.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/Replication.java index b30698caf78..5b0f469a04a 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/Replication.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/Replication.java @@ -23,6 +23,7 @@ import static org.apache.hadoop.hbase.HConstants.REPLICATION_ENABLE_KEY; import static org.apache.hadoop.hbase.HConstants.REPLICATION_SCOPE_LOCAL; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.NavigableMap; import java.util.TreeMap; @@ -65,7 +66,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; * Gateway to Replication. Used by {@link org.apache.hadoop.hbase.regionserver.HRegionServer}. */ @InterfaceAudience.Private -public class Replication extends WALActionsListener.Base implements +public class Replication extends WALActionsListener.Base implements ReplicationSourceService, ReplicationSinkService { private static final Log LOG = LogFactory.getLog(Replication.class); @@ -81,6 +82,8 @@ public class Replication extends WALActionsListener.Base implements /** Statistics thread schedule pool */ private ScheduledExecutorService scheduleThreadPool; private int statsThreadPeriod; + // ReplicationLoad to access replication metrics + private ReplicationLoad replicationLoad; /** * Instantiate the replication management (if rep is enabled). @@ -137,11 +140,13 @@ public class Replication extends WALActionsListener.Base implements this.statsThreadPeriod = this.conf.getInt("replication.stats.thread.period.seconds", 5 * 60); LOG.debug("ReplicationStatisticsThread " + this.statsThreadPeriod); + this.replicationLoad = new ReplicationLoad(); } else { this.replicationManager = null; this.replicationQueues = null; this.replicationPeers = null; this.replicationTracker = null; + this.replicationLoad = null; } } @@ -309,4 +314,29 @@ public class Replication extends WALActionsListener.Base implements } } } + + @Override + public ReplicationLoad refreshAndGetReplicationLoad() { + if (this.replicationLoad == null) { + return null; + } + // always build for latest data + buildReplicationLoad(); + return this.replicationLoad; + } + + private void buildReplicationLoad() { + // get source + List sources = this.replicationManager.getSources(); + List sourceMetricsList = new ArrayList(); + + for (ReplicationSourceInterface source : sources) { + if (source instanceof ReplicationSource) { + sourceMetricsList.add(((ReplicationSource) source).getSourceMetrics()); + } + } + // get sink + MetricsSink sinkMetrics = this.replicationSink.getSinkMetrics(); + this.replicationLoad.buildReplicationLoad(sourceMetricsList, sinkMetrics); + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationLoad.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationLoad.java new file mode 100644 index 00000000000..b3f3ecbcc0e --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationLoad.java @@ -0,0 +1,151 @@ +/** + * Copyright 2014 The Apache Software Foundation 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.hbase.replication.regionserver; + +import java.util.Date; +import java.util.List; +import java.util.ArrayList; + +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.protobuf.generated.ClusterStatusProtos; +import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; +import org.apache.hadoop.hbase.util.Strings; + +/** + * This class is used for exporting some of the info from replication metrics + */ +@InterfaceAudience.Private +public class ReplicationLoad { + + // Empty load instance. + public static final ReplicationLoad EMPTY_REPLICATIONLOAD = new ReplicationLoad(); + + private List sourceMetricsList; + private MetricsSink sinkMetrics; + + private List replicationLoadSourceList; + private ClusterStatusProtos.ReplicationLoadSink replicationLoadSink; + + /** default constructor */ + public ReplicationLoad() { + super(); + } + + /** + * buildReplicationLoad + * @param srMetricsList + * @param skMetrics + */ + + public void buildReplicationLoad(final List srMetricsList, + final MetricsSink skMetrics) { + this.sourceMetricsList = srMetricsList; + this.sinkMetrics = skMetrics; + + // build the SinkLoad + ClusterStatusProtos.ReplicationLoadSink.Builder rLoadSinkBuild = + ClusterStatusProtos.ReplicationLoadSink.newBuilder(); + rLoadSinkBuild.setAgeOfLastAppliedOp(sinkMetrics.getAgeOfLastAppliedOp()); + rLoadSinkBuild.setTimeStampsOfLastAppliedOp(sinkMetrics.getTimeStampOfLastAppliedOp()); + this.replicationLoadSink = rLoadSinkBuild.build(); + + // build the SourceLoad List + this.replicationLoadSourceList = new ArrayList(); + for (MetricsSource sm : this.sourceMetricsList) { + long ageOfLastShippedOp = sm.getAgeOfLastShippedOp(); + int sizeOfLogQueue = sm.getSizeOfLogQueue(); + long timeStampOfLastShippedOp = sm.getTimeStampOfLastShippedOp(); + long replicationLag; + long timePassedAfterLastShippedOp = + EnvironmentEdgeManager.currentTime() - timeStampOfLastShippedOp; + if (sizeOfLogQueue != 0) { + // err on the large side + replicationLag = Math.max(ageOfLastShippedOp, timePassedAfterLastShippedOp); + } else if (timePassedAfterLastShippedOp < 2 * ageOfLastShippedOp) { + replicationLag = ageOfLastShippedOp; // last shipped happen recently + } else { + // last shipped may happen last night, + // so NO real lag although ageOfLastShippedOp is non-zero + replicationLag = 0; + } + + ClusterStatusProtos.ReplicationLoadSource.Builder rLoadSourceBuild = + ClusterStatusProtos.ReplicationLoadSource.newBuilder(); + rLoadSourceBuild.setPeerID(sm.getPeerID()); + rLoadSourceBuild.setAgeOfLastShippedOp(ageOfLastShippedOp); + rLoadSourceBuild.setSizeOfLogQueue(sizeOfLogQueue); + rLoadSourceBuild.setTimeStampOfLastShippedOp(timeStampOfLastShippedOp); + rLoadSourceBuild.setReplicationLag(replicationLag); + + this.replicationLoadSourceList.add(rLoadSourceBuild.build()); + } + + } + + /** + * sourceToString + * @return a string contains sourceReplicationLoad information + */ + public String sourceToString() { + if (this.sourceMetricsList == null) return null; + + StringBuilder sb = new StringBuilder(); + + for (ClusterStatusProtos.ReplicationLoadSource rls : this.replicationLoadSourceList) { + + sb = Strings.appendKeyValue(sb, "\n PeerID", rls.getPeerID()); + sb = Strings.appendKeyValue(sb, "AgeOfLastShippedOp", rls.getAgeOfLastShippedOp()); + sb = Strings.appendKeyValue(sb, "SizeOfLogQueue", rls.getSizeOfLogQueue()); + sb = + Strings.appendKeyValue(sb, "TimeStampsOfLastShippedOp", + (new Date(rls.getTimeStampOfLastShippedOp()).toString())); + sb = Strings.appendKeyValue(sb, "Replication Lag", rls.getReplicationLag()); + } + + return sb.toString(); + } + + /** + * sinkToString + * @return a string contains sinkReplicationLoad information + */ + public String sinkToString() { + if (this.replicationLoadSink == null) return null; + + StringBuilder sb = new StringBuilder(); + sb = + Strings.appendKeyValue(sb, "AgeOfLastAppliedOp", + this.replicationLoadSink.getAgeOfLastAppliedOp()); + sb = + Strings.appendKeyValue(sb, "TimeStampsOfLastAppliedOp", + (new Date(this.replicationLoadSink.getTimeStampsOfLastAppliedOp()).toString())); + + return sb.toString(); + } + + public ClusterStatusProtos.ReplicationLoadSink getReplicationLoadSink() { + return this.replicationLoadSink; + } + + public List getReplicationLoadSourceList() { + return this.replicationLoadSourceList; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return this.sourceToString() + System.getProperty("line.separator") + this.sinkToString(); + } + +} \ No newline at end of file diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSink.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSink.java index 9a6013188da..32764180aa4 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSink.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSink.java @@ -254,4 +254,12 @@ public class ReplicationSink { "age in ms of last applied edit: " + this.metrics.refreshAgeOfLastAppliedOp() + ", total replicated edits: " + this.totalReplicatedEdits; } + + /** + * Get replication Sink Metrics + * @return MetricsSink + */ + public MetricsSink getSinkMetrics() { + return this.metrics; + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSource.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSource.java index ee43956f482..714080fee68 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSource.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/ReplicationSource.java @@ -869,4 +869,12 @@ public class ReplicationSource extends Thread ", currently replicating from: " + this.currentPath + " at position: " + position; } + + /** + * Get Replication Source Metrics + * @return sourceMetrics + */ + public MetricsSource getSourceMetrics() { + return this.metrics; + } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/replication/TestReplicationSmallTests.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/replication/TestReplicationSmallTests.java index f0db865bad2..2dc3c896559 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/replication/TestReplicationSmallTests.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/replication/TestReplicationSmallTests.java @@ -31,11 +31,15 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.ClusterStatus; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.ServerLoad; +import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.HBaseAdmin; @@ -556,4 +560,45 @@ public class TestReplicationSmallTests extends TestReplicationBase { hadmin.close(); } + /** + * Test for HBASE-9531 + * put a few rows into htable1, which should be replicated to htable2 + * create a ClusterStatus instance 'status' from HBaseAdmin + * test : status.getLoad(server).getReplicationLoadSourceList() + * test : status.getLoad(server).getReplicationLoadSink() + * * @throws Exception + */ + @Test(timeout = 300000) + public void testReplicationStatus() throws Exception { + LOG.info("testReplicationStatus"); + + try (Admin admin = utility1.getConnection().getAdmin()) { + + final byte[] qualName = Bytes.toBytes("q"); + Put p; + + for (int i = 0; i < NB_ROWS_IN_BATCH; i++) { + p = new Put(Bytes.toBytes("row" + i)); + p.add(famName, qualName, Bytes.toBytes("val" + i)); + htable1.put(p); + } + + ClusterStatus status = admin.getClusterStatus(); + + for (ServerName server : status.getServers()) { + ServerLoad sl = status.getLoad(server); + List rLoadSourceList = sl.getReplicationLoadSourceList(); + ReplicationLoadSink rLoadSink = sl.getReplicationLoadSink(); + + // check SourceList has at least one entry + assertTrue("failed to get ReplicationLoadSourceList", (rLoadSourceList.size() > 0)); + + // check Sink exist only as it is difficult to verify the value on the fly + assertTrue("failed to get ReplicationLoadSink.AgeOfLastShippedOp ", + (rLoadSink.getAgeOfLastAppliedOp() >= 0)); + assertTrue("failed to get ReplicationLoadSink.TimeStampsOfLastAppliedOp ", + (rLoadSink.getTimeStampsOfLastAppliedOp() >= 0)); + } + } + } } diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb index c0ea862561a..35ee36cd760 100644 --- a/hbase-shell/src/main/ruby/hbase/admin.rb +++ b/hbase-shell/src/main/ruby/hbase/admin.rb @@ -608,7 +608,7 @@ module Hbase end end - def status(format) + def status(format, type) status = @admin.getClusterStatus() if format == "detailed" puts("version %s" % [ status.getHBaseVersion() ]) @@ -635,6 +635,46 @@ module Hbase for server in status.getDeadServerNames() puts(" %s" % [ server ]) end + elsif format == "replication" + #check whether replication is enabled or not + if (!@admin.getConfiguration().getBoolean(org.apache.hadoop.hbase.HConstants::REPLICATION_ENABLE_KEY, + org.apache.hadoop.hbase.HConstants::REPLICATION_ENABLE_DEFAULT)) + puts("Please enable replication first.") + else + puts("version %s" % [ status.getHBaseVersion() ]) + puts("%d live servers" % [ status.getServersSize() ]) + for server in status.getServers() + sl = status.getLoad(server) + rSinkString = " SINK :" + rSourceString = " SOURCE:" + rLoadSink = sl.getReplicationLoadSink() + rSinkString << " AgeOfLastAppliedOp=" + rLoadSink.getAgeOfLastAppliedOp().to_s + rSinkString << ", TimeStampsOfLastAppliedOp=" + + (java.util.Date.new(rLoadSink.getTimeStampsOfLastAppliedOp())).toString() + rLoadSourceList = sl.getReplicationLoadSourceList() + index = 0 + while index < rLoadSourceList.size() + rLoadSource = rLoadSourceList.get(index) + rSourceString << " PeerID=" + rLoadSource.getPeerID() + rSourceString << ", AgeOfLastShippedOp=" + rLoadSource.getAgeOfLastShippedOp().to_s + rSourceString << ", SizeOfLogQueue=" + rLoadSource.getSizeOfLogQueue().to_s + rSourceString << ", TimeStampsOfLastShippedOp=" + + (java.util.Date.new(rLoadSource.getTimeStampOfLastShippedOp())).toString() + rSourceString << ", Replication Lag=" + rLoadSource.getReplicationLag().to_s + index = index + 1 + end + puts(" %s:" % + [ server.getHostname() ]) + if type.casecmp("SOURCE") == 0 + puts("%s" % rSourceString) + elsif type.casecmp("SINK") == 0 + puts("%s" % rSinkString) + else + puts("%s" % rSourceString) + puts("%s" % rSinkString) + end + end + end elsif format == "simple" load = 0 regions = 0 diff --git a/hbase-shell/src/main/ruby/shell/commands/status.rb b/hbase-shell/src/main/ruby/shell/commands/status.rb index f72c13caef6..b22b2723987 100644 --- a/hbase-shell/src/main/ruby/shell/commands/status.rb +++ b/hbase-shell/src/main/ruby/shell/commands/status.rb @@ -22,18 +22,21 @@ module Shell class Status < Command def help return <<-EOF -Show cluster status. Can be 'summary', 'simple', or 'detailed'. The +Show cluster status. Can be 'summary', 'simple', 'detailed', or 'replication'. The default is 'summary'. Examples: hbase> status hbase> status 'simple' hbase> status 'summary' hbase> status 'detailed' + hbase> status 'replication' + hbase> status 'replication', 'source' + hbase> status 'replication', 'sink' EOF end - def command(format = 'summary') - admin.status(format) + def command(format = 'summary',type = 'both') + admin.status(format, type) end end end diff --git a/hbase-shell/src/test/ruby/hbase/admin_test.rb b/hbase-shell/src/test/ruby/hbase/admin_test.rb index caede3ad9f6..19258649c79 100644 --- a/hbase-shell/src/test/ruby/hbase/admin_test.rb +++ b/hbase-shell/src/test/ruby/hbase/admin_test.rb @@ -356,5 +356,17 @@ module Hbase assert_not_equal(nil, table) table.close end + + define_test "Get replication status" do + replication_status("replication", "both") + end + + define_test "Get replication source metrics information" do + replication_status("replication", "source") + end + + define_test "Get replication sink metrics information" do + replication_status("replication", "sink") + end end end diff --git a/hbase-shell/src/test/ruby/test_helper.rb b/hbase-shell/src/test/ruby/test_helper.rb index 55797610614..5dfafc5657a 100644 --- a/hbase-shell/src/test/ruby/test_helper.rb +++ b/hbase-shell/src/test/ruby/test_helper.rb @@ -94,6 +94,10 @@ module Hbase puts "IGNORING DROP TABLE ERROR: #{e}" end end + + def replication_status(format,type) + return admin.status(format,type) + end end end From 7561ae6d1257b51c0bb1ef46e52d8ede2c7c926f Mon Sep 17 00:00:00 2001 From: Kevin Risden Date: Tue, 27 Jan 2015 11:20:42 -0600 Subject: [PATCH 010/329] HBASE-12867 Add ability to specify custom replication endpoint to add_peer --- hbase-shell/src/main/ruby/hbase.rb | 5 + .../src/main/ruby/hbase/replication_admin.rb | 69 ++++++- .../src/main/ruby/shell/commands/add_peer.rb | 38 +++- .../test/ruby/hbase/replication_admin_test.rb | 191 ++++++++++++++++++ hbase-shell/src/test/ruby/test_helper.rb | 4 + 5 files changed, 296 insertions(+), 11 deletions(-) create mode 100644 hbase-shell/src/test/ruby/hbase/replication_admin_test.rb diff --git a/hbase-shell/src/main/ruby/hbase.rb b/hbase-shell/src/main/ruby/hbase.rb index a857bd961aa..f181edabded 100644 --- a/hbase-shell/src/main/ruby/hbase.rb +++ b/hbase-shell/src/main/ruby/hbase.rb @@ -72,6 +72,11 @@ module HBaseConstants TYPE = 'TYPE' NONE = 'NONE' VALUE = 'VALUE' + ENDPOINT_CLASSNAME = 'ENDPOINT_CLASSNAME' + CLUSTER_KEY = 'CLUSTER_KEY' + TABLE_CFS = 'TABLE_CFS' + CONFIG = 'CONFIG' + DATA = 'DATA' # Load constants from hbase java API def self.promote_constants(constants) diff --git a/hbase-shell/src/main/ruby/hbase/replication_admin.rb b/hbase-shell/src/main/ruby/hbase/replication_admin.rb index 6dedb2effc7..2d0845f5625 100644 --- a/hbase-shell/src/main/ruby/hbase/replication_admin.rb +++ b/hbase-shell/src/main/ruby/hbase/replication_admin.rb @@ -19,21 +19,80 @@ include Java -# Wrapper for org.apache.hadoop.hbase.client.HBaseAdmin +java_import org.apache.hadoop.hbase.client.replication.ReplicationAdmin +java_import org.apache.hadoop.hbase.replication.ReplicationPeerConfig +java_import org.apache.hadoop.hbase.util.Bytes +java_import org.apache.hadoop.hbase.zookeeper.ZKUtil + +# Wrapper for org.apache.hadoop.hbase.client.replication.ReplicationAdmin module Hbase class RepAdmin include HBaseConstants def initialize(configuration, formatter) - @replication_admin = org.apache.hadoop.hbase.client.replication.ReplicationAdmin.new(configuration) + @replication_admin = ReplicationAdmin.new(configuration) + @configuration = configuration @formatter = formatter end #---------------------------------------------------------------------------------------------- # Add a new peer cluster to replicate to - def add_peer(id, cluster_key, peer_tableCFs = nil) - @replication_admin.addPeer(id, cluster_key, peer_tableCFs) + def add_peer(id, args = {}, peer_tableCFs = nil) + # make add_peer backwards compatible to take in string for clusterKey and peer_tableCFs + if args.is_a?(String) + cluster_key = args + @replication_admin.addPeer(id, cluster_key, peer_tableCFs) + elsif args.is_a?(Hash) + unless peer_tableCFs.nil? + raise(ArgumentError, "peer_tableCFs should be specified as TABLE_CFS in args") + end + + endpoint_classname = args.fetch(ENDPOINT_CLASSNAME, nil) + cluster_key = args.fetch(CLUSTER_KEY, nil) + + # Handle cases where custom replication endpoint and cluster key are either both provided + # or neither are provided + if endpoint_classname.nil? and cluster_key.nil? + raise(ArgumentError, "Either ENDPOINT_CLASSNAME or CLUSTER_KEY must be specified.") + elsif !endpoint_classname.nil? and !cluster_key.nil? + raise(ArgumentError, "ENDPOINT_CLASSNAME and CLUSTER_KEY cannot both be specified.") + end + + # Cluster Key is required for ReplicationPeerConfig for a custom replication endpoint + if !endpoint_classname.nil? and cluster_key.nil? + cluster_key = ZKUtil.getZooKeeperClusterKey(@configuration) + end + + # Optional parameters + config = args.fetch(CONFIG, nil) + data = args.fetch(DATA, nil) + table_cfs = args.fetch(TABLE_CFS, nil) + + # Create and populate a ReplicationPeerConfig + replication_peer_config = ReplicationPeerConfig.new + replication_peer_config.set_cluster_key(cluster_key) + + unless endpoint_classname.nil? + replication_peer_config.set_replication_endpoint_impl(endpoint_classname) + end + + unless config.nil? + replication_peer_config.get_configuration.put_all(config) + end + + unless data.nil? + # Convert Strings to Bytes for peer_data + peer_data = replication_peer_config.get_peer_data + data.each{|key, val| + peer_data.put(Bytes.to_bytes(key), Bytes.to_bytes(val)) + } + end + + @replication_admin.add_peer(id, replication_peer_config, table_cfs) + else + raise(ArgumentError, "args must be either a String or Hash") + end end #---------------------------------------------------------------------------------------------- @@ -48,7 +107,7 @@ module Hbase def list_replicated_tables(regex = ".*") pattern = java.util.regex.Pattern.compile(regex) list = @replication_admin.listReplicated() - list.select {|s| pattern.match(s.get(org.apache.hadoop.hbase.client.replication.ReplicationAdmin::TNAME))} + list.select {|s| pattern.match(s.get(ReplicationAdmin.TNAME))} end #---------------------------------------------------------------------------------------------- diff --git a/hbase-shell/src/main/ruby/shell/commands/add_peer.rb b/hbase-shell/src/main/ruby/shell/commands/add_peer.rb index ecd8e753920..be010416445 100644 --- a/hbase-shell/src/main/ruby/shell/commands/add_peer.rb +++ b/hbase-shell/src/main/ruby/shell/commands/add_peer.rb @@ -22,21 +22,47 @@ module Shell class AddPeer< Command def help return <<-EOF -Add a peer cluster to replicate to, the id must be a short and -the cluster key is composed like this: +A peer can either be another HBase cluster or a custom replication endpoint. In either case an id +must be specified to identify the peer. + +For a HBase cluster peer, a cluster key must be provided and is composed like this: hbase.zookeeper.quorum:hbase.zookeeper.property.clientPort:zookeeper.znode.parent -This gives a full path for HBase to connect to another cluster. +This gives a full path for HBase to connect to another HBase cluster. An optional parameter for +table column families identifies which column families will be replicated to the peer cluster. Examples: hbase> add_peer '1', "server1.cie.com:2181:/hbase" hbase> add_peer '2', "zk1,zk2,zk3:2182:/hbase-prod" - hbase> add_peer '3', "zk4,zk5,zk6:11000:/hbase-test", "tab1; tab2:cf1; tab3:cf2,cf3" + hbase> add_peer '3', "zk4,zk5,zk6:11000:/hbase-test", "table1; table2:cf1; table3:cf1,cf2" + hbase> add_peer '4', CLUSTER_KEY => "server1.cie.com:2181:/hbase" + hbase> add_peer '5', CLUSTER_KEY => "server1.cie.com:2181:/hbase", + TABLE_CFS => { "table1" => [], "table2" => ["cf1"], "table3" => ["cf1", "cf2"] } + +For a custom replication endpoint, the ENDPOINT_CLASSNAME can be provided. Two optional arguments +are DATA and CONFIG which can be specified to set different either the peer_data or configuration +for the custom replication endpoint. Table column families is optional and can be specified with +the key TABLE_CFS. + + hbase> add_peer '6', ENDPOINT_CLASSNAME => 'org.apache.hadoop.hbase.MyReplicationEndpoint' + hbase> add_peer '7', ENDPOINT_CLASSNAME => 'org.apache.hadoop.hbase.MyReplicationEndpoint', + DATA => { "key1" => 1 } + hbase> add_peer '8', ENDPOINT_CLASSNAME => 'org.apache.hadoop.hbase.MyReplicationEndpoint', + CONFIG => { "config1" => "value1", "config2" => "value2" } + hbase> add_peer '9', ENDPOINT_CLASSNAME => 'org.apache.hadoop.hbase.MyReplicationEndpoint', + DATA => { "key1" => 1 }, CONFIG => { "config1" => "value1", "config2" => "value2" }, + hbase> add_peer '10', ENDPOINT_CLASSNAME => 'org.apache.hadoop.hbase.MyReplicationEndpoint', + TABLE_CFS => { "table1" => [], "table2" => ["cf1"], "table3" => ["cf1", "cf2"] } + hbase> add_peer '11', ENDPOINT_CLASSNAME => 'org.apache.hadoop.hbase.MyReplicationEndpoint', + DATA => { "key1" => 1 }, CONFIG => { "config1" => "value1", "config2" => "value2" }, + TABLE_CFS => { "table1" => [], "table2" => ["cf1"], "table3" => ["cf1", "cf2"] } + +Note: Either CLUSTER_KEY or ENDPOINT_CLASSNAME must be specified but not both. EOF end - def command(id, cluster_key, peer_tableCFs = nil) + def command(id, args = {}, peer_tableCFs = nil) format_simple_command do - replication_admin.add_peer(id, cluster_key, peer_tableCFs) + replication_admin.add_peer(id, args, peer_tableCFs) end end end diff --git a/hbase-shell/src/test/ruby/hbase/replication_admin_test.rb b/hbase-shell/src/test/ruby/hbase/replication_admin_test.rb new file mode 100644 index 00000000000..648efa7f861 --- /dev/null +++ b/hbase-shell/src/test/ruby/hbase/replication_admin_test.rb @@ -0,0 +1,191 @@ +# +# +# 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. +# + +require 'shell' +require 'shell/formatter' +require 'hbase' +require 'hbase/hbase' +require 'hbase/table' + +include HBaseConstants + +module Hbase + class ReplicationAdminTest < Test::Unit::TestCase + include TestHelpers + + def setup + @test_name = "hbase_shell_tests_table" + @peer_id = '1' + + setup_hbase + drop_test_table(@test_name) + create_test_table(@test_name) + + assert_equal(0, replication_admin.list_peers.length) + end + + def teardown + assert_equal(0, replication_admin.list_peers.length) + + shutdown + end + + define_test "add_peer: should fail when args isn't specified" do + assert_raise(ArgumentError) do + replication_admin.add_peer(@peer_id, nil) + end + end + + define_test "add_peer: fail when neither CLUSTER_KEY nor ENDPOINT_CLASSNAME are specified" do + assert_raise(ArgumentError) do + args = {} + replication_admin.add_peer(@peer_id, args) + end + end + + define_test "add_peer: fail when both CLUSTER_KEY and ENDPOINT_CLASSNAME are specified" do + assert_raise(ArgumentError) do + args = { CLUSTER_KEY => 'zk1,zk2,zk3:2182:/hbase-prod', + ENDPOINT_CLASSNAME => 'org.apache.hadoop.hbase.MyReplicationEndpoint' } + replication_admin.add_peer(@peer_id, args) + end + end + + define_test "add_peer: args must be a string or number" do + assert_raise(ArgumentError) do + replication_admin.add_peer(@peer_id, 1) + end + assert_raise(ArgumentError) do + replication_admin.add_peer(@peer_id, ['test']) + end + end + + define_test "add_peer: single zk cluster key" do + cluster_key = "server1.cie.com:2181:/hbase" + + replication_admin.add_peer(@peer_id, cluster_key) + + assert_equal(1, replication_admin.list_peers.length) + assert(replication_admin.list_peers.key?(@peer_id)) + assert_equal(cluster_key, replication_admin.list_peers.fetch(@peer_id)) + + # cleanup for future tests + replication_admin.remove_peer(@peer_id) + end + + define_test "add_peer: multiple zk cluster key" do + cluster_key = "zk1,zk2,zk3:2182:/hbase-prod" + + replication_admin.add_peer(@peer_id, cluster_key) + + assert_equal(1, replication_admin.list_peers.length) + assert(replication_admin.list_peers.key?(@peer_id)) + assert_equal(replication_admin.list_peers.fetch(@peer_id), cluster_key) + + # cleanup for future tests + replication_admin.remove_peer(@peer_id) + end + + define_test "add_peer: multiple zk cluster key and table_cfs" do + cluster_key = "zk4,zk5,zk6:11000:/hbase-test" + table_cfs_str = "table1;table2:cf1;table3:cf2,cf3" + + replication_admin.add_peer(@peer_id, cluster_key, table_cfs_str) + + assert_equal(1, replication_admin.list_peers.length) + assert(replication_admin.list_peers.key?(@peer_id)) + assert_equal(cluster_key, replication_admin.list_peers.fetch(@peer_id)) + assert_equal(table_cfs_str, replication_admin.show_peer_tableCFs(@peer_id)) + + # cleanup for future tests + replication_admin.remove_peer(@peer_id) + end + + define_test "add_peer: single zk cluster key - peer config" do + cluster_key = "server1.cie.com:2181:/hbase" + + args = { CLUSTER_KEY => cluster_key } + replication_admin.add_peer(@peer_id, args) + + assert_equal(1, replication_admin.list_peers.length) + assert(replication_admin.list_peers.key?(@peer_id)) + assert_equal(cluster_key, replication_admin.list_peers.fetch(@peer_id)) + + # cleanup for future tests + replication_admin.remove_peer(@peer_id) + end + + define_test "add_peer: multiple zk cluster key - peer config" do + cluster_key = "zk1,zk2,zk3:2182:/hbase-prod" + + args = { CLUSTER_KEY => cluster_key } + replication_admin.add_peer(@peer_id, args) + + assert_equal(1, replication_admin.list_peers.length) + assert(replication_admin.list_peers.key?(@peer_id)) + assert_equal(cluster_key, replication_admin.list_peers.fetch(@peer_id)) + + # cleanup for future tests + replication_admin.remove_peer(@peer_id) + end + + define_test "add_peer: multiple zk cluster key and table_cfs - peer config" do + cluster_key = "zk4,zk5,zk6:11000:/hbase-test" + table_cfs = { "table1" => [], "table2" => ["cf1"], "table3" => ["cf1", "cf2"] } + table_cfs_str = "table1;table2:cf1;table3:cf1,cf2" + + args = { CLUSTER_KEY => cluster_key, TABLE_CFS => table_cfs } + replication_admin.add_peer(@peer_id, args) + + assert_equal(1, replication_admin.list_peers.length) + assert(replication_admin.list_peers.key?(@peer_id)) + assert_equal(cluster_key, replication_admin.list_peers.fetch(@peer_id)) + assert_equal(table_cfs_str, replication_admin.show_peer_tableCFs(@peer_id)) + + # cleanup for future tests + replication_admin.remove_peer(@peer_id) + end + + define_test "add_peer: should fail when args is a hash and peer_tableCFs provided" do + cluster_key = "zk4,zk5,zk6:11000:/hbase-test" + table_cfs_str = "table1;table2:cf1;table3:cf1,cf2" + + assert_raise(ArgumentError) do + args = { CLUSTER_KEY => cluster_key } + replication_admin.add_peer(@peer_id, args, table_cfs_str) + end + end + + # assert_raise fails on native exceptions - https://jira.codehaus.org/browse/JRUBY-5279 + # Can't catch native Java exception with assert_raise in JRuby 1.6.8 as in the test below. + # define_test "add_peer: adding a second peer with same id should error" do + # replication_admin.add_peer(@peer_id, '') + # assert_equal(1, replication_admin.list_peers.length) + # + # assert_raise(java.lang.IllegalArgumentException) do + # replication_admin.add_peer(@peer_id, '') + # end + # + # assert_equal(1, replication_admin.list_peers.length, 1) + # + # # cleanup for future tests + # replication_admin.remove_peer(@peer_id) + # end + end +end diff --git a/hbase-shell/src/test/ruby/test_helper.rb b/hbase-shell/src/test/ruby/test_helper.rb index 5dfafc5657a..e2ab9214f55 100644 --- a/hbase-shell/src/test/ruby/test_helper.rb +++ b/hbase-shell/src/test/ruby/test_helper.rb @@ -68,6 +68,10 @@ module Hbase @shell.hbase_visibility_labels_admin end + def replication_admin + @shell.hbase_replication_admin + end + def create_test_table(name) # Create the table if needed unless admin.exists?(name) From cfc131e4379b3ac4c9de83e63dda41ce4df01e22 Mon Sep 17 00:00:00 2001 From: Ashish Singhi Date: Fri, 13 Feb 2015 19:10:53 +0530 Subject: [PATCH 011/329] HBASE-13038 Fix the java doc continuously reported by Hadoop QA --- .../src/main/java/org/apache/hadoop/hbase/KeyValue.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java index 8566a8815cd..04551deb45f 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java @@ -2214,7 +2214,8 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, * @param leftKey * @param rightKey * @return 0 if equal, <0 if left smaller, >0 if right smaller - * @deprecated Since 0.99.2; Use {@link CellComparator#getMidpoint(Cell, Cell)} instead. + * @deprecated Since 0.99.2; Use + * {@link CellComparator#getMidpoint(KeyValue.KVComparator, Cell, Cell) instead} */ @Deprecated public byte[] getShortMidpointKey(final byte[] leftKey, final byte[] rightKey) { From 3e10e6e1a61aa2a481a1450fecb94bce23809dfe Mon Sep 17 00:00:00 2001 From: Enis Soztutar Date: Fri, 13 Feb 2015 11:08:24 -0800 Subject: [PATCH 012/329] HBASE-11569 Flush / Compaction handling from secondary region replicas --- .../hadoop/hbase/protobuf/ProtobufUtil.java | 2 + .../org/apache/hadoop/hbase/KeyValue.java | 39 +- .../hbase/protobuf/generated/WALProtos.java | 299 ++++- hbase-protocol/src/main/protobuf/WAL.proto | 2 + .../hbase/regionserver/DefaultMemStore.java | 10 +- .../hadoop/hbase/regionserver/HRegion.java | 728 ++++++++++- .../hbase/regionserver/HRegionFileSystem.java | 10 +- .../hadoop/hbase/regionserver/HStore.java | 107 +- .../hadoop/hbase/regionserver/MemStore.java | 6 + .../hbase/regionserver/RSRpcServices.java | 19 +- .../hadoop/hbase/regionserver/Store.java | 28 +- .../hbase/regionserver/StoreFlushContext.java | 16 + .../hbase/regionserver/wal/ReplayHLogKey.java | 53 + .../RegionReplicaReplicationEndpoint.java | 2 +- .../hbase/util/ServerRegionReplicaUtil.java | 21 +- .../apache/hadoop/hbase/wal/WALSplitter.java | 12 +- .../hbase/regionserver/TestHRegion.java | 28 +- .../regionserver/TestHRegionReplayEvents.java | 1162 +++++++++++++++++ .../TestPerColumnFamilyFlush.java | 12 +- 19 files changed, 2439 insertions(+), 117 deletions(-) create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/ReplayHLogKey.java create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegionReplayEvents.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java index 43e91d23550..038b148d12e 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java @@ -2584,6 +2584,7 @@ public final class ProtobufUtil { FlushDescriptor.Builder desc = FlushDescriptor.newBuilder() .setAction(action) .setEncodedRegionName(ByteStringer.wrap(hri.getEncodedNameAsBytes())) + .setRegionName(ByteStringer.wrap(hri.getRegionName())) .setFlushSequenceNumber(flushSeqId) .setTableName(ByteStringer.wrap(hri.getTable().getName())); @@ -2609,6 +2610,7 @@ public final class ProtobufUtil { .setEventType(eventType) .setTableName(ByteStringer.wrap(hri.getTable().getName())) .setEncodedRegionName(ByteStringer.wrap(hri.getEncodedNameAsBytes())) + .setRegionName(ByteStringer.wrap(hri.getRegionName())) .setLogSequenceNumber(seqId) .setServer(toServerName(server)); diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java index 04551deb45f..3ae324a576f 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java @@ -47,7 +47,7 @@ import org.apache.hadoop.io.RawComparator; import com.google.common.annotations.VisibleForTesting; /** - * An HBase Key/Value. This is the fundamental HBase Type. + * An HBase Key/Value. This is the fundamental HBase Type. *

* HBase applications and users should use the Cell interface and avoid directly using KeyValue * and member functions not defined in Cell. @@ -297,6 +297,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, return seqId; } + @Override public void setSequenceId(long seqId) { this.seqId = seqId; } @@ -577,7 +578,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, this(row, roffset, rlength, family, foffset, flength, qualifier, qoffset, qlength, timestamp, type, value, voffset, vlength, null); } - + /** * Constructs KeyValue structure filled with specified values. Uses the provided buffer as the * data buffer. @@ -742,9 +743,9 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, public KeyValue(Cell c) { this(c.getRowArray(), c.getRowOffset(), (int)c.getRowLength(), - c.getFamilyArray(), c.getFamilyOffset(), (int)c.getFamilyLength(), - c.getQualifierArray(), c.getQualifierOffset(), (int) c.getQualifierLength(), - c.getTimestamp(), Type.codeToType(c.getTypeByte()), c.getValueArray(), c.getValueOffset(), + c.getFamilyArray(), c.getFamilyOffset(), (int)c.getFamilyLength(), + c.getQualifierArray(), c.getQualifierOffset(), (int) c.getQualifierLength(), + c.getTimestamp(), Type.codeToType(c.getTypeByte()), c.getValueArray(), c.getValueOffset(), c.getValueLength(), c.getTagsArray(), c.getTagsOffset(), c.getTagsLength()); this.seqId = c.getSequenceId(); } @@ -955,7 +956,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, final int rlength, final byte [] family, final int foffset, int flength, final byte [] qualifier, final int qoffset, int qlength, final long timestamp, final Type type, - final byte [] value, final int voffset, + final byte [] value, final int voffset, int vlength, byte[] tags, int tagsOffset, int tagsLength) { checkParameters(row, rlength, family, flength, qlength, vlength); @@ -1115,6 +1116,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, // //--------------------------------------------------------------------------- + @Override public String toString() { if (this.bytes == null || this.bytes.length == 0) { return "empty"; @@ -1125,10 +1127,10 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, /** * @param k Key portion of a KeyValue. - * @return Key as a String, empty string if k is null. + * @return Key as a String, empty string if k is null. */ public static String keyToString(final byte [] k) { - if (k == null) { + if (k == null) { return ""; } return keyToString(k, 0, k.length); @@ -1464,6 +1466,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, * save on allocations. * @return Value in a new byte array. */ + @Override @Deprecated // use CellUtil.getValueArray() public byte [] getValue() { return CellUtil.cloneValue(this); @@ -1477,6 +1480,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, * lengths instead. * @return Row in a new byte array. */ + @Override @Deprecated // use CellUtil.getRowArray() public byte [] getRow() { return CellUtil.cloneRow(this); @@ -1534,6 +1538,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, * lengths instead. * @return Returns family. Makes a copy. */ + @Override @Deprecated // use CellUtil.getFamilyArray public byte [] getFamily() { return CellUtil.cloneFamily(this); @@ -1548,6 +1553,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, * Use {@link #getBuffer()} with appropriate offsets and lengths instead. * @return Returns qualifier. Makes a copy. */ + @Override @Deprecated // use CellUtil.getQualifierArray public byte [] getQualifier() { return CellUtil.cloneQualifier(this); @@ -1846,7 +1852,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, return compareFlatKey(l,loff,llen, r,roff,rlen); } - + /** * Compares the only the user specified portion of a Key. This is overridden by MetaComparator. * @param left @@ -2355,7 +2361,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, in.readFully(bytes); return new KeyValue(bytes, 0, length); } - + /** * Create a new KeyValue by copying existing cell and adding new tags * @param c @@ -2371,9 +2377,9 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, existingTags = newTags; } return new KeyValue(c.getRowArray(), c.getRowOffset(), (int)c.getRowLength(), - c.getFamilyArray(), c.getFamilyOffset(), (int)c.getFamilyLength(), - c.getQualifierArray(), c.getQualifierOffset(), (int) c.getQualifierLength(), - c.getTimestamp(), Type.codeToType(c.getTypeByte()), c.getValueArray(), c.getValueOffset(), + c.getFamilyArray(), c.getFamilyOffset(), (int)c.getFamilyLength(), + c.getQualifierArray(), c.getQualifierOffset(), (int) c.getQualifierLength(), + c.getTimestamp(), Type.codeToType(c.getTypeByte()), c.getValueArray(), c.getValueOffset(), c.getValueLength(), existingTags); } @@ -2478,6 +2484,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, this.comparator = c; } + @Override public int compare(KeyValue left, KeyValue right) { return comparator.compareRows(left, right); } @@ -2486,7 +2493,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, /** * Avoids redundant comparisons for better performance. - * + * * TODO get rid of this wart */ public interface SamePrefixComparator { @@ -2509,6 +2516,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, * TODO: With V3 consider removing this. * @return legacy class name for FileFileTrailer#comparatorClassName */ + @Override public String getLegacyKeyComparatorName() { return "org.apache.hadoop.hbase.util.Bytes$ByteArrayComparator"; } @@ -2516,6 +2524,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, /** * @deprecated Since 0.99.2. */ + @Override @Deprecated public int compareFlatKey(byte[] left, int loffset, int llength, byte[] right, int roffset, int rlength) { @@ -2527,6 +2536,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, return compareOnlyKeyPortion(left, right); } + @Override @VisibleForTesting public int compareOnlyKeyPortion(Cell left, Cell right) { int c = Bytes.BYTES_RAWCOMPARATOR.compare(left.getRowArray(), left.getRowOffset(), @@ -2553,6 +2563,7 @@ public class KeyValue implements Cell, HeapSize, Cloneable, SettableSequenceId, return (0xff & left.getTypeByte()) - (0xff & right.getTypeByte()); } + @Override public byte[] calcIndexKey(byte[] lastKeyOfPreviousBlock, byte[] firstKeyInBlock) { return firstKeyInBlock; } diff --git a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/WALProtos.java b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/WALProtos.java index c9fa8548063..35192cccad6 100644 --- a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/WALProtos.java +++ b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/WALProtos.java @@ -5522,6 +5522,24 @@ public final class WALProtos { */ org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor.StoreFlushDescriptorOrBuilder getStoreFlushesOrBuilder( int index); + + // optional bytes region_name = 6; + /** + * optional bytes region_name = 6; + * + *

+     * full region name
+     * 
+ */ + boolean hasRegionName(); + /** + * optional bytes region_name = 6; + * + *
+     * full region name
+     * 
+ */ + com.google.protobuf.ByteString getRegionName(); } /** * Protobuf type {@code FlushDescriptor} @@ -5613,6 +5631,11 @@ public final class WALProtos { storeFlushes_.add(input.readMessage(org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor.StoreFlushDescriptor.PARSER, extensionRegistry)); break; } + case 50: { + bitField0_ |= 0x00000010; + regionName_ = input.readBytes(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -6772,12 +6795,37 @@ public final class WALProtos { return storeFlushes_.get(index); } + // optional bytes region_name = 6; + public static final int REGION_NAME_FIELD_NUMBER = 6; + private com.google.protobuf.ByteString regionName_; + /** + * optional bytes region_name = 6; + * + *
+     * full region name
+     * 
+ */ + public boolean hasRegionName() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional bytes region_name = 6; + * + *
+     * full region name
+     * 
+ */ + public com.google.protobuf.ByteString getRegionName() { + return regionName_; + } + private void initFields() { action_ = org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor.FlushAction.START_FLUSH; tableName_ = com.google.protobuf.ByteString.EMPTY; encodedRegionName_ = com.google.protobuf.ByteString.EMPTY; flushSequenceNumber_ = 0L; storeFlushes_ = java.util.Collections.emptyList(); + regionName_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -6824,6 +6872,9 @@ public final class WALProtos { for (int i = 0; i < storeFlushes_.size(); i++) { output.writeMessage(5, storeFlushes_.get(i)); } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBytes(6, regionName_); + } getUnknownFields().writeTo(output); } @@ -6853,6 +6904,10 @@ public final class WALProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(5, storeFlushes_.get(i)); } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(6, regionName_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -6898,6 +6953,11 @@ public final class WALProtos { } result = result && getStoreFlushesList() .equals(other.getStoreFlushesList()); + result = result && (hasRegionName() == other.hasRegionName()); + if (hasRegionName()) { + result = result && getRegionName() + .equals(other.getRegionName()); + } result = result && getUnknownFields().equals(other.getUnknownFields()); return result; @@ -6931,6 +6991,10 @@ public final class WALProtos { hash = (37 * hash) + STORE_FLUSHES_FIELD_NUMBER; hash = (53 * hash) + getStoreFlushesList().hashCode(); } + if (hasRegionName()) { + hash = (37 * hash) + REGION_NAME_FIELD_NUMBER; + hash = (53 * hash) + getRegionName().hashCode(); + } hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -7060,6 +7124,8 @@ public final class WALProtos { } else { storeFlushesBuilder_.clear(); } + regionName_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000020); return this; } @@ -7113,6 +7179,10 @@ public final class WALProtos { } else { result.storeFlushes_ = storeFlushesBuilder_.build(); } + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000010; + } + result.regionName_ = regionName_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -7167,6 +7237,9 @@ public final class WALProtos { } } } + if (other.hasRegionName()) { + setRegionName(other.getRegionName()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -7593,6 +7666,58 @@ public final class WALProtos { return storeFlushesBuilder_; } + // optional bytes region_name = 6; + private com.google.protobuf.ByteString regionName_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes region_name = 6; + * + *
+       * full region name
+       * 
+ */ + public boolean hasRegionName() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bytes region_name = 6; + * + *
+       * full region name
+       * 
+ */ + public com.google.protobuf.ByteString getRegionName() { + return regionName_; + } + /** + * optional bytes region_name = 6; + * + *
+       * full region name
+       * 
+ */ + public Builder setRegionName(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000020; + regionName_ = value; + onChanged(); + return this; + } + /** + * optional bytes region_name = 6; + * + *
+       * full region name
+       * 
+ */ + public Builder clearRegionName() { + bitField0_ = (bitField0_ & ~0x00000020); + regionName_ = getDefaultInstance().getRegionName(); + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:FlushDescriptor) } @@ -9772,6 +9897,24 @@ public final class WALProtos { * */ org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.ServerNameOrBuilder getServerOrBuilder(); + + // optional bytes region_name = 7; + /** + * optional bytes region_name = 7; + * + *
+     * full region name
+     * 
+ */ + boolean hasRegionName(); + /** + * optional bytes region_name = 7; + * + *
+     * full region name
+     * 
+ */ + com.google.protobuf.ByteString getRegionName(); } /** * Protobuf type {@code RegionEventDescriptor} @@ -9876,6 +10019,11 @@ public final class WALProtos { bitField0_ |= 0x00000010; break; } + case 58: { + bitField0_ |= 0x00000020; + regionName_ = input.readBytes(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -10135,6 +10283,30 @@ public final class WALProtos { return server_; } + // optional bytes region_name = 7; + public static final int REGION_NAME_FIELD_NUMBER = 7; + private com.google.protobuf.ByteString regionName_; + /** + * optional bytes region_name = 7; + * + *
+     * full region name
+     * 
+ */ + public boolean hasRegionName() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bytes region_name = 7; + * + *
+     * full region name
+     * 
+ */ + public com.google.protobuf.ByteString getRegionName() { + return regionName_; + } + private void initFields() { eventType_ = org.apache.hadoop.hbase.protobuf.generated.WALProtos.RegionEventDescriptor.EventType.REGION_OPEN; tableName_ = com.google.protobuf.ByteString.EMPTY; @@ -10142,6 +10314,7 @@ public final class WALProtos { logSequenceNumber_ = 0L; stores_ = java.util.Collections.emptyList(); server_ = org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.ServerName.getDefaultInstance(); + regionName_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -10197,6 +10370,9 @@ public final class WALProtos { if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeMessage(6, server_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeBytes(7, regionName_); + } getUnknownFields().writeTo(output); } @@ -10230,6 +10406,10 @@ public final class WALProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(6, server_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(7, regionName_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -10280,6 +10460,11 @@ public final class WALProtos { result = result && getServer() .equals(other.getServer()); } + result = result && (hasRegionName() == other.hasRegionName()); + if (hasRegionName()) { + result = result && getRegionName() + .equals(other.getRegionName()); + } result = result && getUnknownFields().equals(other.getUnknownFields()); return result; @@ -10317,6 +10502,10 @@ public final class WALProtos { hash = (37 * hash) + SERVER_FIELD_NUMBER; hash = (53 * hash) + getServer().hashCode(); } + if (hasRegionName()) { + hash = (37 * hash) + REGION_NAME_FIELD_NUMBER; + hash = (53 * hash) + getRegionName().hashCode(); + } hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -10453,6 +10642,8 @@ public final class WALProtos { serverBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000020); + regionName_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000040); return this; } @@ -10514,6 +10705,10 @@ public final class WALProtos { } else { result.server_ = serverBuilder_.build(); } + if (((from_bitField0_ & 0x00000040) == 0x00000040)) { + to_bitField0_ |= 0x00000020; + } + result.regionName_ = regionName_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -10571,6 +10766,9 @@ public final class WALProtos { if (other.hasServer()) { mergeServer(other.getServer()); } + if (other.hasRegionName()) { + setRegionName(other.getRegionName()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -11156,6 +11354,58 @@ public final class WALProtos { return serverBuilder_; } + // optional bytes region_name = 7; + private com.google.protobuf.ByteString regionName_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes region_name = 7; + * + *
+       * full region name
+       * 
+ */ + public boolean hasRegionName() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + /** + * optional bytes region_name = 7; + * + *
+       * full region name
+       * 
+ */ + public com.google.protobuf.ByteString getRegionName() { + return regionName_; + } + /** + * optional bytes region_name = 7; + * + *
+       * full region name
+       * 
+ */ + public Builder setRegionName(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000040; + regionName_ = value; + onChanged(); + return this; + } + /** + * optional bytes region_name = 7; + * + *
+       * full region name
+       * 
+ */ + public Builder clearRegionName() { + bitField0_ = (bitField0_ & ~0x00000040); + regionName_ = getDefaultInstance().getRegionName(); + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:RegionEventDescriptor) } @@ -11598,32 +11848,33 @@ public final class WALProtos { "n_name\030\002 \002(\014\022\023\n\013family_name\030\003 \002(\014\022\030\n\020com" + "paction_input\030\004 \003(\t\022\031\n\021compaction_output" + "\030\005 \003(\t\022\026\n\016store_home_dir\030\006 \002(\t\022\023\n\013region" + - "_name\030\007 \001(\014\"\353\002\n\017FlushDescriptor\022,\n\006actio" + + "_name\030\007 \001(\014\"\200\003\n\017FlushDescriptor\022,\n\006actio" + "n\030\001 \002(\0162\034.FlushDescriptor.FlushAction\022\022\n", "\ntable_name\030\002 \002(\014\022\033\n\023encoded_region_name" + "\030\003 \002(\014\022\035\n\025flush_sequence_number\030\004 \001(\004\022<\n" + "\rstore_flushes\030\005 \003(\0132%.FlushDescriptor.S" + - "toreFlushDescriptor\032Y\n\024StoreFlushDescrip" + - "tor\022\023\n\013family_name\030\001 \002(\014\022\026\n\016store_home_d" + - "ir\030\002 \002(\t\022\024\n\014flush_output\030\003 \003(\t\"A\n\013FlushA" + - "ction\022\017\n\013START_FLUSH\020\000\022\020\n\014COMMIT_FLUSH\020\001" + - "\022\017\n\013ABORT_FLUSH\020\002\"R\n\017StoreDescriptor\022\023\n\013" + - "family_name\030\001 \002(\014\022\026\n\016store_home_dir\030\002 \002(" + - "\t\022\022\n\nstore_file\030\003 \003(\t\"\215\001\n\022BulkLoadDescri", - "ptor\022\036\n\ntable_name\030\001 \002(\0132\n.TableName\022\033\n\023" + - "encoded_region_name\030\002 \002(\014\022 \n\006stores\030\003 \003(" + - "\0132\020.StoreDescriptor\022\030\n\020bulkload_seq_num\030" + - "\004 \002(\003\"\212\002\n\025RegionEventDescriptor\0224\n\nevent" + - "_type\030\001 \002(\0162 .RegionEventDescriptor.Even" + - "tType\022\022\n\ntable_name\030\002 \002(\014\022\033\n\023encoded_reg" + - "ion_name\030\003 \002(\014\022\033\n\023log_sequence_number\030\004 " + - "\001(\004\022 \n\006stores\030\005 \003(\0132\020.StoreDescriptor\022\033\n" + - "\006server\030\006 \001(\0132\013.ServerName\".\n\tEventType\022" + - "\017\n\013REGION_OPEN\020\000\022\020\n\014REGION_CLOSE\020\001\"\014\n\nWA", - "LTrailer*F\n\tScopeType\022\033\n\027REPLICATION_SCO" + - "PE_LOCAL\020\000\022\034\n\030REPLICATION_SCOPE_GLOBAL\020\001" + - "B?\n*org.apache.hadoop.hbase.protobuf.gen" + - "eratedB\tWALProtosH\001\210\001\000\240\001\001" + "toreFlushDescriptor\022\023\n\013region_name\030\006 \001(\014" + + "\032Y\n\024StoreFlushDescriptor\022\023\n\013family_name\030" + + "\001 \002(\014\022\026\n\016store_home_dir\030\002 \002(\t\022\024\n\014flush_o" + + "utput\030\003 \003(\t\"A\n\013FlushAction\022\017\n\013START_FLUS" + + "H\020\000\022\020\n\014COMMIT_FLUSH\020\001\022\017\n\013ABORT_FLUSH\020\002\"R" + + "\n\017StoreDescriptor\022\023\n\013family_name\030\001 \002(\014\022\026" + + "\n\016store_home_dir\030\002 \002(\t\022\022\n\nstore_file\030\003 \003", + "(\t\"\215\001\n\022BulkLoadDescriptor\022\036\n\ntable_name\030" + + "\001 \002(\0132\n.TableName\022\033\n\023encoded_region_name" + + "\030\002 \002(\014\022 \n\006stores\030\003 \003(\0132\020.StoreDescriptor" + + "\022\030\n\020bulkload_seq_num\030\004 \002(\003\"\237\002\n\025RegionEve" + + "ntDescriptor\0224\n\nevent_type\030\001 \002(\0162 .Regio" + + "nEventDescriptor.EventType\022\022\n\ntable_name" + + "\030\002 \002(\014\022\033\n\023encoded_region_name\030\003 \002(\014\022\033\n\023l" + + "og_sequence_number\030\004 \001(\004\022 \n\006stores\030\005 \003(\013" + + "2\020.StoreDescriptor\022\033\n\006server\030\006 \001(\0132\013.Ser" + + "verName\022\023\n\013region_name\030\007 \001(\014\".\n\tEventTyp", + "e\022\017\n\013REGION_OPEN\020\000\022\020\n\014REGION_CLOSE\020\001\"\014\n\n" + + "WALTrailer*F\n\tScopeType\022\033\n\027REPLICATION_S" + + "COPE_LOCAL\020\000\022\034\n\030REPLICATION_SCOPE_GLOBAL" + + "\020\001B?\n*org.apache.hadoop.hbase.protobuf.g" + + "eneratedB\tWALProtosH\001\210\001\000\240\001\001" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -11659,7 +11910,7 @@ public final class WALProtos { internal_static_FlushDescriptor_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_FlushDescriptor_descriptor, - new java.lang.String[] { "Action", "TableName", "EncodedRegionName", "FlushSequenceNumber", "StoreFlushes", }); + new java.lang.String[] { "Action", "TableName", "EncodedRegionName", "FlushSequenceNumber", "StoreFlushes", "RegionName", }); internal_static_FlushDescriptor_StoreFlushDescriptor_descriptor = internal_static_FlushDescriptor_descriptor.getNestedTypes().get(0); internal_static_FlushDescriptor_StoreFlushDescriptor_fieldAccessorTable = new @@ -11683,7 +11934,7 @@ public final class WALProtos { internal_static_RegionEventDescriptor_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_RegionEventDescriptor_descriptor, - new java.lang.String[] { "EventType", "TableName", "EncodedRegionName", "LogSequenceNumber", "Stores", "Server", }); + new java.lang.String[] { "EventType", "TableName", "EncodedRegionName", "LogSequenceNumber", "Stores", "Server", "RegionName", }); internal_static_WALTrailer_descriptor = getDescriptor().getMessageTypes().get(8); internal_static_WALTrailer_fieldAccessorTable = new diff --git a/hbase-protocol/src/main/protobuf/WAL.proto b/hbase-protocol/src/main/protobuf/WAL.proto index 169a9b2c3e4..3fd60255cba 100644 --- a/hbase-protocol/src/main/protobuf/WAL.proto +++ b/hbase-protocol/src/main/protobuf/WAL.proto @@ -122,6 +122,7 @@ message FlushDescriptor { required bytes encoded_region_name = 3; optional uint64 flush_sequence_number = 4; repeated StoreFlushDescriptor store_flushes = 5; + optional bytes region_name = 6; // full region name } message StoreDescriptor { @@ -155,6 +156,7 @@ message RegionEventDescriptor { optional uint64 log_sequence_number = 4; repeated StoreDescriptor stores = 5; optional ServerName server = 6; // Server who opened the region + optional bytes region_name = 7; // full region name } /** diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/DefaultMemStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/DefaultMemStore.java index 48b78c2305b..081d7a5a1ea 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/DefaultMemStore.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/DefaultMemStore.java @@ -208,6 +208,11 @@ public class DefaultMemStore implements MemStore { return this.snapshotSize > 0 ? this.snapshotSize : keySize(); } + @Override + public long getSnapshotSize() { + return this.snapshotSize; + } + /** * Write an update * @param cell @@ -462,6 +467,7 @@ public class DefaultMemStore implements MemStore { * @param now * @return Timestamp */ + @Override public long updateColumnValue(byte[] row, byte[] family, byte[] qualifier, @@ -524,7 +530,7 @@ public class DefaultMemStore implements MemStore { * atomically. Scans will only see each KeyValue update as atomic. * * @param cells - * @param readpoint readpoint below which we can safely remove duplicate KVs + * @param readpoint readpoint below which we can safely remove duplicate KVs * @return change in memstore size */ @Override @@ -1031,7 +1037,7 @@ public class DefaultMemStore implements MemStore { public long size() { return heapSize(); } - + /** * Code to help figure if our approximation of object heap sizes is close * enough. See hbase-900. Fills memstores then waits so user can heap diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java index 53e732a73f0..aa65ddd6109 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java @@ -34,6 +34,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.NavigableMap; import java.util.NavigableSet; import java.util.RandomAccess; @@ -61,7 +62,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import com.google.protobuf.ByteString; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; @@ -72,7 +72,6 @@ import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellScanner; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.CompoundConfiguration; -import org.apache.hadoop.hbase.DoNotRetryIOException; import org.apache.hadoop.hbase.DroppedSnapshotException; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HColumnDescriptor; @@ -100,6 +99,7 @@ import org.apache.hadoop.hbase.client.Increment; import org.apache.hadoop.hbase.client.IsolationLevel; import org.apache.hadoop.hbase.client.Mutation; import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.RegionReplicaUtil; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.RowMutations; import org.apache.hadoop.hbase.client.Scan; @@ -133,12 +133,16 @@ import org.apache.hadoop.hbase.protobuf.generated.WALProtos; import org.apache.hadoop.hbase.protobuf.generated.WALProtos.CompactionDescriptor; import org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor; import org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor.FlushAction; +import org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor.StoreFlushDescriptor; import org.apache.hadoop.hbase.protobuf.generated.WALProtos.RegionEventDescriptor; +import org.apache.hadoop.hbase.protobuf.generated.WALProtos.RegionEventDescriptor.EventType; +import org.apache.hadoop.hbase.protobuf.generated.WALProtos.StoreDescriptor; import org.apache.hadoop.hbase.regionserver.MultiVersionConsistencyControl.WriteEntry; import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext; import org.apache.hadoop.hbase.regionserver.compactions.NoLimitCompactionThroughputController; import org.apache.hadoop.hbase.regionserver.compactions.CompactionThroughputController; import org.apache.hadoop.hbase.regionserver.wal.HLogKey; +import org.apache.hadoop.hbase.regionserver.wal.ReplayHLogKey; import org.apache.hadoop.hbase.regionserver.wal.WALEdit; import org.apache.hadoop.hbase.regionserver.wal.WALUtil; import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; @@ -176,6 +180,7 @@ import com.google.protobuf.Message; import com.google.protobuf.RpcCallback; import com.google.protobuf.RpcController; import com.google.protobuf.Service; +import com.google.protobuf.TextFormat; /** * HRegion stores data for a certain region of a table. It stores all columns @@ -254,6 +259,13 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // */ private final AtomicLong sequenceId = new AtomicLong(-1L); + /** + * The sequence id of the last replayed open region event from the primary region. This is used + * to skip entries before this due to the possibility of replay edits coming out of order from + * replication. + */ + protected volatile long lastReplayedOpenRegionSeqId = -1L; + /** * Operation enum is used in {@link HRegion#startRegionOperation} to provide operation context for * startRegionOperation to possibly invoke different checks before any region operations. Not all @@ -262,7 +274,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // */ public enum Operation { ANY, GET, PUT, DELETE, SCAN, APPEND, INCREMENT, SPLIT_REGION, MERGE_REGION, BATCH_MUTATE, - REPLAY_BATCH_MUTATE, COMPACT_REGION + REPLAY_BATCH_MUTATE, COMPACT_REGION, REPLAY_EVENT } ////////////////////////////////////////////////////////////////////////////// @@ -367,6 +379,9 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // // The following map is populated when opening the region Map maxSeqIdInStores = new TreeMap(Bytes.BYTES_COMPARATOR); + /** Saved state from replaying prepare flush cache */ + private PrepareFlushResult prepareFlushResult = null; + /** * Config setting for whether to allow writes when a region is in recovering or not. */ @@ -516,6 +531,54 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // public boolean isCompactionNeeded() { return result == Result.FLUSHED_COMPACTION_NEEDED; } + + @Override + public String toString() { + return new StringBuilder() + .append("flush result:").append(result).append(", ") + .append("failureReason:").append(failureReason).append(",") + .append("flush seq id").append(flushSequenceId).toString(); + } + } + + /** A result object from prepare flush cache stage */ + @VisibleForTesting + static class PrepareFlushResult { + final FlushResult result; // indicating a failure result from prepare + final TreeMap storeFlushCtxs; + final TreeMap> committedFiles; + final long startTime; + final long flushOpSeqId; + final long flushedSeqId; + final long totalFlushableSize; + + /** Constructs an early exit case */ + PrepareFlushResult(FlushResult result, long flushSeqId) { + this(result, null, null, Math.max(0, flushSeqId), 0, 0, 0); + } + + /** Constructs a successful prepare flush result */ + PrepareFlushResult( + TreeMap storeFlushCtxs, + TreeMap> committedFiles, long startTime, long flushSeqId, + long flushedSeqId, long totalFlushableSize) { + this(null, storeFlushCtxs, committedFiles, startTime, + flushSeqId, flushedSeqId, totalFlushableSize); + } + + private PrepareFlushResult( + FlushResult result, + TreeMap storeFlushCtxs, + TreeMap> committedFiles, long startTime, long flushSeqId, + long flushedSeqId, long totalFlushableSize) { + this.result = result; + this.storeFlushCtxs = storeFlushCtxs; + this.committedFiles = committedFiles; + this.startTime = startTime; + this.flushOpSeqId = flushSeqId; + this.flushedSeqId = flushedSeqId; + this.totalFlushableSize = totalFlushableSize; + } } final WriteState writestate = new WriteState(); @@ -771,6 +834,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // // Initialize all the HStores status.setStatus("Initializing all the Stores"); long maxSeqId = initializeRegionStores(reporter, status); + this.lastReplayedOpenRegionSeqId = maxSeqId; this.writestate.setReadOnly(ServerRegionReplicaUtil.isReadOnly(this)); this.writestate.flushRequested = false; @@ -1229,9 +1293,11 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // } status.setStatus("Disabling compacts and flushes for region"); + boolean canFlush = true; synchronized (writestate) { // Disable compacting and flushing by background threads for this // region. + canFlush = !writestate.readOnly; writestate.writesEnabled = false; LOG.debug("Closing " + this + ": disabling compactions & flushes"); waitForFlushesAndCompactions(); @@ -1239,7 +1305,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // // If we were not just flushing, is it worth doing a preflush...one // that will clear out of the bulk of the memstore before we put up // the close flag? - if (!abort && worthPreFlushing()) { + if (!abort && worthPreFlushing() && canFlush) { status.setStatus("Pre-flushing region before close"); LOG.info("Running close preflush of " + this.getRegionNameAsString()); try { @@ -1262,7 +1328,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // } LOG.debug("Updates disabled for region " + this); // Don't flush the cache if we are aborting - if (!abort) { + if (!abort && canFlush) { int flushCount = 0; while (this.getMemstoreSize().get() > 0) { try { @@ -1300,7 +1366,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // // close each store in parallel for (final Store store : stores.values()) { - assert abort || store.getFlushableSize() == 0; + assert abort || store.getFlushableSize() == 0 || writestate.readOnly; completionService .submit(new Callable>>() { @Override @@ -1336,7 +1402,11 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // } this.closed.set(true); - if (memstoreSize.get() != 0) LOG.error("Memstore size is " + memstoreSize.get()); + if (!canFlush) { + addAndGetGlobalMemstoreSize(-memstoreSize.get()); + } else if (memstoreSize.get() != 0) { + LOG.error("Memstore size is " + memstoreSize.get()); + } if (coprocessorHost != null) { status.setStatus("Running coprocessor post-close hooks"); this.coprocessorHost.postClose(abort); @@ -1362,6 +1432,11 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // */ public void waitForFlushesAndCompactions() { synchronized (writestate) { + if (this.writestate.readOnly) { + // we should not wait for replayed flushed if we are read only (for example in case the + // region is a secondary replica). + return; + } boolean interrupted = false; try { while (writestate.compacting > 0 || writestate.flushing) { @@ -1592,6 +1667,22 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // } /** + * This is a helper function that compact the given store + * It is used by utilities and testing + * + * @throws IOException e + */ + @VisibleForTesting + void compactStore(byte[] family, CompactionThroughputController throughputController) + throws IOException { + Store s = getStore(family); + CompactionContext compaction = s.requestCompaction(); + if (compaction != null) { + compact(compaction, s, throughputController); + } + } + + /* * Called by compaction thread and after region is opened to compact the * HStores if necessary. * @@ -1738,6 +1829,8 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // status.setStatus("Running coprocessor pre-flush hooks"); coprocessorHost.preFlush(); } + // TODO: this should be managed within memstore with the snapshot, updated only after flush + // successful if (numMutationsWithoutWAL.get() > 0) { numMutationsWithoutWAL.set(0); dataInMemoryWithoutWAL.set(0); @@ -1903,6 +1996,20 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // */ protected FlushResult internalFlushcache(final WAL wal, final long myseqid, final Collection storesToFlush, MonitoredTask status) throws IOException { + PrepareFlushResult result + = internalPrepareFlushCache(wal, myseqid, storesToFlush, status, false); + if (result.result == null) { + return internalFlushCacheAndCommit(wal, status, result, storesToFlush); + } else { + return result.result; // early exit due to failure from prepare stage + } + } + + protected PrepareFlushResult internalPrepareFlushCache( + final WAL wal, final long myseqid, final Collection storesToFlush, + MonitoredTask status, boolean isReplay) + throws IOException { + if (this.rsServices != null && this.rsServices.isAborted()) { // Don't flush when server aborting, it's unsafe throw new IOException("Aborting flush because server is aborted..."); @@ -1930,10 +2037,11 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // w.setWriteNumber(flushSeqId); mvcc.waitForPreviousTransactionsComplete(w); w = null; - return flushResult; + return new PrepareFlushResult(flushResult, myseqid); } else { - return new FlushResult(FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY, - "Nothing to flush"); + return new PrepareFlushResult( + new FlushResult(FlushResult.Result.CANNOT_FLUSH_MEMSTORE_EMPTY, "Nothing to flush"), + myseqid); } } } finally { @@ -1977,7 +2085,8 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // flushedFamilyNames.add(store.getFamily().getName()); } - List storeFlushCtxs = new ArrayList(stores.size()); + TreeMap storeFlushCtxs + = new TreeMap(Bytes.BYTES_COMPARATOR); TreeMap> committedFiles = new TreeMap>( Bytes.BYTES_COMPARATOR); // The sequence id of this flush operation which is used to log FlushMarker and pass to @@ -1998,7 +2107,8 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // String msg = "Flush will not be started for [" + this.getRegionInfo().getEncodedName() + "] - because the WAL is closing."; status.setStatus(msg); - return new FlushResult(FlushResult.Result.CANNOT_FLUSH, msg); + return new PrepareFlushResult(new FlushResult(FlushResult.Result.CANNOT_FLUSH, msg), + myseqid); } flushOpSeqId = getNextSequenceId(wal); long oldestUnflushedSeqId = wal.getEarliestMemstoreSeqNum(encodedRegionName); @@ -2013,12 +2123,12 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // for (Store s : storesToFlush) { totalFlushableSizeOfFlushableStores += s.getFlushableSize(); - storeFlushCtxs.add(s.createFlushContext(flushOpSeqId)); + storeFlushCtxs.put(s.getFamily().getName(), s.createFlushContext(flushOpSeqId)); committedFiles.put(s.getFamily().getName(), null); // for writing stores to WAL } // write the snapshot start to WAL - if (wal != null) { + if (wal != null && !writestate.readOnly) { FlushDescriptor desc = ProtobufUtil.toFlushDescriptor(FlushAction.START_FLUSH, getRegionInfo(), flushOpSeqId, committedFiles); // no sync. Sync is below where we do not hold the updates lock @@ -2027,7 +2137,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // } // Prepare flush (take a snapshot) - for (StoreFlushContext flush : storeFlushCtxs) { + for (StoreFlushContext flush : storeFlushCtxs.values()) { flush.prepare(); } } catch (IOException ex) { @@ -2075,15 +2185,32 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // mvcc.waitForPreviousTransactionsComplete(w); // set w to null to prevent mvcc.advanceMemstore from being called again inside finally block w = null; - s = "Flushing stores of " + this; - status.setStatus(s); - if (LOG.isTraceEnabled()) LOG.trace(s); } finally { if (w != null) { // in case of failure just mark current w as complete mvcc.advanceMemstore(w); } } + return new PrepareFlushResult(storeFlushCtxs, committedFiles, startTime, flushOpSeqId, + flushedSeqId, totalFlushableSizeOfFlushableStores); + } + + protected FlushResult internalFlushCacheAndCommit( + final WAL wal, MonitoredTask status, final PrepareFlushResult prepareResult, + final Collection storesToFlush) + throws IOException { + + // prepare flush context is carried via PrepareFlushResult + TreeMap storeFlushCtxs = prepareResult.storeFlushCtxs; + TreeMap> committedFiles = prepareResult.committedFiles; + long startTime = prepareResult.startTime; + long flushOpSeqId = prepareResult.flushOpSeqId; + long flushedSeqId = prepareResult.flushedSeqId; + long totalFlushableSizeOfFlushableStores = prepareResult.totalFlushableSize; + + String s = "Flushing stores of " + this; + status.setStatus(s); + if (LOG.isTraceEnabled()) LOG.trace(s); // Any failure from here on out will be catastrophic requiring server // restart so wal content can be replayed and put back into the memstore. @@ -2096,7 +2223,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // // just-made new flush store file. The new flushed file is still in the // tmp directory. - for (StoreFlushContext flush : storeFlushCtxs) { + for (StoreFlushContext flush : storeFlushCtxs.values()) { flush.flushCache(status); } @@ -2104,7 +2231,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // // all the store scanners to reset/reseek). Iterator it = storesToFlush.iterator(); // stores.values() and storeFlushCtxs have same order - for (StoreFlushContext flush : storeFlushCtxs) { + for (StoreFlushContext flush : storeFlushCtxs.values()) { boolean needsCompaction = flush.commit(status); if (needsCompaction) { compactionRequested = true; @@ -2593,6 +2720,25 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // */ public OperationStatus[] batchReplay(MutationReplay[] mutations, long replaySeqId) throws IOException { + if (!RegionReplicaUtil.isDefaultReplica(getRegionInfo()) + && replaySeqId < lastReplayedOpenRegionSeqId) { + // if it is a secondary replica we should ignore these entries silently + // since they are coming out of order + if (LOG.isTraceEnabled()) { + LOG.trace(getRegionInfo().getEncodedName() + " : " + + "Skipping " + mutations.length + " mutations with replaySeqId=" + replaySeqId + + " which is < than lastReplayedOpenRegionSeqId=" + lastReplayedOpenRegionSeqId); + for (MutationReplay mut : mutations) { + LOG.trace(getRegionInfo().getEncodedName() + " : Skipping : " + mut.mutation); + } + } + + OperationStatus[] statuses = new OperationStatus[mutations.length]; + for (int i = 0; i < statuses.length; i++) { + statuses[i] = OperationStatus.SUCCESS; + } + return statuses; + } return batchMutate(new ReplayBatch(mutations, replaySeqId)); } @@ -2897,7 +3043,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // } // txid should always increase, so having the one from the last call is ok. // we use HLogKey here instead of WALKey directly to support legacy coprocessors. - walKey = new HLogKey(this.getRegionInfo().getEncodedNameAsBytes(), + walKey = new ReplayHLogKey(this.getRegionInfo().getEncodedNameAsBytes(), this.htableDescriptor.getTableName(), now, m.getClusterIds(), currentNonceGroup, currentNonce); txid = this.wal.append(this.htableDescriptor, this.getRegionInfo(), walKey, @@ -2923,14 +3069,29 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // // STEP 5. Append the final edit to WAL. Do not sync wal. // ------------------------- Mutation mutation = batchOp.getMutation(firstIndex); + if (isInReplay) { + // use wal key from the original + walKey = new ReplayHLogKey(this.getRegionInfo().getEncodedNameAsBytes(), + this.htableDescriptor.getTableName(), WALKey.NO_SEQUENCE_ID, now, + mutation.getClusterIds(), currentNonceGroup, currentNonce); + long replaySeqId = batchOp.getReplaySequenceId(); + walKey.setOrigLogSeqNum(replaySeqId); + + // ensure that the sequence id of the region is at least as big as orig log seq id + while (true) { + long seqId = getSequenceId().get(); + if (seqId >= replaySeqId) break; + if (getSequenceId().compareAndSet(seqId, replaySeqId)) break; + } + } if (walEdit.size() > 0) { + if (!isInReplay) { // we use HLogKey here instead of WALKey directly to support legacy coprocessors. walKey = new HLogKey(this.getRegionInfo().getEncodedNameAsBytes(), this.htableDescriptor.getTableName(), WALKey.NO_SEQUENCE_ID, now, mutation.getClusterIds(), currentNonceGroup, currentNonce); - if(isInReplay) { - walKey.setOrigLogSeqNum(batchOp.getReplaySequenceId()); } + txid = this.wal.append(this.htableDescriptor, this.getRegionInfo(), walKey, walEdit, getSequenceId(), true, memstoreCells); } @@ -3803,7 +3964,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // CompactionDescriptor compaction = WALEdit.getCompaction(cell); if (compaction != null) { //replay the compaction - completeCompactionMarker(compaction); + replayWALCompactionMarker(compaction, false, true, Long.MAX_VALUE); } skippedEdits++; continue; @@ -3886,15 +4047,506 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // * that was not finished. We could find one recovering a WAL after a regionserver crash. * See HBASE-2331. */ - void completeCompactionMarker(CompactionDescriptor compaction) + void replayWALCompactionMarker(CompactionDescriptor compaction, boolean pickCompactionFiles, + boolean removeFiles, long replaySeqId) throws IOException { - Store store = this.getStore(compaction.getFamilyName().toByteArray()); - if (store == null) { - LOG.warn("Found Compaction WAL edit for deleted family:" + - Bytes.toString(compaction.getFamilyName().toByteArray())); + checkTargetRegion(compaction.getEncodedRegionName().toByteArray(), + "Compaction marker from WAL ", compaction); + + if (replaySeqId < lastReplayedOpenRegionSeqId) { + LOG.warn("Skipping replaying compaction event :" + TextFormat.shortDebugString(compaction) + + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId " + + " of " + lastReplayedOpenRegionSeqId); return; } - store.completeCompactionMarker(compaction); + + startRegionOperation(Operation.REPLAY_EVENT); + try { + Store store = this.getStore(compaction.getFamilyName().toByteArray()); + if (store == null) { + LOG.warn("Found Compaction WAL edit for deleted family:" + + Bytes.toString(compaction.getFamilyName().toByteArray())); + return; + } + store.replayCompactionMarker(compaction, pickCompactionFiles, removeFiles); + } finally { + closeRegionOperation(Operation.REPLAY_EVENT); + } + } + + void replayWALFlushMarker(FlushDescriptor flush) throws IOException { + checkTargetRegion(flush.getEncodedRegionName().toByteArray(), + "Flush marker from WAL ", flush); + + if (ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) { + return; // if primary nothing to do + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Replaying flush marker " + TextFormat.shortDebugString(flush)); + } + + startRegionOperation(Operation.REPLAY_EVENT); // use region close lock to guard against close + try { + FlushAction action = flush.getAction(); + switch (action) { + case START_FLUSH: + replayWALFlushStartMarker(flush); + break; + case COMMIT_FLUSH: + replayWALFlushCommitMarker(flush); + break; + case ABORT_FLUSH: + replayWALFlushAbortMarker(flush); + break; + default: + LOG.warn("Received a flush event with unknown action, ignoring. " + + TextFormat.shortDebugString(flush)); + break; + } + } finally { + closeRegionOperation(Operation.REPLAY_EVENT); + } + } + + /** Replay the flush marker from primary region by creating a corresponding snapshot of + * the store memstores, only if the memstores do not have a higher seqId from an earlier wal + * edit (because the events may be coming out of order). + */ + @VisibleForTesting + PrepareFlushResult replayWALFlushStartMarker(FlushDescriptor flush) throws IOException { + long flushSeqId = flush.getFlushSequenceNumber(); + + HashSet storesToFlush = new HashSet(); + for (StoreFlushDescriptor storeFlush : flush.getStoreFlushesList()) { + byte[] family = storeFlush.getFamilyName().toByteArray(); + Store store = getStore(family); + if (store == null) { + LOG.info("Received a flush start marker from primary, but the family is not found. Ignoring" + + " StoreFlushDescriptor:" + TextFormat.shortDebugString(storeFlush)); + continue; + } + storesToFlush.add(store); + } + + MonitoredTask status = TaskMonitor.get().createStatus("Preparing flush " + this); + + // we will use writestate as a coarse-grain lock for all the replay events + // (flush, compaction, region open etc) + synchronized (writestate) { + try { + if (flush.getFlushSequenceNumber() < lastReplayedOpenRegionSeqId) { + LOG.warn("Skipping replaying flush event :" + TextFormat.shortDebugString(flush) + + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId " + + " of " + lastReplayedOpenRegionSeqId); + return null; + } + if (numMutationsWithoutWAL.get() > 0) { + numMutationsWithoutWAL.set(0); + dataInMemoryWithoutWAL.set(0); + } + + if (!writestate.flushing) { + // we do not have an active snapshot and corresponding this.prepareResult. This means + // we can just snapshot our memstores and continue as normal. + + // invoke prepareFlushCache. Send null as wal since we do not want the flush events in wal + PrepareFlushResult prepareResult = internalPrepareFlushCache(null, + flushSeqId, storesToFlush, status, true); + if (prepareResult.result == null) { + // save the PrepareFlushResult so that we can use it later from commit flush + this.writestate.flushing = true; + this.prepareFlushResult = prepareResult; + status.markComplete("Flush prepare successful"); + if (LOG.isDebugEnabled()) { + LOG.debug(getRegionInfo().getEncodedName() + " : " + + " Prepared flush with seqId:" + flush.getFlushSequenceNumber()); + } + } else { + status.abort("Flush prepare failed with " + prepareResult.result); + // nothing much to do. prepare flush failed because of some reason. + } + return prepareResult; + } else { + // we already have an active snapshot. + if (flush.getFlushSequenceNumber() == this.prepareFlushResult.flushOpSeqId) { + // They define the same flush. Log and continue. + LOG.warn("Received a flush prepare marker with the same seqId: " + + + flush.getFlushSequenceNumber() + " before clearing the previous one with seqId: " + + prepareFlushResult.flushOpSeqId + ". Ignoring"); + // ignore + } else if (flush.getFlushSequenceNumber() < this.prepareFlushResult.flushOpSeqId) { + // We received a flush with a smaller seqNum than what we have prepared. We can only + // ignore this prepare flush request. + LOG.warn("Received a flush prepare marker with a smaller seqId: " + + + flush.getFlushSequenceNumber() + " before clearing the previous one with seqId: " + + prepareFlushResult.flushOpSeqId + ". Ignoring"); + // ignore + } else { + // We received a flush with a larger seqNum than what we have prepared + LOG.warn("Received a flush prepare marker with a larger seqId: " + + + flush.getFlushSequenceNumber() + " before clearing the previous one with seqId: " + + prepareFlushResult.flushOpSeqId + ". Ignoring"); + // We do not have multiple active snapshots in the memstore or a way to merge current + // memstore snapshot with the contents and resnapshot for now. We cannot take + // another snapshot and drop the previous one because that will cause temporary + // data loss in the secondary. So we ignore this for now, deferring the resolution + // to happen when we see the corresponding flush commit marker. If we have a memstore + // snapshot with x, and later received another prepare snapshot with y (where x < y), + // when we see flush commit for y, we will drop snapshot for x, and can also drop all + // the memstore edits if everything in memstore is < y. This is the usual case for + // RS crash + recovery where we might see consequtive prepare flush wal markers. + // Otherwise, this will cause more memory to be used in secondary replica until a + // further prapare + commit flush is seen and replayed. + } + } + } finally { + status.cleanup(); + writestate.notifyAll(); + } + } + return null; + } + + @VisibleForTesting + void replayWALFlushCommitMarker(FlushDescriptor flush) throws IOException { + MonitoredTask status = TaskMonitor.get().createStatus("Committing flush " + this); + + // check whether we have the memstore snapshot with the corresponding seqId. Replay to + // secondary region replicas are in order, except for when the region moves or then the + // region server crashes. In those cases, we may receive replay requests out of order from + // the original seqIds. + synchronized (writestate) { + try { + if (flush.getFlushSequenceNumber() < lastReplayedOpenRegionSeqId) { + LOG.warn("Skipping replaying flush event :" + TextFormat.shortDebugString(flush) + + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId " + + " of " + lastReplayedOpenRegionSeqId); + return; + } + + if (writestate.flushing) { + PrepareFlushResult prepareFlushResult = this.prepareFlushResult; + if (flush.getFlushSequenceNumber() == prepareFlushResult.flushOpSeqId) { + if (LOG.isDebugEnabled()) { + LOG.debug(getRegionInfo().getEncodedName() + " : " + + "Received a flush commit marker with seqId:" + flush.getFlushSequenceNumber() + + " and a previous prepared snapshot was found"); + } + // This is the regular case where we received commit flush after prepare flush + // corresponding to the same seqId. + replayFlushInStores(flush, prepareFlushResult, true); + + // Set down the memstore size by amount of flush. + this.addAndGetGlobalMemstoreSize(-prepareFlushResult.totalFlushableSize); + + this.prepareFlushResult = null; + writestate.flushing = false; + } else if (flush.getFlushSequenceNumber() < prepareFlushResult.flushOpSeqId) { + // This should not happen normally. However, lets be safe and guard against these cases + // we received a flush commit with a smaller seqId than what we have prepared + // we will pick the flush file up from this commit (if we have not seen it), but we + // will not drop the memstore + LOG.warn("Received a flush commit marker with smaller seqId: " + + flush.getFlushSequenceNumber() + " than what we have prepared with seqId: " + + prepareFlushResult.flushOpSeqId + ". Picking up new file, but not dropping" + +" prepared memstore snapshot"); + replayFlushInStores(flush, prepareFlushResult, false); + + // snapshot is not dropped, so memstore sizes should not be decremented + // we still have the prepared snapshot, flushing should still be true + } else { + // This should not happen normally. However, lets be safe and guard against these cases + // we received a flush commit with a larger seqId than what we have prepared + // we will pick the flush file for this. We will also obtain the updates lock and + // look for contents of the memstore to see whether we have edits after this seqId. + // If not, we will drop all the memstore edits and the snapshot as well. + LOG.warn("Received a flush commit marker with larger seqId: " + + flush.getFlushSequenceNumber() + " than what we have prepared with seqId: " + + prepareFlushResult.flushOpSeqId + ". Picking up new file and dropping prepared" + +" memstore snapshot"); + + replayFlushInStores(flush, prepareFlushResult, true); + + // Set down the memstore size by amount of flush. + this.addAndGetGlobalMemstoreSize(-prepareFlushResult.totalFlushableSize); + + // Inspect the memstore contents to see whether the memstore contains only edits + // with seqId smaller than the flush seqId. If so, we can discard those edits. + dropMemstoreContentsForSeqId(flush.getFlushSequenceNumber(), null); + + this.prepareFlushResult = null; + writestate.flushing = false; + } + } else { + LOG.warn(getRegionInfo().getEncodedName() + " : " + + "Received a flush commit marker with seqId:" + flush.getFlushSequenceNumber() + + ", but no previous prepared snapshot was found"); + // There is no corresponding prepare snapshot from before. + // We will pick up the new flushed file + replayFlushInStores(flush, null, false); + + // Inspect the memstore contents to see whether the memstore contains only edits + // with seqId smaller than the flush seqId. If so, we can discard those edits. + dropMemstoreContentsForSeqId(flush.getFlushSequenceNumber(), null); + } + + status.markComplete("Flush commit successful"); + + // Update the last flushed sequence id for region. + this.maxFlushedSeqId = flush.getFlushSequenceNumber(); + + // advance the mvcc read point so that the new flushed file is visible. + // there may be some in-flight transactions, but they won't be made visible since they are + // either greater than flush seq number or they were already dropped via flush. + // TODO: If we are using FlushAllStoresPolicy, then this can make edits visible from other + // stores while they are still in flight because the flush commit marker will not contain + // flushes from ALL stores. + getMVCC().advanceMemstoreReadPointIfNeeded(flush.getFlushSequenceNumber()); + + // C. Finally notify anyone waiting on memstore to clear: + // e.g. checkResources(). + synchronized (this) { + notifyAll(); // FindBugs NN_NAKED_NOTIFY + } + } finally { + status.cleanup(); + writestate.notifyAll(); + } + } + } + + /** + * Replays the given flush descriptor by opening the flush files in stores and dropping the + * memstore snapshots if requested. + * @param flush + * @param prepareFlushResult + * @param dropMemstoreSnapshot + * @throws IOException + */ + private void replayFlushInStores(FlushDescriptor flush, PrepareFlushResult prepareFlushResult, + boolean dropMemstoreSnapshot) + throws IOException { + for (StoreFlushDescriptor storeFlush : flush.getStoreFlushesList()) { + byte[] family = storeFlush.getFamilyName().toByteArray(); + Store store = getStore(family); + if (store == null) { + LOG.warn("Received a flush commit marker from primary, but the family is not found." + + "Ignoring StoreFlushDescriptor:" + storeFlush); + continue; + } + List flushFiles = storeFlush.getFlushOutputList(); + StoreFlushContext ctx = null; + long startTime = EnvironmentEdgeManager.currentTime(); + if (prepareFlushResult == null) { + ctx = store.createFlushContext(flush.getFlushSequenceNumber()); + } else { + ctx = prepareFlushResult.storeFlushCtxs.get(family); + startTime = prepareFlushResult.startTime; + } + + if (ctx == null) { + LOG.warn("Unexpected: flush commit marker received from store " + + Bytes.toString(family) + " but no associated flush context. Ignoring"); + continue; + } + ctx.replayFlush(flushFiles, dropMemstoreSnapshot); // replay the flush + + // Record latest flush time + this.lastStoreFlushTimeMap.put(store, startTime); + } + } + + /** + * Drops the memstore contents after replaying a flush descriptor or region open event replay + * if the memstore edits have seqNums smaller than the given seq id + * @param flush the flush descriptor + * @throws IOException + */ + private void dropMemstoreContentsForSeqId(long seqId, Store store) throws IOException { + this.updatesLock.writeLock().lock(); + try { + mvcc.waitForPreviousTransactionsComplete(); + long currentSeqId = getSequenceId().get(); + if (seqId >= currentSeqId) { + // then we can drop the memstore contents since everything is below this seqId + LOG.info("Dropping memstore contents as well since replayed flush seqId: " + + seqId + " is greater than current seqId:" + currentSeqId); + + // Prepare flush (take a snapshot) and then abort (drop the snapshot) + if (store == null ) { + for (Store s : stores.values()) { + dropStoreMemstoreContentsForSeqId(s, currentSeqId); + } + } else { + dropStoreMemstoreContentsForSeqId(store, currentSeqId); + } + } else { + LOG.info("Not dropping memstore contents since replayed flush seqId: " + + seqId + " is smaller than current seqId:" + currentSeqId); + } + } finally { + this.updatesLock.writeLock().unlock(); + } + } + + private void dropStoreMemstoreContentsForSeqId(Store s, long currentSeqId) throws IOException { + this.addAndGetGlobalMemstoreSize(-s.getFlushableSize()); + StoreFlushContext ctx = s.createFlushContext(currentSeqId); + ctx.prepare(); + ctx.abort(); + } + + private void replayWALFlushAbortMarker(FlushDescriptor flush) { + // nothing to do for now. A flush abort will cause a RS abort which means that the region + // will be opened somewhere else later. We will see the region open event soon, and replaying + // that will drop the snapshot + } + + @VisibleForTesting + PrepareFlushResult getPrepareFlushResult() { + return prepareFlushResult; + } + + void replayWALRegionEventMarker(RegionEventDescriptor regionEvent) throws IOException { + checkTargetRegion(regionEvent.getEncodedRegionName().toByteArray(), + "RegionEvent marker from WAL ", regionEvent); + + startRegionOperation(Operation.REPLAY_EVENT); + try { + if (ServerRegionReplicaUtil.isDefaultReplica(this.getRegionInfo())) { + return; // if primary nothing to do + } + + if (regionEvent.getEventType() == EventType.REGION_CLOSE) { + // nothing to do on REGION_CLOSE for now. + return; + } + if (regionEvent.getEventType() != EventType.REGION_OPEN) { + LOG.warn("Unknown region event received, ignoring :" + + TextFormat.shortDebugString(regionEvent)); + return; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Replaying region open event marker " + TextFormat.shortDebugString(regionEvent)); + } + + // we will use writestate as a coarse-grain lock for all the replay events + synchronized (writestate) { + // Replication can deliver events out of order when primary region moves or the region + // server crashes, since there is no coordination between replication of different wal files + // belonging to different region servers. We have to safe guard against this case by using + // region open event's seqid. Since this is the first event that the region puts (after + // possibly flushing recovered.edits), after seeing this event, we can ignore every edit + // smaller than this seqId + if (this.lastReplayedOpenRegionSeqId < regionEvent.getLogSequenceNumber()) { + this.lastReplayedOpenRegionSeqId = regionEvent.getLogSequenceNumber(); + } else { + LOG.warn("Skipping replaying region event :" + TextFormat.shortDebugString(regionEvent) + + " because its sequence id is smaller than this regions lastReplayedOpenRegionSeqId " + + " of " + lastReplayedOpenRegionSeqId); + return; + } + + // region open lists all the files that the region has at the time of the opening. Just pick + // all the files and drop prepared flushes and empty memstores + for (StoreDescriptor storeDescriptor : regionEvent.getStoresList()) { + // stores of primary may be different now + byte[] family = storeDescriptor.getFamilyName().toByteArray(); + Store store = getStore(family); + if (store == null) { + LOG.warn("Received a region open marker from primary, but the family is not found. " + + "Ignoring. StoreDescriptor:" + storeDescriptor); + continue; + } + + long storeSeqId = store.getMaxSequenceId(); + List storeFiles = storeDescriptor.getStoreFileList(); + store.refreshStoreFiles(storeFiles); // replace the files with the new ones + if (store.getMaxSequenceId() != storeSeqId) { + // Record latest flush time if we picked up new files + lastStoreFlushTimeMap.put(store, EnvironmentEdgeManager.currentTime()); + } + + if (writestate.flushing) { + // only drop memstore snapshots if they are smaller than last flush for the store + if (this.prepareFlushResult.flushOpSeqId <= regionEvent.getLogSequenceNumber()) { + StoreFlushContext ctx = this.prepareFlushResult.storeFlushCtxs.get(family); + if (ctx != null) { + long snapshotSize = store.getFlushableSize(); + ctx.abort(); + this.addAndGetGlobalMemstoreSize(-snapshotSize); + this.prepareFlushResult.storeFlushCtxs.remove(family); + } + } + } + + // Drop the memstore contents if they are now smaller than the latest seen flushed file + dropMemstoreContentsForSeqId(regionEvent.getLogSequenceNumber(), store); + if (storeSeqId > this.maxFlushedSeqId) { + this.maxFlushedSeqId = storeSeqId; + } + } + + // if all stores ended up dropping their snapshots, we can safely drop the + // prepareFlushResult + if (writestate.flushing) { + boolean canDrop = true; + for (Entry entry + : prepareFlushResult.storeFlushCtxs.entrySet()) { + Store store = getStore(entry.getKey()); + if (store == null) { + continue; + } + if (store.getSnapshotSize() > 0) { + canDrop = false; + } + } + + // this means that all the stores in the region has finished flushing, but the WAL marker + // may not have been written or we did not receive it yet. + if (canDrop) { + writestate.flushing = false; + this.prepareFlushResult = null; + } + } + + + // advance the mvcc read point so that the new flushed file is visible. + // there may be some in-flight transactions, but they won't be made visible since they are + // either greater than flush seq number or they were already dropped via flush. + getMVCC().advanceMemstoreReadPointIfNeeded(this.maxFlushedSeqId); + + // C. Finally notify anyone waiting on memstore to clear: + // e.g. checkResources(). + synchronized (this) { + notifyAll(); // FindBugs NN_NAKED_NOTIFY + } + } + } finally { + closeRegionOperation(Operation.REPLAY_EVENT); + } + } + + /** Checks whether the given regionName is either equal to our region, or that + * the regionName is the primary region to our corresponding range for the secondary replica. + */ + private void checkTargetRegion(byte[] encodedRegionName, String exceptionMsg, Object payload) + throws WrongRegionException { + if (Bytes.equals(this.getRegionInfo().getEncodedNameAsBytes(), encodedRegionName)) { + return; + } + + if (!RegionReplicaUtil.isDefaultReplica(this.getRegionInfo()) && + Bytes.equals(encodedRegionName, + this.fs.getRegionInfoForFS().getEncodedNameAsBytes())) { + return; + } + + throw new WrongRegionException(exceptionMsg + payload + + " targetted for region " + Bytes.toStringBinary(encodedRegionName) + + " does not match this region: " + this.getRegionInfo()); } /** @@ -4127,8 +4779,8 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // * @param familyPaths List of Pair * @param bulkLoadListener Internal hooks enabling massaging/preparation of a * file about to be bulk loaded - * @param assignSeqId Force a flush, get it's sequenceId to preserve the guarantee that - * all the edits lower than the highest sequential ID from all the + * @param assignSeqId Force a flush, get it's sequenceId to preserve the guarantee that + * all the edits lower than the highest sequential ID from all the * HFiles are flushed on disk. * @return true if successful, false if failed recoverably * @throws IOException if failed unrecoverably. @@ -4217,7 +4869,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // finalPath = bulkLoadListener.prepareBulkLoad(familyName, path); } store.bulkLoadHFile(finalPath, seqId); - + if(storeFiles.containsKey(familyName)) { storeFiles.get(familyName).add(new Path(finalPath)); } else { @@ -4265,7 +4917,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // } } } - + closeBulkRegionOperation(); } } @@ -4989,7 +5641,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // checkClassLoading(); this.openSeqNum = initialize(reporter); this.setSequenceId(openSeqNum); - if (wal != null && getRegionServerServices() != null) { + if (wal != null && getRegionServerServices() != null && !writestate.readOnly) { writeRegionOpenMarker(wal, openSeqNum); } return this; @@ -5660,7 +6312,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // } } if (cell.getTagsLength() > 0) { - Iterator i = CellUtil.tagsIterator(cell.getTagsArray(), + Iterator i = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(), cell.getTagsLength()); while (i.hasNext()) { newTags.add(i.next()); @@ -6080,8 +6732,8 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // public static final long FIXED_OVERHEAD = ClassSize.align( ClassSize.OBJECT + ClassSize.ARRAY + - 44 * ClassSize.REFERENCE + 2 * Bytes.SIZEOF_INT + - (11 * Bytes.SIZEOF_LONG) + + 45 * ClassSize.REFERENCE + 2 * Bytes.SIZEOF_INT + + (12 * Bytes.SIZEOF_LONG) + 4 * Bytes.SIZEOF_BOOLEAN); // woefully out of date - currently missing: diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionFileSystem.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionFileSystem.java index 0751634263a..014ec2c0dae 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionFileSystem.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionFileSystem.java @@ -119,6 +119,10 @@ public class HRegionFileSystem { return this.regionInfo; } + public HRegionInfo getRegionInfoForFS() { + return this.regionInfoForFs; + } + /** @return {@link Path} to the region's root directory. */ public Path getTableDir() { return this.tableDir; @@ -205,7 +209,7 @@ public class HRegionFileSystem { continue; } StoreFileInfo info = ServerRegionReplicaUtil.getStoreFileInfo(conf, fs, regionInfo, - regionInfoForFs, familyName, status); + regionInfoForFs, familyName, status.getPath()); storeFiles.add(info); } @@ -234,8 +238,8 @@ public class HRegionFileSystem { StoreFileInfo getStoreFileInfo(final String familyName, final String fileName) throws IOException { Path familyDir = getStoreDir(familyName); - FileStatus status = fs.getFileStatus(new Path(familyDir, fileName)); - return new StoreFileInfo(this.conf, this.fs, status); + return ServerRegionReplicaUtil.getStoreFileInfo(conf, fs, regionInfo, + regionInfoForFs, familyName, new Path(familyDir, fileName)); } /** diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java index 252e5e13cd0..df9e4828597 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java @@ -398,6 +398,11 @@ public class HStore implements Store { return this.memstore.getFlushableSize(); } + @Override + public long getSnapshotSize() { + return this.memstore.getSnapshotSize(); + } + @Override public long getCompactionCheckMultiplier() { return this.compactionCheckMultiplier; @@ -448,7 +453,8 @@ public class HStore implements Store { /** * @return The maximum sequence id in all store files. Used for log replay. */ - long getMaxSequenceId() { + @Override + public long getMaxSequenceId() { return StoreFile.getMaxSequenceIdInList(this.getStorefiles()); } @@ -576,11 +582,31 @@ public class HStore implements Store { */ @Override public void refreshStoreFiles() throws IOException { + Collection newFiles = fs.getStoreFiles(getColumnFamilyName()); + refreshStoreFilesInternal(newFiles); + } + + @Override + public void refreshStoreFiles(Collection newFiles) throws IOException { + List storeFiles = new ArrayList(newFiles.size()); + for (String file : newFiles) { + storeFiles.add(fs.getStoreFileInfo(getColumnFamilyName(), file)); + } + refreshStoreFilesInternal(storeFiles); + } + + /** + * Checks the underlying store files, and opens the files that have not + * been opened, and removes the store file readers for store files no longer + * available. Mainly used by secondary region replicas to keep up to date with + * the primary region files. + * @throws IOException + */ + private void refreshStoreFilesInternal(Collection newFiles) throws IOException { StoreFileManager sfm = storeEngine.getStoreFileManager(); Collection currentFiles = sfm.getStorefiles(); if (currentFiles == null) currentFiles = new ArrayList(0); - Collection newFiles = fs.getStoreFiles(getColumnFamilyName()); if (newFiles == null) newFiles = new ArrayList(0); HashMap currentFilesSet = new HashMap(currentFiles.size()); @@ -1011,7 +1037,9 @@ public class HStore implements Store { this.lock.writeLock().lock(); try { this.storeEngine.getStoreFileManager().insertNewFiles(sfs); - this.memstore.clearSnapshot(snapshotId); + if (snapshotId > 0) { + this.memstore.clearSnapshot(snapshotId); + } } finally { // We need the lock, as long as we are updating the storeFiles // or changing the memstore. Let us release it before calling @@ -1311,10 +1339,12 @@ public class HStore implements Store { * @param compaction */ @Override - public void completeCompactionMarker(CompactionDescriptor compaction) + public void replayCompactionMarker(CompactionDescriptor compaction, + boolean pickCompactionFiles, boolean removeFiles) throws IOException { LOG.debug("Completing compaction from the WAL marker"); List compactionInputs = compaction.getCompactionInputList(); + List compactionOutputs = Lists.newArrayList(compaction.getCompactionOutputList()); // The Compaction Marker is written after the compaction is completed, // and the files moved into the region/family folder. @@ -1331,22 +1361,40 @@ public class HStore implements Store { // being in the store's folder) or they may be missing due to a compaction. String familyName = this.getColumnFamilyName(); - List inputPaths = new ArrayList(compactionInputs.size()); + List inputFiles = new ArrayList(compactionInputs.size()); for (String compactionInput : compactionInputs) { Path inputPath = fs.getStoreFilePath(familyName, compactionInput); - inputPaths.add(inputPath); + inputFiles.add(inputPath.getName()); } //some of the input files might already be deleted List inputStoreFiles = new ArrayList(compactionInputs.size()); for (StoreFile sf : this.getStorefiles()) { - if (inputPaths.contains(sf.getQualifiedPath())) { + if (inputFiles.contains(sf.getPath().getName())) { inputStoreFiles.add(sf); } } - this.replaceStoreFiles(inputStoreFiles, Collections.emptyList()); - this.completeCompaction(inputStoreFiles); + // check whether we need to pick up the new files + List outputStoreFiles = new ArrayList(compactionOutputs.size()); + + if (pickCompactionFiles) { + for (StoreFile sf : this.getStorefiles()) { + compactionOutputs.remove(sf.getPath().getName()); + } + for (String compactionOutput : compactionOutputs) { + StoreFileInfo storeFileInfo = fs.getStoreFileInfo(getColumnFamilyName(), compactionOutput); + StoreFile storeFile = createStoreFileAndReader(storeFileInfo); + outputStoreFiles.add(storeFile); + } + } + + if (!inputStoreFiles.isEmpty() || !outputStoreFiles.isEmpty()) { + LOG.info("Replaying compaction marker, replacing input files: " + + inputStoreFiles + " with output files : " + outputStoreFiles); + this.replaceStoreFiles(inputStoreFiles, outputStoreFiles); + this.completeCompaction(inputStoreFiles, removeFiles); + } } /** @@ -2175,6 +2223,47 @@ public class HStore implements Store { public List getCommittedFiles() { return committedFiles; } + + /** + * Similar to commit, but called in secondary region replicas for replaying the + * flush cache from primary region. Adds the new files to the store, and drops the + * snapshot depending on dropMemstoreSnapshot argument. + * @param fileNames names of the flushed files + * @param dropMemstoreSnapshot whether to drop the prepared memstore snapshot + * @throws IOException + */ + @Override + public void replayFlush(List fileNames, boolean dropMemstoreSnapshot) + throws IOException { + List storeFiles = new ArrayList(fileNames.size()); + for (String file : fileNames) { + // open the file as a store file (hfile link, etc) + StoreFileInfo storeFileInfo = fs.getStoreFileInfo(getColumnFamilyName(), file); + StoreFile storeFile = createStoreFileAndReader(storeFileInfo); + storeFiles.add(storeFile); + if (LOG.isInfoEnabled()) { + LOG.info("Region: " + HStore.this.getRegionInfo().getEncodedName() + + " added " + storeFile + ", entries=" + storeFile.getReader().getEntries() + + ", sequenceid=" + + storeFile.getReader().getSequenceID() + + ", filesize=" + StringUtils.humanReadableInt(storeFile.getReader().length())); + } + } + + long snapshotId = dropMemstoreSnapshot ? snapshot.getId() : -1; // -1 means do not drop + HStore.this.updateStorefiles(storeFiles, snapshotId); + } + + /** + * Abort the snapshot preparation. Drops the snapshot if any. + * @throws IOException + */ + @Override + public void abort() throws IOException { + if (snapshot == null) { + return; + } + HStore.this.updateStorefiles(new ArrayList(0), snapshot.getId()); + } } @Override diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MemStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MemStore.java index 7fa81fc9f21..364b9c9b639 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MemStore.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/MemStore.java @@ -59,6 +59,12 @@ public interface MemStore extends HeapSize { */ long getFlushableSize(); + /** + * Return the size of the snapshot(s) if any + * @return size of the memstore snapshot + */ + long getSnapshotSize(); + /** * Write an update * @param cell diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java index 3653cfb9274..3944ae8808a 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java @@ -144,6 +144,8 @@ import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionSpecifier; import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionSpecifier.RegionSpecifierType; import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.RequestHeader; import org.apache.hadoop.hbase.protobuf.generated.WALProtos.CompactionDescriptor; +import org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor; +import org.apache.hadoop.hbase.protobuf.generated.WALProtos.RegionEventDescriptor; import org.apache.hadoop.hbase.quotas.OperationQuota; import org.apache.hadoop.hbase.quotas.RegionServerQuotaManager; import org.apache.hadoop.hbase.regionserver.HRegion.Operation; @@ -712,8 +714,23 @@ public class RSRpcServices implements HBaseRPCErrorHandler, if (metaCells != null && !metaCells.isEmpty()) { for (Cell metaCell : metaCells) { CompactionDescriptor compactionDesc = WALEdit.getCompaction(metaCell); + boolean isDefaultReplica = RegionReplicaUtil.isDefaultReplica(region.getRegionInfo()); if (compactionDesc != null) { - region.completeCompactionMarker(compactionDesc); + // replay the compaction. Remove the files from stores only if we are the primary + // region replica (thus own the files) + region.replayWALCompactionMarker(compactionDesc, !isDefaultReplica, isDefaultReplica, + replaySeqId); + continue; + } + FlushDescriptor flushDesc = WALEdit.getFlushDescriptor(metaCell); + if (flushDesc != null && !isDefaultReplica) { + region.replayWALFlushMarker(flushDesc); + continue; + } + RegionEventDescriptor regionEvent = WALEdit.getRegionEventDescriptor(metaCell); + if (regionEvent != null && !isDefaultReplica) { + region.replayWALRegionEventMarker(regionEvent); + continue; } } it.remove(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/Store.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/Store.java index 0c420b5ce41..6a422a9533b 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/Store.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/Store.java @@ -213,9 +213,13 @@ public interface Store extends HeapSize, StoreConfigInformation, PropagatingConf * Call to complete a compaction. Its for the case where we find in the WAL a compaction * that was not finished. We could find one recovering a WAL after a regionserver crash. * See HBASE-2331. - * @param compaction + * @param compaction the descriptor for compaction + * @param pickCompactionFiles whether or not pick up the new compaction output files and + * add it to the store + * @param removeFiles whether to remove/archive files from filesystem */ - void completeCompactionMarker(CompactionDescriptor compaction) + void replayCompactionMarker(CompactionDescriptor compaction, boolean pickCompactionFiles, + boolean removeFiles) throws IOException; // Split oriented methods @@ -265,8 +269,19 @@ public interface Store extends HeapSize, StoreConfigInformation, PropagatingConf */ long getFlushableSize(); + /** + * Returns the memstore snapshot size + * @return size of the memstore snapshot + */ + long getSnapshotSize(); + HColumnDescriptor getFamily(); + /** + * @return The maximum sequence id in all store files. + */ + long getMaxSequenceId(); + /** * @return The maximum memstoreTS in all store files. */ @@ -416,4 +431,13 @@ public interface Store extends HeapSize, StoreConfigInformation, PropagatingConf * linear formula. */ double getCompactionPressure(); + + /** + * Replaces the store files that the store has with the given files. Mainly used by + * secondary region replicas to keep up to date with + * the primary region files. + * @throws IOException + */ + void refreshStoreFiles(Collection newFiles) throws IOException; + } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFlushContext.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFlushContext.java index 0c2fe6fd19c..34ba1fa4637 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFlushContext.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFlushContext.java @@ -64,6 +64,22 @@ interface StoreFlushContext { */ boolean commit(MonitoredTask status) throws IOException; + /** + * Similar to commit, but called in secondary region replicas for replaying the + * flush cache from primary region. Adds the new files to the store, and drops the + * snapshot depending on dropMemstoreSnapshot argument. + * @param fileNames names of the flushed files + * @param dropMemstoreSnapshot whether to drop the prepared memstore snapshot + * @throws IOException + */ + void replayFlush(List fileNames, boolean dropMemstoreSnapshot) throws IOException; + + /** + * Abort the snapshot preparation. Drops the snapshot if any. + * @throws IOException + */ + void abort() throws IOException; + /** * Returns the newly committed files from the flush. Called only if commit returns true * @return a list of Paths for new files diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/ReplayHLogKey.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/ReplayHLogKey.java new file mode 100644 index 00000000000..4506b19ac08 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/ReplayHLogKey.java @@ -0,0 +1,53 @@ +/** + * 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.hbase.regionserver.wal; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.hbase.TableName; + +/** + * An HLogKey specific to WalEdits coming from replay. + */ +@InterfaceAudience.Private +public class ReplayHLogKey extends HLogKey { + + public ReplayHLogKey(final byte [] encodedRegionName, final TableName tablename, + final long now, List clusterIds, long nonceGroup, long nonce) { + super(encodedRegionName, tablename, now, clusterIds, nonceGroup, nonce); + } + + public ReplayHLogKey(final byte [] encodedRegionName, final TableName tablename, + long logSeqNum, final long now, List clusterIds, long nonceGroup, long nonce) { + super(encodedRegionName, tablename, logSeqNum, now, clusterIds, nonceGroup, nonce); + } + + /** + * Returns the original sequence id + * @return long the new assigned sequence number + * @throws InterruptedException + */ + @Override + public long getSequenceId() throws IOException { + return this.getOrigLogSeqNum(); + } +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java index c3d4e5a5cc6..fc19603d815 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/RegionReplicaReplicationEndpoint.java @@ -287,7 +287,7 @@ public class RegionReplicaReplicationEndpoint extends HBaseReplicationEndpoint { @Override public List finishWritingAndClose() throws IOException { - finishWriting(); + finishWriting(true); return null; } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ServerRegionReplicaUtil.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ServerRegionReplicaUtil.java index cf8721967f7..7bcee0b1de9 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ServerRegionReplicaUtil.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/ServerRegionReplicaUtil.java @@ -21,8 +21,8 @@ package org.apache.hadoop.hbase.util; import java.io.IOException; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.client.RegionReplicaUtil; import org.apache.hadoop.hbase.client.replication.ReplicationAdmin; @@ -96,23 +96,24 @@ public class ServerRegionReplicaUtil extends RegionReplicaUtil { * @throws IOException */ public static StoreFileInfo getStoreFileInfo(Configuration conf, FileSystem fs, - HRegionInfo regionInfo, HRegionInfo regionInfoForFs, String familyName, FileStatus status) + HRegionInfo regionInfo, HRegionInfo regionInfoForFs, String familyName, Path path) throws IOException { // if this is a primary region, just return the StoreFileInfo constructed from path if (regionInfo.equals(regionInfoForFs)) { - return new StoreFileInfo(conf, fs, status); - } - - if (StoreFileInfo.isReference(status.getPath())) { - Reference reference = Reference.read(fs, status.getPath()); - return new StoreFileInfo(conf, fs, status, reference); + return new StoreFileInfo(conf, fs, path); } // else create a store file link. The link file does not exists on filesystem though. HFileLink link = HFileLink.build(conf, regionInfoForFs.getTable(), - regionInfoForFs.getEncodedName(), familyName, status.getPath().getName()); - return new StoreFileInfo(conf, fs, status, link); + regionInfoForFs.getEncodedName(), familyName, path.getName()); + + if (StoreFileInfo.isReference(path)) { + Reference reference = Reference.read(fs, path); + return new StoreFileInfo(conf, fs, link.getFileStatus(fs), reference); + } + + return new StoreFileInfo(conf, fs, link.getFileStatus(fs), link); } /** diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/wal/WALSplitter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/wal/WALSplitter.java index 4d8cc2dcc2b..53f46b48e39 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/wal/WALSplitter.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/wal/WALSplitter.java @@ -1186,12 +1186,18 @@ public class WALSplitter { * @return true when there is no error * @throws IOException */ - protected boolean finishWriting() throws IOException { + protected boolean finishWriting(boolean interrupt) throws IOException { LOG.debug("Waiting for split writer threads to finish"); boolean progress_failed = false; for (WriterThread t : writerThreads) { t.finish(); } + if (interrupt) { + for (WriterThread t : writerThreads) { + t.interrupt(); // interrupt the writer threads. We are stopping now. + } + } + for (WriterThread t : writerThreads) { if (!progress_failed && reporter != null && !reporter.progress()) { progress_failed = true; @@ -1260,7 +1266,7 @@ public class WALSplitter { boolean isSuccessful = false; List result = null; try { - isSuccessful = finishWriting(); + isSuccessful = finishWriting(false); } finally { result = close(); List thrown = closeLogWriters(null); @@ -1960,7 +1966,7 @@ public class WALSplitter { @Override public List finishWritingAndClose() throws IOException { try { - if (!finishWriting()) { + if (!finishWriting(false)) { return null; } if (hasEditsInDisablingOrDisabledTables) { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java index ea063469f30..2930f726821 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java @@ -61,6 +61,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; @@ -4655,7 +4656,7 @@ public class TestHRegion { // create a primary region, load some data and flush // create a secondary region, and do a get against that Path rootDir = new Path(dir + "testRegionReplicaSecondary"); - FSUtils.setRootDir(TEST_UTIL.getConfiguration(), rootDir); + FSUtils.setRootDir(TEST_UTIL.getConfiguration(), rootDir); byte[][] families = new byte[][] { Bytes.toBytes("cf1"), Bytes.toBytes("cf2"), Bytes.toBytes("cf3") @@ -4755,6 +4756,14 @@ public class TestHRegion { } } + static WALFactory createWALFactory(Configuration conf, Path rootDir) throws IOException { + Configuration confForWAL = new Configuration(conf); + confForWAL.set(HConstants.HBASE_DIR, rootDir.toString()); + return new WALFactory(confForWAL, + Collections.singletonList(new MetricsWAL()), + "hregion-" + RandomStringUtils.randomNumeric(8)); + } + @Test public void testCompactionFromPrimary() throws IOException { Path rootDir = new Path(dir + "testRegionReplicaSecondary"); @@ -4815,9 +4824,14 @@ public class TestHRegion { private void putData(HRegion region, int startRow, int numRows, byte[] qf, byte[]... families) throws IOException { + putData(region, Durability.SKIP_WAL, startRow, numRows, qf, families); + } + + static void putData(HRegion region, Durability durability, + int startRow, int numRows, byte[] qf, byte[]... families) throws IOException { for (int i = startRow; i < startRow + numRows; i++) { Put put = new Put(Bytes.toBytes("" + i)); - put.setDurability(Durability.SKIP_WAL); + put.setDurability(durability); for (byte[] family : families) { put.add(family, qf, null); } @@ -4825,7 +4839,7 @@ public class TestHRegion { } } - private void verifyData(HRegion newReg, int startRow, int numRows, byte[] qf, byte[]... families) + static void verifyData(HRegion newReg, int startRow, int numRows, byte[] qf, byte[]... families) throws IOException { for (int i = startRow; i < startRow + numRows; i++) { byte[] row = Bytes.toBytes("" + i); @@ -4844,7 +4858,7 @@ public class TestHRegion { } } - private void assertGet(final HRegion r, final byte[] family, final byte[] k) throws IOException { + static void assertGet(final HRegion r, final byte[] family, final byte[] k) throws IOException { // Now I have k, get values out and assert they are as expected. Get get = new Get(k).addFamily(family).setMaxVersions(); Cell[] results = r.get(get).rawCells(); @@ -4991,7 +5005,7 @@ public class TestHRegion { return initHRegion(tableName, null, null, callingMethod, conf, isReadOnly, families); } - private static HRegion initHRegion(byte[] tableName, byte[] startKey, byte[] stopKey, + public static HRegion initHRegion(byte[] tableName, byte[] startKey, byte[] stopKey, String callingMethod, Configuration conf, boolean isReadOnly, byte[]... families) throws IOException { Path logDir = TEST_UTIL.getDataTestDirOnTestFS(callingMethod + ".log"); @@ -5013,7 +5027,7 @@ public class TestHRegion { * @return A region on which you must call * {@link HBaseTestingUtility#closeRegionAndWAL(HRegion)} when done. */ - private static HRegion initHRegion(byte[] tableName, byte[] startKey, byte[] stopKey, + public static HRegion initHRegion(byte[] tableName, byte[] startKey, byte[] stopKey, String callingMethod, Configuration conf, boolean isReadOnly, Durability durability, WAL wal, byte[]... families) throws IOException { return TEST_UTIL.createLocalHRegion(tableName, startKey, stopKey, callingMethod, conf, @@ -6028,7 +6042,7 @@ public class TestHRegion { } } - private static HRegion initHRegion(byte[] tableName, String callingMethod, + static HRegion initHRegion(byte[] tableName, String callingMethod, byte[]... families) throws IOException { return initHRegion(tableName, callingMethod, HBaseConfiguration.create(), families); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegionReplayEvents.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegionReplayEvents.java new file mode 100644 index 00000000000..09e9d5edc88 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegionReplayEvents.java @@ -0,0 +1,1162 @@ +/** + * 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.hbase.regionserver; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.apache.hadoop.hbase.regionserver.TestHRegion.*; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Durability; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType; +import org.apache.hadoop.hbase.protobuf.generated.WALProtos.CompactionDescriptor; +import org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor; +import org.apache.hadoop.hbase.protobuf.generated.WALProtos.RegionEventDescriptor; +import org.apache.hadoop.hbase.protobuf.generated.WALProtos.FlushDescriptor.FlushAction; +import org.apache.hadoop.hbase.regionserver.HRegion.FlushResult; +import org.apache.hadoop.hbase.regionserver.HRegion.PrepareFlushResult; +import org.apache.hadoop.hbase.regionserver.compactions.NoLimitCompactionThroughputController; +import org.apache.hadoop.hbase.regionserver.wal.WALEdit; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; +import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper; +import org.apache.hadoop.hbase.wal.DefaultWALProvider; +import org.apache.hadoop.hbase.wal.WAL; +import org.apache.hadoop.hbase.wal.WALFactory; +import org.apache.hadoop.hbase.wal.WALKey; +import org.apache.hadoop.hbase.wal.WALSplitter.MutationReplay; +import org.apache.hadoop.util.StringUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TestName; + +import com.google.common.collect.Lists; +import com.google.protobuf.ByteString; + +/** + * Tests of HRegion methods for replaying flush, compaction, region open, etc events for secondary + * region replicas + */ +@Category(MediumTests.class) +public class TestHRegionReplayEvents { + + static final Log LOG = LogFactory.getLog(TestHRegion.class); + @Rule public TestName name = new TestName(); + + private static HBaseTestingUtility TEST_UTIL; + + public static Configuration CONF ; + private String dir; + private static FileSystem FILESYSTEM; + + private byte[][] families = new byte[][] { + Bytes.toBytes("cf1"), Bytes.toBytes("cf2"), Bytes.toBytes("cf3")}; + + // Test names + protected byte[] tableName; + protected String method; + protected final byte[] row = Bytes.toBytes("rowA"); + protected final byte[] row2 = Bytes.toBytes("rowB"); + protected byte[] cq = Bytes.toBytes("cq"); + + // per test fields + private Path rootDir; + private HTableDescriptor htd; + private long time; + private RegionServerServices rss; + private HRegionInfo primaryHri, secondaryHri; + private HRegion primaryRegion, secondaryRegion; + private WALFactory wals; + private WAL walPrimary, walSecondary; + private WAL.Reader reader; + + @Before + public void setup() throws IOException { + TEST_UTIL = HBaseTestingUtility.createLocalHTU(); + FILESYSTEM = TEST_UTIL.getTestFileSystem(); + CONF = TEST_UTIL.getConfiguration(); + dir = TEST_UTIL.getDataTestDir("TestHRegionReplayEvents").toString(); + method = name.getMethodName(); + tableName = Bytes.toBytes(name.getMethodName()); + rootDir = new Path(dir + method); + TEST_UTIL.getConfiguration().set(HConstants.HBASE_DIR, rootDir.toString()); + method = name.getMethodName(); + + htd = new HTableDescriptor(TableName.valueOf(method)); + for (byte[] family : families) { + htd.addFamily(new HColumnDescriptor(family)); + } + + time = System.currentTimeMillis(); + + primaryHri = new HRegionInfo(htd.getTableName(), + HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW, + false, time, 0); + secondaryHri = new HRegionInfo(htd.getTableName(), + HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW, + false, time, 1); + + wals = TestHRegion.createWALFactory(CONF, rootDir); + walPrimary = wals.getWAL(primaryHri.getEncodedNameAsBytes()); + walSecondary = wals.getWAL(secondaryHri.getEncodedNameAsBytes()); + + rss = mock(RegionServerServices.class); + when(rss.getServerName()).thenReturn(ServerName.valueOf("foo", 1, 1)); + when(rss.getConfiguration()).thenReturn(CONF); + when(rss.getRegionServerAccounting()).thenReturn(new RegionServerAccounting()); + + primaryRegion = HRegion.createHRegion(primaryHri, rootDir, CONF, htd, walPrimary); + primaryRegion.close(); + + primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null); + secondaryRegion = HRegion.openHRegion(secondaryHri, htd, null, CONF, rss, null); + + reader = null; + } + + @After + public void tearDown() throws Exception { + if (reader != null) { + reader.close(); + } + + if (primaryRegion != null) { + HBaseTestingUtility.closeRegionAndWAL(primaryRegion); + } + if (secondaryRegion != null) { + HBaseTestingUtility.closeRegionAndWAL(secondaryRegion); + } + + EnvironmentEdgeManagerTestHelper.reset(); + LOG.info("Cleaning test directory: " + TEST_UTIL.getDataTestDir()); + TEST_UTIL.cleanupTestDir(); + } + + String getName() { + return name.getMethodName(); + } + + // Some of the test cases are as follows: + // 1. replay flush start marker again + // 2. replay flush with smaller seqId than what is there in memstore snapshot + // 3. replay flush with larger seqId than what is there in memstore snapshot + // 4. replay flush commit without flush prepare. non droppable memstore + // 5. replay flush commit without flush prepare. droppable memstore + // 6. replay open region event + // 7. replay open region event after flush start + // 8. replay flush form an earlier seqId (test ignoring seqIds) + // 9. start flush does not prevent region from closing. + + @Test + public void testRegionReplicaSecondaryCannotFlush() throws IOException { + // load some data and flush ensure that the secondary replica will not execute the flush + + // load some data to secondary by replaying + putDataByReplay(secondaryRegion, 0, 1000, cq, families); + + verifyData(secondaryRegion, 0, 1000, cq, families); + + // flush region + FlushResult flush = secondaryRegion.flushcache(); + assertEquals(flush.result, FlushResult.Result.CANNOT_FLUSH); + + verifyData(secondaryRegion, 0, 1000, cq, families); + + // close the region, and inspect that it has not flushed + Map> files = secondaryRegion.close(false); + // assert that there are no files (due to flush) + for (List f : files.values()) { + assertTrue(f.isEmpty()); + } + } + + /** + * Tests a case where we replay only a flush start marker, then the region is closed. This region + * should not block indefinitely + */ + @Test (timeout = 60000) + public void testOnlyReplayingFlushStartDoesNotHoldUpRegionClose() throws IOException { + // load some data to primary and flush + int start = 0; + LOG.info("-- Writing some data to primary from " + start + " to " + (start+100)); + putData(primaryRegion, Durability.SYNC_WAL, start, 100, cq, families); + LOG.info("-- Flushing primary, creating 3 files for 3 stores"); + primaryRegion.flushcache(); + + // now replay the edits and the flush marker + reader = createWALReaderForPrimary(); + + LOG.info("-- Replaying edits and flush events in secondary"); + while (true) { + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + if (flushDesc != null) { + if (flushDesc.getAction() == FlushAction.START_FLUSH) { + LOG.info("-- Replaying flush start in secondary"); + PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(flushDesc); + } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) { + LOG.info("-- NOT Replaying flush commit in secondary"); + } + } else { + replayEdit(secondaryRegion, entry); + } + } + + assertTrue(rss.getRegionServerAccounting().getGlobalMemstoreSize() > 0); + // now close the region which should not cause hold because of un-committed flush + secondaryRegion.close(); + + // verify that the memstore size is back to what it was + assertEquals(0, rss.getRegionServerAccounting().getGlobalMemstoreSize()); + } + + static int replayEdit(HRegion region, WAL.Entry entry) throws IOException { + if (WALEdit.isMetaEditFamily(entry.getEdit().getCells().get(0))) { + return 0; // handled elsewhere + } + Put put = new Put(entry.getEdit().getCells().get(0).getRow()); + for (Cell cell : entry.getEdit().getCells()) put.add(cell); + put.setDurability(Durability.SKIP_WAL); + MutationReplay mutation = new MutationReplay(MutationType.PUT, put, 0, 0); + region.batchReplay(new MutationReplay[] {mutation}, + entry.getKey().getLogSeqNum()); + return Integer.parseInt(Bytes.toString(put.getRow())); + } + + WAL.Reader createWALReaderForPrimary() throws FileNotFoundException, IOException { + return wals.createReader(TEST_UTIL.getTestFileSystem(), + DefaultWALProvider.getCurrentFileName(walPrimary), + TEST_UTIL.getConfiguration()); + } + + @Test + public void testReplayFlushesAndCompactions() throws IOException { + // initiate a secondary region with some data. + + // load some data to primary and flush. 3 flushes and some more unflushed data + putDataWithFlushes(primaryRegion, 100, 300, 100); + + // compaction from primary + LOG.info("-- Compacting primary, only 1 store"); + primaryRegion.compactStore(Bytes.toBytes("cf1"), + NoLimitCompactionThroughputController.INSTANCE); + + // now replay the edits and the flush marker + reader = createWALReaderForPrimary(); + + LOG.info("-- Replaying edits and flush events in secondary"); + int lastReplayed = 0; + int expectedStoreFileCount = 0; + while (true) { + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + CompactionDescriptor compactionDesc + = WALEdit.getCompaction(entry.getEdit().getCells().get(0)); + if (flushDesc != null) { + // first verify that everything is replayed and visible before flush event replay + verifyData(secondaryRegion, 0, lastReplayed, cq, families); + Store store = secondaryRegion.getStore(Bytes.toBytes("cf1")); + long storeMemstoreSize = store.getMemStoreSize(); + long regionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + long storeFlushableSize = store.getFlushableSize(); + if (flushDesc.getAction() == FlushAction.START_FLUSH) { + LOG.info("-- Replaying flush start in secondary"); + PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(flushDesc); + assertNull(result.result); + assertEquals(result.flushOpSeqId, flushDesc.getFlushSequenceNumber()); + + // assert that the store memstore is smaller now + long newStoreMemstoreSize = store.getMemStoreSize(); + LOG.info("Memstore size reduced by:" + + StringUtils.humanReadableInt(newStoreMemstoreSize - storeMemstoreSize)); + assertTrue(storeMemstoreSize > newStoreMemstoreSize); + + } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) { + LOG.info("-- Replaying flush commit in secondary"); + secondaryRegion.replayWALFlushCommitMarker(flushDesc); + + // assert that the flush files are picked + expectedStoreFileCount++; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + long newFlushableSize = store.getFlushableSize(); + assertTrue(storeFlushableSize > newFlushableSize); + + // assert that the region memstore is smaller now + long newRegionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + assertTrue(regionMemstoreSize > newRegionMemstoreSize); + } + // after replay verify that everything is still visible + verifyData(secondaryRegion, 0, lastReplayed+1, cq, families); + } else if (compactionDesc != null) { + secondaryRegion.replayWALCompactionMarker(compactionDesc, true, false, Long.MAX_VALUE); + + // assert that the compaction is applied + for (Store store : secondaryRegion.getStores().values()) { + if (store.getColumnFamilyName().equals("cf1")) { + assertEquals(1, store.getStorefilesCount()); + } else { + assertEquals(expectedStoreFileCount, store.getStorefilesCount()); + } + } + } else { + lastReplayed = replayEdit(secondaryRegion, entry);; + } + } + + assertEquals(400-1, lastReplayed); + LOG.info("-- Verifying edits from secondary"); + verifyData(secondaryRegion, 0, 400, cq, families); + + LOG.info("-- Verifying edits from primary. Ensuring that files are not deleted"); + verifyData(primaryRegion, 0, lastReplayed, cq, families); + for (Store store : primaryRegion.getStores().values()) { + if (store.getColumnFamilyName().equals("cf1")) { + assertEquals(1, store.getStorefilesCount()); + } else { + assertEquals(expectedStoreFileCount, store.getStorefilesCount()); + } + } + } + + /** + * Tests cases where we prepare a flush with some seqId and we receive other flush start markers + * equal to, greater or less than the previous flush start marker. + */ + @Test + public void testReplayFlushStartMarkers() throws IOException { + // load some data to primary and flush. 1 flush and some more unflushed data + putDataWithFlushes(primaryRegion, 100, 100, 100); + int numRows = 200; + + // now replay the edits and the flush marker + reader = createWALReaderForPrimary(); + + LOG.info("-- Replaying edits and flush events in secondary"); + + FlushDescriptor startFlushDesc = null; + + int lastReplayed = 0; + while (true) { + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + if (flushDesc != null) { + // first verify that everything is replayed and visible before flush event replay + Store store = secondaryRegion.getStore(Bytes.toBytes("cf1")); + long storeMemstoreSize = store.getMemStoreSize(); + long regionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + long storeFlushableSize = store.getFlushableSize(); + + if (flushDesc.getAction() == FlushAction.START_FLUSH) { + startFlushDesc = flushDesc; + LOG.info("-- Replaying flush start in secondary"); + PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(startFlushDesc); + assertNull(result.result); + assertEquals(result.flushOpSeqId, startFlushDesc.getFlushSequenceNumber()); + assertTrue(regionMemstoreSize > 0); + assertTrue(storeFlushableSize > 0); + + // assert that the store memstore is smaller now + long newStoreMemstoreSize = store.getMemStoreSize(); + LOG.info("Memstore size reduced by:" + + StringUtils.humanReadableInt(newStoreMemstoreSize - storeMemstoreSize)); + assertTrue(storeMemstoreSize > newStoreMemstoreSize); + verifyData(secondaryRegion, 0, lastReplayed+1, cq, families); + + } + // after replay verify that everything is still visible + verifyData(secondaryRegion, 0, lastReplayed+1, cq, families); + } else { + lastReplayed = replayEdit(secondaryRegion, entry); + } + } + + // at this point, there should be some data (rows 0-100) in memstore snapshot + // and some more data in memstores (rows 100-200) + + verifyData(secondaryRegion, 0, numRows, cq, families); + + // Test case 1: replay the same flush start marker again + LOG.info("-- Replaying same flush start in secondary again"); + PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(startFlushDesc); + assertNull(result); // this should return null. Ignoring the flush start marker + // assert that we still have prepared flush with the previous setup. + assertNotNull(secondaryRegion.getPrepareFlushResult()); + assertEquals(secondaryRegion.getPrepareFlushResult().flushOpSeqId, + startFlushDesc.getFlushSequenceNumber()); + assertTrue(secondaryRegion.getMemstoreSize().get() > 0); // memstore is not empty + verifyData(secondaryRegion, 0, numRows, cq, families); + + // Test case 2: replay a flush start marker with a smaller seqId + FlushDescriptor startFlushDescSmallerSeqId + = clone(startFlushDesc, startFlushDesc.getFlushSequenceNumber() - 50); + LOG.info("-- Replaying same flush start in secondary again " + startFlushDescSmallerSeqId); + result = secondaryRegion.replayWALFlushStartMarker(startFlushDescSmallerSeqId); + assertNull(result); // this should return null. Ignoring the flush start marker + // assert that we still have prepared flush with the previous setup. + assertNotNull(secondaryRegion.getPrepareFlushResult()); + assertEquals(secondaryRegion.getPrepareFlushResult().flushOpSeqId, + startFlushDesc.getFlushSequenceNumber()); + assertTrue(secondaryRegion.getMemstoreSize().get() > 0); // memstore is not empty + verifyData(secondaryRegion, 0, numRows, cq, families); + + // Test case 3: replay a flush start marker with a larger seqId + FlushDescriptor startFlushDescLargerSeqId + = clone(startFlushDesc, startFlushDesc.getFlushSequenceNumber() + 50); + LOG.info("-- Replaying same flush start in secondary again " + startFlushDescLargerSeqId); + result = secondaryRegion.replayWALFlushStartMarker(startFlushDescLargerSeqId); + assertNull(result); // this should return null. Ignoring the flush start marker + // assert that we still have prepared flush with the previous setup. + assertNotNull(secondaryRegion.getPrepareFlushResult()); + assertEquals(secondaryRegion.getPrepareFlushResult().flushOpSeqId, + startFlushDesc.getFlushSequenceNumber()); + assertTrue(secondaryRegion.getMemstoreSize().get() > 0); // memstore is not empty + verifyData(secondaryRegion, 0, numRows, cq, families); + + LOG.info("-- Verifying edits from secondary"); + verifyData(secondaryRegion, 0, numRows, cq, families); + + LOG.info("-- Verifying edits from primary."); + verifyData(primaryRegion, 0, numRows, cq, families); + } + + /** + * Tests the case where we prepare a flush with some seqId and we receive a flush commit marker + * less than the previous flush start marker. + */ + @Test + public void testReplayFlushCommitMarkerSmallerThanFlushStartMarker() throws IOException { + // load some data to primary and flush. 2 flushes and some more unflushed data + putDataWithFlushes(primaryRegion, 100, 200, 100); + int numRows = 300; + + // now replay the edits and the flush marker + reader = createWALReaderForPrimary(); + + LOG.info("-- Replaying edits and flush events in secondary"); + FlushDescriptor startFlushDesc = null; + FlushDescriptor commitFlushDesc = null; + + int lastReplayed = 0; + while (true) { + System.out.println(lastReplayed); + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + if (flushDesc != null) { + if (flushDesc.getAction() == FlushAction.START_FLUSH) { + // don't replay the first flush start marker, hold on to it, replay the second one + if (startFlushDesc == null) { + startFlushDesc = flushDesc; + } else { + LOG.info("-- Replaying flush start in secondary"); + startFlushDesc = flushDesc; + PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(startFlushDesc); + assertNull(result.result); + } + } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) { + // do not replay any flush commit yet + if (commitFlushDesc == null) { + commitFlushDesc = flushDesc; // hold on to the first flush commit marker + } + } + // after replay verify that everything is still visible + verifyData(secondaryRegion, 0, lastReplayed+1, cq, families); + } else { + lastReplayed = replayEdit(secondaryRegion, entry); + } + } + + // at this point, there should be some data (rows 0-200) in memstore snapshot + // and some more data in memstores (rows 200-300) + verifyData(secondaryRegion, 0, numRows, cq, families); + + // no store files in the region + int expectedStoreFileCount = 0; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + long regionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + + // Test case 1: replay the a flush commit marker smaller than what we have prepared + LOG.info("Testing replaying flush COMMIT " + commitFlushDesc + " on top of flush START" + + startFlushDesc); + assertTrue(commitFlushDesc.getFlushSequenceNumber() < startFlushDesc.getFlushSequenceNumber()); + + LOG.info("-- Replaying flush commit in secondary" + commitFlushDesc); + secondaryRegion.replayWALFlushCommitMarker(commitFlushDesc); + + // assert that the flush files are picked + expectedStoreFileCount++; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + Store store = secondaryRegion.getStore(Bytes.toBytes("cf1")); + long newFlushableSize = store.getFlushableSize(); + assertTrue(newFlushableSize > 0); // assert that the memstore is not dropped + + // assert that the region memstore is same as before + long newRegionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + assertEquals(regionMemstoreSize, newRegionMemstoreSize); + + assertNotNull(secondaryRegion.getPrepareFlushResult()); // not dropped + + LOG.info("-- Verifying edits from secondary"); + verifyData(secondaryRegion, 0, numRows, cq, families); + + LOG.info("-- Verifying edits from primary."); + verifyData(primaryRegion, 0, numRows, cq, families); + } + + /** + * Tests the case where we prepare a flush with some seqId and we receive a flush commit marker + * larger than the previous flush start marker. + */ + @Test + public void testReplayFlushCommitMarkerLargerThanFlushStartMarker() throws IOException { + // load some data to primary and flush. 1 flush and some more unflushed data + putDataWithFlushes(primaryRegion, 100, 100, 100); + int numRows = 200; + + // now replay the edits and the flush marker + reader = createWALReaderForPrimary(); + + LOG.info("-- Replaying edits and flush events in secondary"); + FlushDescriptor startFlushDesc = null; + FlushDescriptor commitFlushDesc = null; + + int lastReplayed = 0; + while (true) { + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + if (flushDesc != null) { + if (flushDesc.getAction() == FlushAction.START_FLUSH) { + if (startFlushDesc == null) { + LOG.info("-- Replaying flush start in secondary"); + startFlushDesc = flushDesc; + PrepareFlushResult result = secondaryRegion.replayWALFlushStartMarker(startFlushDesc); + assertNull(result.result); + } + } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) { + // do not replay any flush commit yet + // hold on to the flush commit marker but simulate a larger + // flush commit seqId + commitFlushDesc = + FlushDescriptor.newBuilder(flushDesc) + .setFlushSequenceNumber(flushDesc.getFlushSequenceNumber() + 50) + .build(); + } + // after replay verify that everything is still visible + verifyData(secondaryRegion, 0, lastReplayed+1, cq, families); + } else { + lastReplayed = replayEdit(secondaryRegion, entry); + } + } + + // at this point, there should be some data (rows 0-100) in memstore snapshot + // and some more data in memstores (rows 100-200) + verifyData(secondaryRegion, 0, numRows, cq, families); + + // no store files in the region + int expectedStoreFileCount = 0; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + long regionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + + // Test case 1: replay the a flush commit marker larger than what we have prepared + LOG.info("Testing replaying flush COMMIT " + commitFlushDesc + " on top of flush START" + + startFlushDesc); + assertTrue(commitFlushDesc.getFlushSequenceNumber() > startFlushDesc.getFlushSequenceNumber()); + + LOG.info("-- Replaying flush commit in secondary" + commitFlushDesc); + secondaryRegion.replayWALFlushCommitMarker(commitFlushDesc); + + // assert that the flush files are picked + expectedStoreFileCount++; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + Store store = secondaryRegion.getStore(Bytes.toBytes("cf1")); + long newFlushableSize = store.getFlushableSize(); + assertTrue(newFlushableSize > 0); // assert that the memstore is not dropped + + // assert that the region memstore is smaller than before, but not empty + long newRegionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + assertTrue(newRegionMemstoreSize > 0); + assertTrue(regionMemstoreSize > newRegionMemstoreSize); + + assertNull(secondaryRegion.getPrepareFlushResult()); // prepare snapshot should be dropped + + LOG.info("-- Verifying edits from secondary"); + verifyData(secondaryRegion, 0, numRows, cq, families); + + LOG.info("-- Verifying edits from primary."); + verifyData(primaryRegion, 0, numRows, cq, families); + } + + /** + * Tests the case where we receive a flush commit before receiving any flush prepare markers. + * The memstore edits should be dropped after the flush commit replay since they should be in + * flushed files + */ + @Test + public void testReplayFlushCommitMarkerWithoutFlushStartMarkerDroppableMemstore() + throws IOException { + testReplayFlushCommitMarkerWithoutFlushStartMarker(true); + } + + /** + * Tests the case where we receive a flush commit before receiving any flush prepare markers. + * The memstore edits should be not dropped after the flush commit replay since not every edit + * will be in flushed files (based on seqId) + */ + @Test + public void testReplayFlushCommitMarkerWithoutFlushStartMarkerNonDroppableMemstore() + throws IOException { + testReplayFlushCommitMarkerWithoutFlushStartMarker(false); + } + + /** + * Tests the case where we receive a flush commit before receiving any flush prepare markers + */ + public void testReplayFlushCommitMarkerWithoutFlushStartMarker(boolean droppableMemstore) + throws IOException { + // load some data to primary and flush. 1 flushes and some more unflushed data. + // write more data after flush depending on whether droppableSnapshot + putDataWithFlushes(primaryRegion, 100, 100, droppableMemstore ? 0 : 100); + int numRows = droppableMemstore ? 100 : 200; + + // now replay the edits and the flush marker + reader = createWALReaderForPrimary(); + + LOG.info("-- Replaying edits and flush events in secondary"); + FlushDescriptor commitFlushDesc = null; + + int lastReplayed = 0; + while (true) { + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + if (flushDesc != null) { + if (flushDesc.getAction() == FlushAction.START_FLUSH) { + // do not replay flush start marker + } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) { + commitFlushDesc = flushDesc; // hold on to the flush commit marker + } + // after replay verify that everything is still visible + verifyData(secondaryRegion, 0, lastReplayed+1, cq, families); + } else { + lastReplayed = replayEdit(secondaryRegion, entry); + } + } + + // at this point, there should be some data (rows 0-200) in the memstore without snapshot + // and some more data in memstores (rows 100-300) + verifyData(secondaryRegion, 0, numRows, cq, families); + + // no store files in the region + int expectedStoreFileCount = 0; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + long regionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + + // Test case 1: replay a flush commit marker without start flush marker + assertNull(secondaryRegion.getPrepareFlushResult()); + assertTrue(commitFlushDesc.getFlushSequenceNumber() > 0); + + // ensure all files are visible in secondary + for (Store store : secondaryRegion.getStores().values()) { + assertTrue(store.getMaxSequenceId() <= secondaryRegion.getSequenceId().get()); + } + + LOG.info("-- Replaying flush commit in secondary" + commitFlushDesc); + secondaryRegion.replayWALFlushCommitMarker(commitFlushDesc); + + // assert that the flush files are picked + expectedStoreFileCount++; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + Store store = secondaryRegion.getStore(Bytes.toBytes("cf1")); + long newFlushableSize = store.getFlushableSize(); + if (droppableMemstore) { + assertTrue(newFlushableSize == 0); // assert that the memstore is dropped + } else { + assertTrue(newFlushableSize > 0); // assert that the memstore is not dropped + } + + // assert that the region memstore is same as before (we could not drop) + long newRegionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + if (droppableMemstore) { + assertTrue(0 == newRegionMemstoreSize); + } else { + assertTrue(regionMemstoreSize == newRegionMemstoreSize); + } + + LOG.info("-- Verifying edits from secondary"); + verifyData(secondaryRegion, 0, numRows, cq, families); + + LOG.info("-- Verifying edits from primary."); + verifyData(primaryRegion, 0, numRows, cq, families); + } + + private FlushDescriptor clone(FlushDescriptor flush, long flushSeqId) { + return FlushDescriptor.newBuilder(flush) + .setFlushSequenceNumber(flushSeqId) + .build(); + } + + /** + * Tests replaying region open markers from primary region. Checks whether the files are picked up + */ + @Test + public void testReplayRegionOpenEvent() throws IOException { + putDataWithFlushes(primaryRegion, 100, 0, 100); // no flush + int numRows = 100; + + // close the region and open again. + primaryRegion.close(); + primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null); + + // now replay the edits and the flush marker + reader = createWALReaderForPrimary(); + List regionEvents = Lists.newArrayList(); + + LOG.info("-- Replaying edits and region events in secondary"); + while (true) { + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + RegionEventDescriptor regionEventDesc + = WALEdit.getRegionEventDescriptor(entry.getEdit().getCells().get(0)); + + if (flushDesc != null) { + // don't replay flush events + } else if (regionEventDesc != null) { + regionEvents.add(regionEventDesc); + } else { + // don't replay edits + } + } + + // we should have 1 open, 1 close and 1 open event + assertEquals(3, regionEvents.size()); + + // replay the first region open event. + secondaryRegion.replayWALRegionEventMarker(regionEvents.get(0)); + + // replay the close event as well + secondaryRegion.replayWALRegionEventMarker(regionEvents.get(1)); + + // no store files in the region + int expectedStoreFileCount = 0; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + long regionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + assertTrue(regionMemstoreSize == 0); + + // now replay the region open event that should contain new file locations + LOG.info("Testing replaying region open event " + regionEvents.get(2)); + secondaryRegion.replayWALRegionEventMarker(regionEvents.get(2)); + + // assert that the flush files are picked + expectedStoreFileCount++; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + Store store = secondaryRegion.getStore(Bytes.toBytes("cf1")); + long newFlushableSize = store.getFlushableSize(); + assertTrue(newFlushableSize == 0); + + // assert that the region memstore is empty + long newRegionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + assertTrue(newRegionMemstoreSize == 0); + + assertNull(secondaryRegion.getPrepareFlushResult()); //prepare snapshot should be dropped if any + + LOG.info("-- Verifying edits from secondary"); + verifyData(secondaryRegion, 0, numRows, cq, families); + + LOG.info("-- Verifying edits from primary."); + verifyData(primaryRegion, 0, numRows, cq, families); + } + + /** + * Tests the case where we replay a region open event after a flush start but before receiving + * flush commit + */ + @Test + public void testReplayRegionOpenEventAfterFlushStart() throws IOException { + putDataWithFlushes(primaryRegion, 100, 100, 100); + int numRows = 200; + + // close the region and open again. + primaryRegion.close(); + primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null); + + // now replay the edits and the flush marker + reader = createWALReaderForPrimary(); + List regionEvents = Lists.newArrayList(); + + LOG.info("-- Replaying edits and region events in secondary"); + while (true) { + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + RegionEventDescriptor regionEventDesc + = WALEdit.getRegionEventDescriptor(entry.getEdit().getCells().get(0)); + + if (flushDesc != null) { + // only replay flush start + if (flushDesc.getAction() == FlushAction.START_FLUSH) { + secondaryRegion.replayWALFlushStartMarker(flushDesc); + } + } else if (regionEventDesc != null) { + regionEvents.add(regionEventDesc); + } else { + replayEdit(secondaryRegion, entry); + } + } + + // at this point, there should be some data (rows 0-100) in the memstore snapshot + // and some more data in memstores (rows 100-200) + verifyData(secondaryRegion, 0, numRows, cq, families); + + // we should have 1 open, 1 close and 1 open event + assertEquals(3, regionEvents.size()); + + // no store files in the region + int expectedStoreFileCount = 0; + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + + // now replay the region open event that should contain new file locations + LOG.info("Testing replaying region open event " + regionEvents.get(2)); + secondaryRegion.replayWALRegionEventMarker(regionEvents.get(2)); + + // assert that the flush files are picked + expectedStoreFileCount = 2; // two flushes happened + for (Store s : secondaryRegion.getStores().values()) { + assertEquals(expectedStoreFileCount, s.getStorefilesCount()); + } + Store store = secondaryRegion.getStore(Bytes.toBytes("cf1")); + long newSnapshotSize = store.getSnapshotSize(); + assertTrue(newSnapshotSize == 0); + + // assert that the region memstore is empty + long newRegionMemstoreSize = secondaryRegion.getMemstoreSize().get(); + assertTrue(newRegionMemstoreSize == 0); + + assertNull(secondaryRegion.getPrepareFlushResult()); //prepare snapshot should be dropped if any + + LOG.info("-- Verifying edits from secondary"); + verifyData(secondaryRegion, 0, numRows, cq, families); + + LOG.info("-- Verifying edits from primary."); + verifyData(primaryRegion, 0, numRows, cq, families); + } + + /** + * Tests whether edits coming in for replay are skipped which have smaller seq id than the seqId + * of the last replayed region open event. + */ + @Test + public void testSkippingEditsWithSmallerSeqIdAfterRegionOpenEvent() throws IOException { + putDataWithFlushes(primaryRegion, 100, 100, 0); + int numRows = 100; + + // close the region and open again. + primaryRegion.close(); + primaryRegion = HRegion.openHRegion(rootDir, primaryHri, htd, walPrimary, CONF, rss, null); + + // now replay the edits and the flush marker + reader = createWALReaderForPrimary(); + List regionEvents = Lists.newArrayList(); + List edits = Lists.newArrayList(); + + LOG.info("-- Replaying edits and region events in secondary"); + while (true) { + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + RegionEventDescriptor regionEventDesc + = WALEdit.getRegionEventDescriptor(entry.getEdit().getCells().get(0)); + + if (flushDesc != null) { + // don't replay flushes + } else if (regionEventDesc != null) { + regionEvents.add(regionEventDesc); + } else { + edits.add(entry); + } + } + + // replay the region open of first open, but with the seqid of the second open + // this way non of the flush files will be picked up. + secondaryRegion.replayWALRegionEventMarker( + RegionEventDescriptor.newBuilder(regionEvents.get(0)).setLogSequenceNumber( + regionEvents.get(2).getLogSequenceNumber()).build()); + + + // replay edits from the before region close. If replay does not + // skip these the following verification will NOT fail. + for (WAL.Entry entry: edits) { + replayEdit(secondaryRegion, entry); + } + + boolean expectedFail = false; + try { + verifyData(secondaryRegion, 0, numRows, cq, families); + } catch (AssertionError e) { + expectedFail = true; // expected + } + if (!expectedFail) { + fail("Should have failed this verification"); + } + } + + @Test + public void testReplayFlushSeqIds() throws IOException { + // load some data to primary and flush + int start = 0; + LOG.info("-- Writing some data to primary from " + start + " to " + (start+100)); + putData(primaryRegion, Durability.SYNC_WAL, start, 100, cq, families); + LOG.info("-- Flushing primary, creating 3 files for 3 stores"); + primaryRegion.flushcache(); + + // now replay the flush marker + reader = createWALReaderForPrimary(); + + long flushSeqId = -1; + LOG.info("-- Replaying flush events in secondary"); + while (true) { + WAL.Entry entry = reader.next(); + if (entry == null) { + break; + } + FlushDescriptor flushDesc + = WALEdit.getFlushDescriptor(entry.getEdit().getCells().get(0)); + if (flushDesc != null) { + if (flushDesc.getAction() == FlushAction.START_FLUSH) { + LOG.info("-- Replaying flush start in secondary"); + secondaryRegion.replayWALFlushStartMarker(flushDesc); + flushSeqId = flushDesc.getFlushSequenceNumber(); + } else if (flushDesc.getAction() == FlushAction.COMMIT_FLUSH) { + LOG.info("-- Replaying flush commit in secondary"); + secondaryRegion.replayWALFlushCommitMarker(flushDesc); + assertEquals(flushSeqId, flushDesc.getFlushSequenceNumber()); + } + } + // else do not replay + } + + // TODO: what to do with this? + // assert that the newly picked up flush file is visible + long readPoint = secondaryRegion.getMVCC().memstoreReadPoint(); + assertEquals(flushSeqId, readPoint); + + // after replay verify that everything is still visible + verifyData(secondaryRegion, 0, 100, cq, families); + } + + @Test + public void testSeqIdsFromReplay() throws IOException { + // test the case where seqId's coming from replayed WALEdits are made persisted with their + // original seqIds and they are made visible through mvcc read point upon replay + String method = name.getMethodName(); + byte[] tableName = Bytes.toBytes(method); + byte[] family = Bytes.toBytes("family"); + + HRegion region = initHRegion(tableName, method, family); + try { + // replay an entry that is bigger than current read point + long readPoint = region.getMVCC().memstoreReadPoint(); + long origSeqId = readPoint + 100; + + Put put = new Put(row).add(family, row, row); + put.setDurability(Durability.SKIP_WAL); // we replay with skip wal + replay(region, put, origSeqId); + + // read point should have advanced to this seqId + assertGet(region, family, row); + + // region seqId should have advanced at least to this seqId + assertEquals(origSeqId, region.getSequenceId().get()); + + // replay an entry that is smaller than current read point + // caution: adding an entry below current read point might cause partial dirty reads. Normal + // replay does not allow reads while replay is going on. + put = new Put(row2).add(family, row2, row2); + put.setDurability(Durability.SKIP_WAL); + replay(region, put, origSeqId - 50); + + assertGet(region, family, row2); + } finally { + region.close(); + } + } + + /** + * Tests that a region opened in secondary mode would not write region open / close + * events to its WAL. + * @throws IOException + */ + @SuppressWarnings("unchecked") + @Test + public void testSecondaryRegionDoesNotWriteRegionEventsToWAL() throws IOException { + secondaryRegion.close(); + walSecondary = spy(walSecondary); + + // test for region open and close + secondaryRegion = HRegion.openHRegion(secondaryHri, htd, walSecondary, CONF, rss, null); + verify(walSecondary, times(0)).append((HTableDescriptor)any(), (HRegionInfo)any(), + (WALKey)any(), (WALEdit)any(), (AtomicLong)any(), anyBoolean(), (List) any()); + + // test for replay prepare flush + putDataByReplay(secondaryRegion, 0, 10, cq, families); + secondaryRegion.replayWALFlushStartMarker(FlushDescriptor.newBuilder(). + setFlushSequenceNumber(10) + .setTableName(ByteString.copyFrom(primaryRegion.getTableDesc().getTableName().getName())) + .setAction(FlushAction.START_FLUSH) + .setEncodedRegionName( + ByteString.copyFrom(primaryRegion.getRegionInfo().getEncodedNameAsBytes())) + .setRegionName(ByteString.copyFrom(primaryRegion.getRegionName())) + .build()); + + verify(walSecondary, times(0)).append((HTableDescriptor)any(), (HRegionInfo)any(), + (WALKey)any(), (WALEdit)any(), (AtomicLong)any(), anyBoolean(), (List) any()); + + secondaryRegion.close(); + verify(walSecondary, times(0)).append((HTableDescriptor)any(), (HRegionInfo)any(), + (WALKey)any(), (WALEdit)any(), (AtomicLong)any(), anyBoolean(), (List) any()); + } + + private void replay(HRegion region, Put put, long replaySeqId) throws IOException { + put.setDurability(Durability.SKIP_WAL); + MutationReplay mutation = new MutationReplay(MutationType.PUT, put, 0, 0); + region.batchReplay(new MutationReplay[] {mutation}, replaySeqId); + } + + /** Puts a total of numRows + numRowsAfterFlush records indexed with numeric row keys. Does + * a flush every flushInterval number of records. Then it puts numRowsAfterFlush number of + * more rows but does not execute flush after + * @throws IOException */ + private void putDataWithFlushes(HRegion region, int flushInterval, + int numRows, int numRowsAfterFlush) throws IOException { + int start = 0; + for (; start < numRows; start += flushInterval) { + LOG.info("-- Writing some data to primary from " + start + " to " + (start+flushInterval)); + putData(region, Durability.SYNC_WAL, start, flushInterval, cq, families); + LOG.info("-- Flushing primary, creating 3 files for 3 stores"); + region.flushcache(); + } + LOG.info("-- Writing some more data to primary, not flushing"); + putData(region, Durability.SYNC_WAL, start, numRowsAfterFlush, cq, families); + } + + private void putDataByReplay(HRegion region, + int startRow, int numRows, byte[] qf, byte[]... families) throws IOException { + for (int i = startRow; i < startRow + numRows; i++) { + Put put = new Put(Bytes.toBytes("" + i)); + put.setDurability(Durability.SKIP_WAL); + for (byte[] family : families) { + put.add(family, qf, EnvironmentEdgeManager.currentTime(), null); + } + replay(region, put, i+1); + } + } + + private static HRegion initHRegion(byte[] tableName, + String callingMethod, byte[]... families) throws IOException { + return initHRegion(tableName, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW, + callingMethod, TEST_UTIL.getConfiguration(), false, Durability.SYNC_WAL, null, families); + } + + private static HRegion initHRegion(byte[] tableName, byte[] startKey, byte[] stopKey, + String callingMethod, Configuration conf, boolean isReadOnly, Durability durability, + WAL wal, byte[]... families) throws IOException { + return TEST_UTIL.createLocalHRegion(tableName, startKey, stopKey, callingMethod, conf, + isReadOnly, durability, wal, families); + } +} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestPerColumnFamilyFlush.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestPerColumnFamilyFlush.java index e3f51ea53ed..bda95dbff4a 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestPerColumnFamilyFlush.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestPerColumnFamilyFlush.java @@ -327,8 +327,7 @@ public class TestPerColumnFamilyFlush { return null; } - @Test (timeout=180000) - public void testLogReplay() throws Exception { + public void doTestLogReplay() throws Exception { Configuration conf = TEST_UTIL.getConfiguration(); conf.setLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 20000); // Carefully chosen limits so that the memstore just flushes when we're done @@ -418,7 +417,14 @@ public class TestPerColumnFamilyFlush { @Test (timeout=180000) public void testLogReplayWithDistributedReplay() throws Exception { TEST_UTIL.getConfiguration().setBoolean(HConstants.DISTRIBUTED_LOG_REPLAY_KEY, true); - testLogReplay(); + doTestLogReplay(); + } + + // Test Log Replay with Distributed log split on. + @Test (timeout=180000) + public void testLogReplayWithDistributedLogSplit() throws Exception { + TEST_UTIL.getConfiguration().setBoolean(HConstants.DISTRIBUTED_LOG_REPLAY_KEY, false); + doTestLogReplay(); } /** From a98898cf417de71c7250acd0a1c107101c3bb487 Mon Sep 17 00:00:00 2001 From: Matteo Bertozzi Date: Fri, 13 Feb 2015 17:51:50 +0100 Subject: [PATCH 013/329] HBASE-13037 LoadIncrementalHFile should try to verify the content of unmatched families --- .../apache/hadoop/hbase/io/hfile/HFile.java | 41 ++++++++++++++ .../mapreduce/LoadIncrementalHFiles.java | 20 +++++-- .../mapreduce/TestLoadIncrementalHFiles.java | 56 +++++++++++++++++++ 3 files changed, 111 insertions(+), 6 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFile.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFile.java index ad62d7186f3..610fe7fc60b 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFile.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFile.java @@ -532,6 +532,47 @@ public class HFile { return pickReaderVersion(path, wrapper, size, cacheConf, null, conf); } + /** + * Returns true if the specified file has a valid HFile Trailer. + * @param fs filesystem + * @param path Path to file to verify + * @return true if the file has a valid HFile Trailer, otherwise false + * @throws IOException if failed to read from the underlying stream + */ + public static boolean isHFileFormat(final FileSystem fs, final Path path) throws IOException { + return isHFileFormat(fs, fs.getFileStatus(path)); + } + + /** + * Returns true if the specified file has a valid HFile Trailer. + * @param fs filesystem + * @param fileStatus the file to verify + * @return true if the file has a valid HFile Trailer, otherwise false + * @throws IOException if failed to read from the underlying stream + */ + public static boolean isHFileFormat(final FileSystem fs, final FileStatus fileStatus) + throws IOException { + final Path path = fileStatus.getPath(); + final long size = fileStatus.getLen(); + FSDataInputStreamWrapper fsdis = new FSDataInputStreamWrapper(fs, path); + try { + boolean isHBaseChecksum = fsdis.shouldUseHBaseChecksum(); + assert !isHBaseChecksum; // Initially we must read with FS checksum. + FixedFileTrailer.readFromStream(fsdis.getStream(isHBaseChecksum), size); + return true; + } catch (IllegalArgumentException e) { + return false; + } catch (IOException e) { + throw e; + } finally { + try { + fsdis.close(); + } catch (Throwable t) { + LOG.warn("Error closing fsdis FSDataInputStreamWrapper: " + path, t); + } + } + } + /** * Metadata for this file. Conjured by the writer. Read in by the reader. */ diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/LoadIncrementalHFiles.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/LoadIncrementalHFiles.java index 45f57b3b4fb..0bed8181433 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/LoadIncrementalHFiles.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/LoadIncrementalHFiles.java @@ -89,6 +89,7 @@ import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -246,7 +247,7 @@ public class LoadIncrementalHFiles extends Configured implements Tool { { doBulkLoad(hfofDir, table.getConnection().getAdmin(), table, table.getRegionLocator()); } - + /** * Perform a bulk load of the given directory into the given * pre-existing table. This method is not threadsafe. @@ -282,15 +283,22 @@ public class LoadIncrementalHFiles extends Configured implements Tool { discoverLoadQueue(queue, hfofDir); // check whether there is invalid family name in HFiles to be bulkloaded Collection families = table.getTableDescriptor().getFamilies(); - ArrayList familyNames = new ArrayList(); + ArrayList familyNames = new ArrayList(families.size()); for (HColumnDescriptor family : families) { familyNames.add(family.getNameAsString()); } ArrayList unmatchedFamilies = new ArrayList(); - for (LoadQueueItem lqi : queue) { + Iterator queueIter = queue.iterator(); + while (queueIter.hasNext()) { + LoadQueueItem lqi = queueIter.next(); String familyNameInHFile = Bytes.toString(lqi.family); if (!familyNames.contains(familyNameInHFile)) { - unmatchedFamilies.add(familyNameInHFile); + if (HFile.isHFileFormat(lqi.hfilePath.getFileSystem(getConf()), lqi.hfilePath)) { + unmatchedFamilies.add(familyNameInHFile); + } else { + LOG.warn("the file " + lqi + " doesn't seems to be an hfile. skipping"); + queueIter.remove(); + } } } if (unmatchedFamilies.size() > 0) { @@ -729,7 +737,7 @@ public class LoadIncrementalHFiles extends Configured implements Tool { throw e; } } - + private boolean isSecureBulkLoadEndpointAvailable() { String classes = getConf().get(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, ""); return classes.contains("org.apache.hadoop.hbase.security.access.SecureBulkLoadEndpoint"); @@ -945,7 +953,7 @@ public class LoadIncrementalHFiles extends Configured implements Tool { HTable table = (HTable) connection.getTable(tableName);) { doBulkLoad(hfofDir, table); } - + return 0; } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestLoadIncrementalHFiles.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestLoadIncrementalHFiles.java index 813f374d51d..0a90fd528f2 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestLoadIncrementalHFiles.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestLoadIncrementalHFiles.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.util.TreeMap; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -306,6 +307,61 @@ public class TestLoadIncrementalHFiles { } } + /** + * Write a random data file in a dir with a valid family name but not part of the table families + * we should we able to bulkload without getting the unmatched family exception. HBASE-13037 + */ + @Test(timeout = 60000) + public void testNonHfileFolderWithUnmatchedFamilyName() throws Exception { + Path dir = util.getDataTestDirOnTestFS("testNonHfileFolderWithUnmatchedFamilyName"); + FileSystem fs = util.getTestFileSystem(); + dir = dir.makeQualified(fs); + + Path familyDir = new Path(dir, Bytes.toString(FAMILY)); + HFileTestUtil.createHFile(util.getConfiguration(), fs, new Path(familyDir, "hfile_0"), + FAMILY, QUALIFIER, Bytes.toBytes("begin"), Bytes.toBytes("end"), 500); + + final String NON_FAMILY_FOLDER = "_logs"; + Path nonFamilyDir = new Path(dir, NON_FAMILY_FOLDER); + fs.mkdirs(nonFamilyDir); + createRandomDataFile(fs, new Path(nonFamilyDir, "012356789"), 16 * 1024); + + Table table = null; + try { + final String TABLE_NAME = "mytable_testNonHfileFolderWithUnmatchedFamilyName"; + table = util.createTable(TableName.valueOf(TABLE_NAME), FAMILY); + + final String[] args = {dir.toString(), TABLE_NAME}; + new LoadIncrementalHFiles(util.getConfiguration()).run(args); + assertEquals(500, util.countRows(table)); + } finally { + if (table != null) { + table.close(); + } + fs.delete(dir, true); + } + } + + private static void createRandomDataFile(FileSystem fs, Path path, int size) + throws IOException { + FSDataOutputStream stream = fs.create(path); + try { + byte[] data = new byte[1024]; + for (int i = 0; i < data.length; ++i) { + data[i] = (byte)(i & 0xff); + } + while (size >= data.length) { + stream.write(data, 0, data.length); + size -= data.length; + } + if (size > 0) { + stream.write(data, 0, size); + } + } finally { + stream.close(); + } + } + @Test(timeout = 60000) public void testSplitStoreFile() throws IOException { Path dir = util.getDataTestDirOnTestFS("testSplitHFile"); From 76a3b50f1fa66b9d6828e636387e35dedcdd70a2 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Fri, 13 Feb 2015 12:35:26 -0800 Subject: [PATCH 014/329] HBASE-13039 Add patchprocess/* to .gitignore to fix builds of branches ( Adrey Stepachev) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9e087dab479..f9fc9f70931 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ hbase-*/test *.iws *.iml *.ipr +patchprocess/ From 3babad30e6d1953115f432532f85a143eaf431ad Mon Sep 17 00:00:00 2001 From: stack Date: Fri, 13 Feb 2015 15:24:22 -0800 Subject: [PATCH 015/329] HBASE-13030 [1.0.0 polish] Make ScanMetrics public again and align Put 'add' with Get, Delete, etc., addColumn --- .../hbase/client/AbstractClientScanner.java | 15 +++--- .../hadoop/hbase/client/ClientScanner.java | 6 +-- .../org/apache/hadoop/hbase/client/Put.java | 46 ++++++++++++++++++- .../apache/hadoop/hbase/client/Result.java | 9 +++- .../org/apache/hadoop/hbase/client/Scan.java | 42 ++++++++++++++++- .../hbase/client/metrics/ScanMetrics.java | 11 ++--- .../org/apache/hadoop/hbase/CellUtil.java | 4 ++ .../hbase/rest/client/RemoteHTable.java | 23 +++++++++- .../mapreduce/TableRecordReaderImpl.java | 11 ++--- .../hbase/regionserver/RSRpcServices.java | 1 - .../hbase/ScanPerformanceEvaluation.java | 7 +-- .../hbase/client/TestFromClientSide.java | 39 ++++++++-------- .../TestRegionObserverInterface.java | 29 ++++++------ 13 files changed, 172 insertions(+), 71 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AbstractClientScanner.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AbstractClientScanner.java index 54c97d7ed95..dc325a3cfe1 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AbstractClientScanner.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AbstractClientScanner.java @@ -23,14 +23,12 @@ import java.util.Iterator; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.metrics.ScanMetrics; -import org.apache.hadoop.hbase.util.Bytes; /** * Helper class for custom client scanners. */ @InterfaceAudience.Private public abstract class AbstractClientScanner implements ResultScanner { - protected ScanMetrics scanMetrics; /** @@ -38,14 +36,19 @@ public abstract class AbstractClientScanner implements ResultScanner { */ protected void initScanMetrics(Scan scan) { // check if application wants to collect scan metrics - byte[] enableMetrics = scan.getAttribute( - Scan.SCAN_ATTRIBUTES_METRICS_ENABLE); - if (enableMetrics != null && Bytes.toBoolean(enableMetrics)) { + if (scan.isScanMetricsEnabled()) { scanMetrics = new ScanMetrics(); } } - // TODO: should this be at ResultScanner? ScanMetrics is not public API it seems. + /** + * Used internally accumulating metrics on scan. To + * enable collection of metrics on a Scanner, call {@link Scan#setScanMetricsEnabled(boolean)}. + * These metrics are cleared at key transition points. Metrics are accumulated in the + * {@link Scan} object itself. + * @see Scan#getScanMetrics() + * @return Returns the running {@link ScanMetrics} instance or null if scan metrics not enabled. + */ public ScanMetrics getScanMetrics() { return scanMetrics; } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientScanner.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientScanner.java index d31642a88d4..110b0396c24 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientScanner.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ClientScanner.java @@ -317,9 +317,9 @@ public class ClientScanner extends AbstractClientScanner { * machine; for scan/map reduce scenarios, we will have multiple scans running at the same time. * * By default, scan metrics are disabled; if the application wants to collect them, this - * behavior can be turned on by calling calling: - * - * scan.setAttribute(SCAN_ATTRIBUTES_METRICS_ENABLE, Bytes.toBytes(Boolean.TRUE)) + * behavior can be turned on by calling calling {@link Scan#setScanMetricsEnabled(boolean)} + * + *

This invocation clears the scan metrics. Metrics are aggregated in the Scan instance. */ protected void writeScanMetrics() { if (this.scanMetrics == null || scanMetricsPublished) { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Put.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Put.java index b9d652d8a80..364783f9ed3 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Put.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Put.java @@ -137,9 +137,22 @@ public class Put extends Mutation implements HeapSize, Comparable { * @param qualifier column qualifier * @param value column value * @return this + * @deprecated Since 1.0.0. Use {@link #addColumn(byte[], byte[], byte[])} */ + @Deprecated public Put add(byte [] family, byte [] qualifier, byte [] value) { - return add(family, qualifier, this.ts, value); + return addColumn(family, qualifier, value); + } + + /** + * Add the specified column and value to this Put operation. + * @param family family name + * @param qualifier column qualifier + * @param value column value + * @return this + */ + public Put addColumn(byte [] family, byte [] qualifier, byte [] value) { + return addColumn(family, qualifier, this.ts, value); } /** @@ -167,8 +180,23 @@ public class Put extends Mutation implements HeapSize, Comparable { * @param ts version timestamp * @param value column value * @return this + * @deprecated Since 1.0.0. Use {@link #addColumn(byte[], byte[], long, byte[])} */ + @Deprecated public Put add(byte [] family, byte [] qualifier, long ts, byte [] value) { + return addColumn(family, qualifier, ts, value); + } + + /** + * Add the specified column and value, with the specified timestamp as + * its version to this Put operation. + * @param family family name + * @param qualifier column qualifier + * @param ts version timestamp + * @param value column value + * @return this + */ + public Put addColumn(byte [] family, byte [] qualifier, long ts, byte [] value) { if (ts < 0) { throw new IllegalArgumentException("Timestamp cannot be negative. ts=" + ts); } @@ -199,7 +227,6 @@ public class Put extends Mutation implements HeapSize, Comparable { * This expects that the underlying arrays won't change. It's intended * for usage internal HBase to and for advanced client applications. */ - @SuppressWarnings("unchecked") public Put addImmutable(byte[] family, byte[] qualifier, long ts, byte[] value, Tag[] tag) { List list = getCellList(family); KeyValue kv = createPutKeyValue(family, qualifier, ts, value, tag); @@ -233,8 +260,23 @@ public class Put extends Mutation implements HeapSize, Comparable { * @param ts version timestamp * @param value column value * @return this + * @deprecated Since 1.0.0. Use {@link Put#addColumn(byte[], ByteBuffer, long, ByteBuffer)} */ + @Deprecated public Put add(byte[] family, ByteBuffer qualifier, long ts, ByteBuffer value) { + return addColumn(family, qualifier, ts, value); + } + + /** + * Add the specified column and value, with the specified timestamp as + * its version to this Put operation. + * @param family family name + * @param qualifier column qualifier + * @param ts version timestamp + * @param value column value + * @return this + */ + public Put addColumn(byte[] family, ByteBuffer qualifier, long ts, ByteBuffer value) { if (ts < 0) { throw new IllegalArgumentException("Timestamp cannot be negative. ts=" + ts); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Result.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Result.java index faef0d3aa97..c418e47c2ab 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Result.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Result.java @@ -361,6 +361,9 @@ public class Result implements CellScannable, CellScanner { /** * Get the latest version of the specified column. + * Note: this call clones the value content of the hosting Cell. See + * {@link #getValueAsByteBuffer(byte[], byte[])}, etc., or {@link #listCells()} if you would + * avoid the cloning. * @param family family name * @param qualifier column qualifier * @return value of latest version of column, null if none found @@ -388,7 +391,8 @@ public class Result implements CellScannable, CellScanner { if (kv == null) { return null; } - return ByteBuffer.wrap(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength()); + return ByteBuffer.wrap(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength()). + asReadOnlyBuffer(); } /** @@ -411,7 +415,8 @@ public class Result implements CellScannable, CellScanner { if (kv == null) { return null; } - return ByteBuffer.wrap(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength()); + return ByteBuffer.wrap(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength()). + asReadOnlyBuffer(); } /** diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java index d2dd770c03a..bfcfa2085ec 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java @@ -34,9 +34,11 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; +import org.apache.hadoop.hbase.client.metrics.ScanMetrics; import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.filter.IncompatibleFilterException; import org.apache.hadoop.hbase.io.TimeRange; +import org.apache.hadoop.hbase.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.security.access.Permission; import org.apache.hadoop.hbase.security.visibility.Authorizations; import org.apache.hadoop.hbase.util.Bytes; @@ -117,9 +119,18 @@ public class Scan extends Query { private int storeOffset = 0; private boolean getScan; - // If application wants to collect scan metrics, it needs to - // call scan.setAttribute(SCAN_ATTRIBUTES_ENABLE, Bytes.toBytes(Boolean.TRUE)) + /** + * @deprecated since 1.0.0. Use {@link #setScanMetricsEnabled(boolean)} + */ + // Make private or remove. + @Deprecated static public final String SCAN_ATTRIBUTES_METRICS_ENABLE = "scan.attributes.metrics.enable"; + + /** + * Use {@link #getScanMetrics()} + */ + // Make this private or remove. + @Deprecated static public final String SCAN_ATTRIBUTES_METRICS_DATA = "scan.attributes.metrics.data"; // If an application wants to use multiple scans over different tables each scan must @@ -916,4 +927,31 @@ public class Scan extends Query { scan.setCaching(1); return scan; } + + /** + * Enable collection of {@link ScanMetrics}. For advanced users. + * @param enabled Set to true to enable accumulating scan metrics + */ + public Scan setScanMetricsEnabled(final boolean enabled) { + setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE, Bytes.toBytes(Boolean.valueOf(enabled))); + return this; + } + + /** + * @return True if collection of scan metrics is enabled. For advanced users. + */ + public boolean isScanMetricsEnabled() { + byte[] attr = getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE); + return attr == null ? false : Bytes.toBoolean(attr); + } + + /** + * @return Metrics on this Scan, if metrics were enabled. + * @see #setScanMetricsEnabled(boolean) + */ + public ScanMetrics getScanMetrics() { + byte [] bytes = getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_DATA); + if (bytes == null) return null; + return ProtobufUtil.toScanMetrics(bytes); + } } \ No newline at end of file diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/metrics/ScanMetrics.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/metrics/ScanMetrics.java index 86bc120d4a5..35c66678c93 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/metrics/ScanMetrics.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/metrics/ScanMetrics.java @@ -22,15 +22,14 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.classification.InterfaceStability; import com.google.common.collect.ImmutableMap; /** - * Provides client-side metrics related to scan operations + * Provides client-side metrics related to scan operations. * The data can be passed to mapreduce framework or other systems. * We use atomic longs so that one thread can increment, * while another atomically resets to zero after the values are reported @@ -40,12 +39,10 @@ import com.google.common.collect.ImmutableMap; * However, there is no need for this. So they are defined under scan operation * for now. */ -@InterfaceAudience.Private +@InterfaceAudience.Public +@InterfaceStability.Evolving public class ScanMetrics { - - private static final Log LOG = LogFactory.getLog(ScanMetrics.class); - /** * Hash to hold the String -> Atomic Long mappings. */ diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/CellUtil.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/CellUtil.java index fefe626d08e..7b68eeef965 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/CellUtil.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/CellUtil.java @@ -61,6 +61,10 @@ public final class CellUtil { cell.getQualifierLength()); } + public static ByteRange fillValueRange(Cell cell, ByteRange range) { + return range.set(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()); + } + public static ByteRange fillTagRange(Cell cell, ByteRange range) { return range.set(cell.getTagsArray(), cell.getTagsOffset(), cell.getTagsLength()); } diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java index eb5f50697d2..0300ea2c9c8 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/client/RemoteHTable.java @@ -250,10 +250,12 @@ public class RemoteHTable implements Table { return TableName.valueOf(name); } + @Override public Configuration getConfiguration() { return conf; } + @Override public HTableDescriptor getTableDescriptor() throws IOException { StringBuilder sb = new StringBuilder(); sb.append('/'); @@ -282,10 +284,12 @@ public class RemoteHTable implements Table { throw new IOException("schema request timed out"); } + @Override public void close() throws IOException { client.shutdown(); } + @Override public Result get(Get get) throws IOException { TimeRange range = get.getTimeRange(); String spec = buildRowSpec(get.getRow(), get.getFamilyMap(), @@ -304,6 +308,7 @@ public class RemoteHTable implements Table { } } + @Override public Result[] get(List gets) throws IOException { byte[][] rows = new byte[gets.size()][]; int maxVersions = 1; @@ -360,6 +365,7 @@ public class RemoteHTable implements Table { throw new IOException("get request timed out"); } + @Override public boolean exists(Get get) throws IOException { LOG.warn("exists() is really get(), just use get()"); Result result = get(get); @@ -370,6 +376,7 @@ public class RemoteHTable implements Table { * exists(List) is really a list of get() calls. Just use get(). * @param gets list of Get to test for the existence */ + @Override public boolean[] existsAll(List gets) throws IOException { LOG.warn("exists(List) is really list of get() calls, just use get()"); boolean[] results = new boolean[gets.size()]; @@ -389,6 +396,7 @@ public class RemoteHTable implements Table { return objectResults; } + @Override public void put(Put put) throws IOException { CellSetModel model = buildModelFromPut(put); StringBuilder sb = new StringBuilder(); @@ -417,6 +425,7 @@ public class RemoteHTable implements Table { throw new IOException("put request timed out"); } + @Override public void put(List puts) throws IOException { // this is a trick: The gateway accepts multiple rows in a cell set and // ignores the row specification in the URI @@ -472,6 +481,7 @@ public class RemoteHTable implements Table { throw new IOException("multiput request timed out"); } + @Override public void delete(Delete delete) throws IOException { String spec = buildRowSpec(delete.getRow(), delete.getFamilyCellMap(), delete.getTimeStamp(), delete.getTimeStamp(), 1); @@ -495,6 +505,7 @@ public class RemoteHTable implements Table { throw new IOException("delete request timed out"); } + @Override public void delete(List deletes) throws IOException { for (Delete delete: deletes) { delete(delete); @@ -632,19 +643,21 @@ public class RemoteHTable implements Table { LOG.warn(StringUtils.stringifyException(e)); } } - } + @Override public ResultScanner getScanner(Scan scan) throws IOException { return new Scanner(scan); } + @Override public ResultScanner getScanner(byte[] family) throws IOException { Scan scan = new Scan(); scan.addFamily(family); return new Scanner(scan); } + @Override public ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOException { Scan scan = new Scan(); @@ -660,6 +673,7 @@ public class RemoteHTable implements Table { throw new IOException("getRowOrBefore not supported"); } + @Override public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put) throws IOException { // column to check-the-value @@ -696,11 +710,13 @@ public class RemoteHTable implements Table { throw new IOException("checkAndPut request timed out"); } + @Override public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, CompareOp compareOp, byte[] value, Put put) throws IOException { throw new IOException("checkAndPut for non-equal comparison not implemented"); } + @Override public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value, Delete delete) throws IOException { Put put = new Put(row); @@ -737,24 +753,29 @@ public class RemoteHTable implements Table { throw new IOException("checkAndDelete request timed out"); } + @Override public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, CompareOp compareOp, byte[] value, Delete delete) throws IOException { throw new IOException("checkAndDelete for non-equal comparison not implemented"); } + @Override public Result increment(Increment increment) throws IOException { throw new IOException("Increment not supported"); } + @Override public Result append(Append append) throws IOException { throw new IOException("Append not supported"); } + @Override public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount) throws IOException { throw new IOException("incrementColumnValue not supported"); } + @Override public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount, Durability durability) throws IOException { throw new IOException("incrementColumnValue not supported"); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableRecordReaderImpl.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableRecordReaderImpl.java index 47f686924ca..06fa7125fa3 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableRecordReaderImpl.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableRecordReaderImpl.java @@ -33,7 +33,6 @@ import org.apache.hadoop.hbase.client.ScannerCallable; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.client.metrics.ScanMetrics; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; -import org.apache.hadoop.hbase.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.mapreduce.Counter; import org.apache.hadoop.mapreduce.InputSplit; @@ -80,8 +79,7 @@ public class TableRecordReaderImpl { public void restart(byte[] firstRow) throws IOException { currentScan = new Scan(scan); currentScan.setStartRow(firstRow); - currentScan.setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE, - Bytes.toBytes(Boolean.TRUE)); + currentScan.setScanMetricsEnabled(true); if (this.scanner != null) { if (logScannerActivity) { LOG.info("Closing the previously opened scanner object."); @@ -265,14 +263,11 @@ public class TableRecordReaderImpl { * @throws IOException */ private void updateCounters() throws IOException { - byte[] serializedMetrics = currentScan.getAttribute( - Scan.SCAN_ATTRIBUTES_METRICS_DATA); - if (serializedMetrics == null || serializedMetrics.length == 0 ) { + ScanMetrics scanMetrics = this.scan.getScanMetrics(); + if (scanMetrics == null) { return; } - ScanMetrics scanMetrics = ProtobufUtil.toScanMetrics(serializedMetrics); - updateCounters(scanMetrics, numRestarts, getCounter, context, numStale); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java index 3944ae8808a..62c33053b8d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java @@ -2073,7 +2073,6 @@ public class RSRpcServices implements HBaseRPCErrorHandler, if (!isLoadingCfsOnDemandSet) { scan.setLoadColumnFamiliesOnDemand(region.isLoadingCfsOnDemandDefault()); } - scan.getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE); region.prepareScanner(scan); if (region.getCoprocessorHost() != null) { scanner = region.getCoprocessorHost().preScannerOpen(scan); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/ScanPerformanceEvaluation.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/ScanPerformanceEvaluation.java index 0c331b76147..24e9590d9c7 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/ScanPerformanceEvaluation.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/ScanPerformanceEvaluation.java @@ -29,7 +29,6 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; -import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; @@ -39,9 +38,7 @@ import org.apache.hadoop.hbase.client.metrics.ScanMetrics; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; import org.apache.hadoop.hbase.mapreduce.TableMapper; -import org.apache.hadoop.hbase.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.util.AbstractHBaseTool; -import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.FSUtils; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapreduce.Counters; @@ -137,7 +134,7 @@ public class ScanPerformanceEvaluation extends AbstractHBaseTool { Scan scan = new Scan(); // default scan settings scan.setCacheBlocks(false); scan.setMaxVersions(1); - scan.setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE, Bytes.toBytes(Boolean.TRUE)); + scan.setScanMetricsEnabled(true); if (caching != null) { scan.setCaching(Integer.parseInt(caching)); } @@ -177,7 +174,7 @@ public class ScanPerformanceEvaluation extends AbstractHBaseTool { table.close(); connection.close(); - ScanMetrics metrics = ProtobufUtil.toScanMetrics(scan.getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_DATA)); + ScanMetrics metrics = scan.getScanMetrics(); long totalBytes = metrics.countOfBytesInResults.get(); double throughput = (double)totalBytes / scanTimer.elapsedTime(TimeUnit.SECONDS); double throughputRows = (double)numRows / scanTimer.elapsedTime(TimeUnit.SECONDS); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java index c77ab2978a8..49a6036ec0d 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java @@ -5003,37 +5003,39 @@ public class TestFromClientSide { Scan scan1 = new Scan(); int numRecords = 0; - for(Result result : ht.getScanner(scan1)) { + ResultScanner scanner = ht.getScanner(scan1); + for(Result result : scanner) { numRecords++; } + scanner.close(); LOG.info("test data has " + numRecords + " records."); // by default, scan metrics collection is turned off - assertEquals(null, scan1.getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_DATA)); + assertEquals(null, scan1.getScanMetrics()); // turn on scan metrics - Scan scan = new Scan(); - scan.setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE, Bytes.toBytes(Boolean.TRUE)); - scan.setCaching(numRecords+1); - ResultScanner scanner = ht.getScanner(scan); + Scan scan2 = new Scan(); + scan2.setScanMetricsEnabled(true); + scan2.setCaching(numRecords+1); + scanner = ht.getScanner(scan2); for (Result result : scanner.next(numRecords - 1)) { } scanner.close(); // closing the scanner will set the metrics. - assertNotNull(scan.getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_DATA)); + assertNotNull(scan2.getScanMetrics()); - // set caching to 1, becasue metrics are collected in each roundtrip only - scan = new Scan(); - scan.setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE, Bytes.toBytes(Boolean.TRUE)); - scan.setCaching(1); - scanner = ht.getScanner(scan); + // set caching to 1, because metrics are collected in each roundtrip only + scan2 = new Scan(); + scan2.setScanMetricsEnabled(true); + scan2.setCaching(1); + scanner = ht.getScanner(scan2); // per HBASE-5717, this should still collect even if you don't run all the way to // the end of the scanner. So this is asking for 2 of the 3 rows we inserted. for (Result result : scanner.next(numRecords - 1)) { } scanner.close(); - ScanMetrics scanMetrics = getScanMetrics(scan); + ScanMetrics scanMetrics = scan2.getScanMetrics(); assertEquals("Did not access all the regions in the table", numOfRegions, scanMetrics.countOfRegions.get()); @@ -5041,7 +5043,7 @@ public class TestFromClientSide { // run past the end of all the records Scan scanWithoutClose = new Scan(); scanWithoutClose.setCaching(1); - scanWithoutClose.setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE, Bytes.toBytes(Boolean.TRUE)); + scanWithoutClose.setScanMetricsEnabled(true); ResultScanner scannerWithoutClose = ht.getScanner(scanWithoutClose); for (Result result : scannerWithoutClose.next(numRecords + 1)) { } @@ -5054,7 +5056,7 @@ public class TestFromClientSide { Scan scanWithClose = new Scan(); // make sure we can set caching up to the number of a scanned values scanWithClose.setCaching(numRecords); - scanWithClose.setAttribute(Scan.SCAN_ATTRIBUTES_METRICS_ENABLE, Bytes.toBytes(Boolean.TRUE)); + scan2.setScanMetricsEnabled(true); ResultScanner scannerWithClose = ht.getScanner(scanWithClose); for (Result result : scannerWithClose.next(numRecords + 1)) { } @@ -5068,7 +5070,6 @@ public class TestFromClientSide { byte[] serializedMetrics = scan.getAttribute(Scan.SCAN_ATTRIBUTES_METRICS_DATA); assertTrue("Serialized metrics were not found.", serializedMetrics != null); - ScanMetrics scanMetrics = ProtobufUtil.toScanMetrics(serializedMetrics); return scanMetrics; @@ -5209,10 +5210,10 @@ public class TestFromClientSide { // Verify region location before move. HRegionLocation addrCache = table.getRegionLocation(regionInfo.getStartKey(), false); HRegionLocation addrNoCache = table.getRegionLocation(regionInfo.getStartKey(), true); - + assertEquals(addrBefore.getPort(), addrCache.getPort()); assertEquals(addrBefore.getPort(), addrNoCache.getPort()); - + ServerName addrAfter = null; // Now move the region to a different server. for (int i = 0; i < SLAVES; i++) { @@ -5227,7 +5228,7 @@ public class TestFromClientSide { break; } } - + // Verify the region was moved. addrCache = table.getRegionLocation(regionInfo.getStartKey(), false); addrNoCache = table.getRegionLocation(regionInfo.getStartKey(), true); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestRegionObserverInterface.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestRegionObserverInterface.java index 6895fbea084..bf5227bb281 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestRegionObserverInterface.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestRegionObserverInterface.java @@ -112,7 +112,7 @@ public class TestRegionObserverInterface { util.shutdownMiniCluster(); } - @Test + @Test (timeout=300000) public void testRegionObserver() throws IOException { TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testRegionObserver"); // recreate table every time in order to reset the status of the @@ -176,7 +176,7 @@ public class TestRegionObserverInterface { new Integer[] {1, 1, 1, 1}); } - @Test + @Test (timeout=300000) public void testRowMutation() throws IOException { TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testRowMutation"); Table table = util.createTable(tableName, new byte[][] {A, B, C}); @@ -213,7 +213,7 @@ public class TestRegionObserverInterface { } } - @Test + @Test (timeout=300000) public void testIncrementHook() throws IOException { TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testIncrementHook"); Table table = util.createTable(tableName, new byte[][] {A, B, C}); @@ -240,7 +240,7 @@ public class TestRegionObserverInterface { } } - @Test + @Test (timeout=300000) public void testCheckAndPutHooks() throws IOException { TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testCheckAndPutHooks"); @@ -268,7 +268,7 @@ public class TestRegionObserverInterface { } } - @Test + @Test (timeout=300000) public void testCheckAndDeleteHooks() throws IOException { TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testCheckAndDeleteHooks"); @@ -298,7 +298,7 @@ public class TestRegionObserverInterface { } } - @Test + @Test (timeout=300000) public void testAppendHook() throws IOException { TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testAppendHook"); Table table = util.createTable(tableName, new byte[][] {A, B, C}); @@ -325,7 +325,7 @@ public class TestRegionObserverInterface { } } - @Test + @Test (timeout=300000) // HBase-3583 public void testHBase3583() throws IOException { TableName tableName = @@ -377,7 +377,7 @@ public class TestRegionObserverInterface { table.close(); } - @Test + @Test (timeout=300000) // HBase-3758 public void testHBase3758() throws IOException { TableName tableName = @@ -483,7 +483,7 @@ public class TestRegionObserverInterface { * Tests overriding compaction handling via coprocessor hooks * @throws Exception */ - @Test + @Test (timeout=300000) public void testCompactionOverride() throws Exception { TableName compactTable = TableName.valueOf("TestCompactionOverride"); Admin admin = util.getHBaseAdmin(); @@ -554,7 +554,7 @@ public class TestRegionObserverInterface { table.close(); } - @Test + @Test (timeout=300000) public void bulkLoadHFileTest() throws Exception { String testName = TestRegionObserverInterface.class.getName()+".bulkLoadHFileTest"; TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".bulkLoadHFileTest"); @@ -587,7 +587,7 @@ public class TestRegionObserverInterface { } } - @Test + @Test (timeout=300000) public void testRecovery() throws Exception { LOG.info(TestRegionObserverInterface.class.getName() +".testRecovery"); TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testRecovery"); @@ -637,7 +637,7 @@ public class TestRegionObserverInterface { } } - @Test + @Test (timeout=300000) public void testLegacyRecovery() throws Exception { LOG.info(TestRegionObserverInterface.class.getName() +".testLegacyRecovery"); TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testLegacyRecovery"); @@ -687,7 +687,7 @@ public class TestRegionObserverInterface { } } - @Test + @Test (timeout=300000) public void testPreWALRestoreSkip() throws Exception { LOG.info(TestRegionObserverInterface.class.getName() + ".testPreWALRestoreSkip"); TableName tableName = TableName.valueOf(SimpleRegionObserver.TABLE_SKIPPED); @@ -772,5 +772,4 @@ public class TestRegionObserverInterface { writer.close(); } } - -} +} \ No newline at end of file From 421036c1c46bfc0be97b1d01bee6b9f7a02b8420 Mon Sep 17 00:00:00 2001 From: stack Date: Fri, 13 Feb 2015 15:53:54 -0800 Subject: [PATCH 016/329] HBASE-13030 [1.0.0 polish] Make ScanMetrics public again and align Put 'add' with Get, Delete, etc., addColumn -- ADDENDUM --- .../org/apache/hadoop/hbase/client/TestFromClientSide.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java index 49a6036ec0d..e44792aa083 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java @@ -5041,6 +5041,7 @@ public class TestFromClientSide { // now, test that the metrics are still collected even if you don't call close, but do // run past the end of all the records + /** There seems to be a timing issue here. Comment out for now. Fix when time. Scan scanWithoutClose = new Scan(); scanWithoutClose.setCaching(1); scanWithoutClose.setScanMetricsEnabled(true); @@ -5050,13 +5051,14 @@ public class TestFromClientSide { ScanMetrics scanMetricsWithoutClose = getScanMetrics(scanWithoutClose); assertEquals("Did not access all the regions in the table", numOfRegions, scanMetricsWithoutClose.countOfRegions.get()); + */ // finally, test that the metrics are collected correctly if you both run past all the records, // AND close the scanner Scan scanWithClose = new Scan(); // make sure we can set caching up to the number of a scanned values scanWithClose.setCaching(numRecords); - scan2.setScanMetricsEnabled(true); + scanWithClose.setScanMetricsEnabled(true); ResultScanner scannerWithClose = ht.getScanner(scanWithClose); for (Result result : scannerWithClose.next(numRecords + 1)) { } From c96df5e240baed7509d77f6349effe0777ebef57 Mon Sep 17 00:00:00 2001 From: Sean Busbey Date: Wed, 11 Feb 2015 17:02:57 -0600 Subject: [PATCH 017/329] HBASE-13027 Ensure extension of TableInputFormatBase works. * move mapreduce version of TableInputFormat tests out of mapred * add ability to get runnable job via MR test shims * correct the javadoc example for current APIs. * add tests the run a job based on the extending TableInputFormatBase (as given in the javadocs) * add tests that run jobs based on the javadocs from 0.98 * fall back to our own Connection if ussers of the deprecated table configuration have a managed connection. --- .../hbase/client/ConnectionManager.java | 4 +- .../NeedUnmanagedConnectionException.java | 31 ++ .../hbase/mapred/TableInputFormatBase.java | 32 +- .../hbase/mapreduce/TableInputFormatBase.java | 68 ++- .../hbase/mapred/TestTableInputFormat.java | 207 ++++---- .../hbase/mapreduce/MapreduceTestingShim.java | 27 +- .../hbase/mapreduce/TestTableInputFormat.java | 462 ++++++++++++++++++ 7 files changed, 694 insertions(+), 137 deletions(-) create mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/client/NeedUnmanagedConnectionException.java create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestTableInputFormat.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java index e986156019a..ce6ed78eb09 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java @@ -723,7 +723,7 @@ final class ConnectionManager { @Override public HTableInterface getTable(TableName tableName, ExecutorService pool) throws IOException { if (managed) { - throw new IOException("The connection has to be unmanaged."); + throw new NeedUnmanagedConnectionException(); } return new HTable(tableName, this, tableConfig, rpcCallerFactory, rpcControllerFactory, pool); } @@ -758,7 +758,7 @@ final class ConnectionManager { @Override public Admin getAdmin() throws IOException { if (managed) { - throw new IOException("The connection has to be unmanaged."); + throw new NeedUnmanagedConnectionException(); } return new HBaseAdmin(this); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/NeedUnmanagedConnectionException.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/NeedUnmanagedConnectionException.java new file mode 100644 index 00000000000..6ea068877da --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/NeedUnmanagedConnectionException.java @@ -0,0 +1,31 @@ +/** + * + * 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.apache.hadoop.hbase.client; + +import org.apache.hadoop.hbase.DoNotRetryIOException; +import org.apache.hadoop.hbase.classification.InterfaceAudience; + +/** + * Used for internal signalling that a Connection implementation needs to be + * user-managed to be used for particular request types. + */ +@InterfaceAudience.Private +public class NeedUnmanagedConnectionException extends DoNotRetryIOException { + private static final long serialVersionUID = 1876775844L; + + public NeedUnmanagedConnectionException() { + super("The connection has to be unmanaged."); + } +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java index 2a50efc4aac..d98b5f4b522 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java @@ -45,23 +45,25 @@ import org.apache.hadoop.mapred.Reporter; *

  *   class ExampleTIF extends TableInputFormatBase implements JobConfigurable {
  *
+ *     {@literal @}Override
  *     public void configure(JobConf job) {
- *       HTable exampleTable = new HTable(HBaseConfiguration.create(job),
- *         Bytes.toBytes("exampleTable"));
- *       // mandatory
- *       setHTable(exampleTable);
- *       Text[] inputColumns = new byte [][] { Bytes.toBytes("columnA"),
- *         Bytes.toBytes("columnB") };
- *       // mandatory
- *       setInputColumns(inputColumns);
- *       RowFilterInterface exampleFilter = new RegExpRowFilter("keyPrefix.*");
- *       // optional
- *       setRowFilter(exampleFilter);
+ *       try {
+ *         HTable exampleTable = new HTable(HBaseConfiguration.create(job),
+ *           Bytes.toBytes("exampleTable"));
+ *         // mandatory
+ *         setHTable(exampleTable);
+ *         byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"),
+ *           Bytes.toBytes("columnB") };
+ *         // mandatory
+ *         setInputColumns(inputColumns);
+ *         // optional, by default we'll get everything for the given columns.
+ *         Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*"));
+ *         setRowFilter(exampleFilter);
+ *       } catch (IOException exception) {
+ *         throw new RuntimeException("Failed to configure for job.", exception);
+ *       }
  *     }
- *
- *     public void validateInput(JobConf job) throws IOException {
- *     }
- *  }
+ *   }
  * 
*/ diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java index 6ab7ba8d2cc..adfe493096d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java @@ -39,7 +39,9 @@ import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.NeedUnmanagedConnectionException; import org.apache.hadoop.hbase.client.RegionLocator; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan; @@ -69,28 +71,38 @@ import org.apache.hadoop.util.StringUtils; * * private JobConf job; * + * {@literal @}Override * public void configure(JobConf job) { * this.job = job; - * Text[] inputColumns = new byte [][] { Bytes.toBytes("cf1:columnA"), - * Bytes.toBytes("cf2") }; - * // mandatory - * setInputColumns(inputColumns); - * RowFilterInterface exampleFilter = new RegExpRowFilter("keyPrefix.*"); - * // optional - * setRowFilter(exampleFilter); + * byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"), + * Bytes.toBytes("columnB") }; + * // optional, by default we'll get everything for the table. + * Scan scan = new Scan(); + * for (byte[] family : inputColumns) { + * scan.addFamily(family); + * } + * Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*")); + * scan.setFilter(exampleFilter); + * setScan(scan); * } - * - * protected void initialize() { - * Connection connection = - * ConnectionFactory.createConnection(HBaseConfiguration.create(job)); - * TableName tableName = TableName.valueOf("exampleTable"); - * // mandatory - * initializeTable(connection, tableName); - * } * - * public void validateInput(JobConf job) throws IOException { + * {@literal @}Override + * protected void initialize() { + * if (job == null) { + * throw new IllegalStateException("must have already gotten the JobConf before " + + * "initialize is called."); + * } + * try { + * Connection connection = + * ConnectionFactory.createConnection(HBaseConfiguration.create(job)); + * TableName tableName = TableName.valueOf("exampleTable"); + * // mandatory + * initializeTable(connection, tableName); + * } catch (IOException exception) { + * throw new RuntimeException("Failed to initialize.", exception); + * } * } - * } + * } * */ @InterfaceAudience.Public @@ -582,15 +594,31 @@ extends InputFormat { @Deprecated protected void setHTable(HTable table) throws IOException { this.table = table; - this.regionLocator = table.getRegionLocator(); this.connection = table.getConnection(); - this.admin = this.connection.getAdmin(); + try { + this.regionLocator = table.getRegionLocator(); + this.admin = this.connection.getAdmin(); + } catch (NeedUnmanagedConnectionException exception) { + LOG.warn("You are using an HTable instance that relies on an HBase-managed Connection. " + + "This is usually due to directly creating an HTable, which is deprecated. Instead, you " + + "should create a Connection object and then request a Table instance from it. If you " + + "don't need the Table instance for your own use, you should instead use the " + + "TableInputFormatBase.initalizeTable method directly."); + LOG.info("Creating an additional unmanaged connection because user provided one can't be " + + "used for administrative actions. We'll close it when we close out the table."); + LOG.debug("Details about our failure to request an administrative interface.", exception); + // Do we need a "copy the settings from this Connection" method? are things like the User + // properly maintained by just looking again at the Configuration? + this.connection = ConnectionFactory.createConnection(this.connection.getConfiguration()); + this.regionLocator = this.connection.getRegionLocator(table.getName()); + this.admin = this.connection.getAdmin(); + } } /** * Allows subclasses to initialize the table information. * - * @param connection The {@link Connection} to the HBase cluster. + * @param connection The Connection to the HBase cluster. MUST be unmanaged. We will close. * @param tableName The {@link TableName} of the table to process. * @throws IOException */ diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapred/TestTableInputFormat.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapred/TestTableInputFormat.java index 9fb4eb8dd26..234a2e80170 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapred/TestTableInputFormat.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapred/TestTableInputFormat.java @@ -35,15 +35,31 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.*; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; +import org.apache.hadoop.hbase.filter.Filter; +import org.apache.hadoop.hbase.filter.RegexStringComparator; +import org.apache.hadoop.hbase.filter.RowFilter; +import org.apache.hadoop.hbase.mapreduce.MapreduceTestingShim; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.testclassification.LargeTests; import org.apache.hadoop.hbase.testclassification.MapReduceTests; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.io.NullWritable; +import org.apache.hadoop.mapred.JobClient; +import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.mapred.JobConfigurable; +import org.apache.hadoop.mapred.MiniMRCluster; +import org.apache.hadoop.mapred.OutputCollector; +import org.apache.hadoop.mapred.Reporter; +import org.apache.hadoop.mapred.RunningJob; +import org.apache.hadoop.mapred.lib.NullOutputFormat; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -62,6 +78,7 @@ public class TestTableInputFormat { private static final Log LOG = LogFactory.getLog(TestTableInputFormat.class); private final static HBaseTestingUtility UTIL = new HBaseTestingUtility(); + private static MiniMRCluster mrCluster; static final byte[] FAMILY = Bytes.toBytes("family"); private static final byte[][] columns = new byte[][] { FAMILY }; @@ -69,10 +86,12 @@ public class TestTableInputFormat { @BeforeClass public static void beforeClass() throws Exception { UTIL.startMiniCluster(); + mrCluster = UTIL.startMiniMapReduceCluster(); } @AfterClass public static void afterClass() throws Exception { + UTIL.shutdownMiniMapReduceCluster(); UTIL.shutdownMiniCluster(); } @@ -91,12 +110,27 @@ public class TestTableInputFormat { * @throws IOException */ public static Table createTable(byte[] tableName) throws IOException { - Table table = UTIL.createTable(TableName.valueOf(tableName), new byte[][]{FAMILY}); + return createTable(tableName, new byte[][] { FAMILY }); + } + + /** + * Setup a table with two rows and values per column family. + * + * @param tableName + * @return + * @throws IOException + */ + public static Table createTable(byte[] tableName, byte[][] families) throws IOException { + Table table = UTIL.createTable(TableName.valueOf(tableName), families); Put p = new Put("aaa".getBytes()); - p.add(FAMILY, null, "value aaa".getBytes()); + for (byte[] family : families) { + p.add(family, null, "value aaa".getBytes()); + } table.put(p); p = new Put("bbb".getBytes()); - p.add(FAMILY, null, "value bbb".getBytes()); + for (byte[] family : families) { + p.add(family, null, "value bbb".getBytes()); + } table.put(p); return table; } @@ -151,46 +185,6 @@ public class TestTableInputFormat { assertFalse(more); } - /** - * Create table data and run tests on specified htable using the - * o.a.h.hbase.mapreduce API. - * - * @param table - * @throws IOException - * @throws InterruptedException - */ - static void runTestMapreduce(Table table) throws IOException, - InterruptedException { - org.apache.hadoop.hbase.mapreduce.TableRecordReaderImpl trr = - new org.apache.hadoop.hbase.mapreduce.TableRecordReaderImpl(); - Scan s = new Scan(); - s.setStartRow("aaa".getBytes()); - s.setStopRow("zzz".getBytes()); - s.addFamily(FAMILY); - trr.setScan(s); - trr.setHTable(table); - - trr.initialize(null, null); - Result r = new Result(); - ImmutableBytesWritable key = new ImmutableBytesWritable(); - - boolean more = trr.nextKeyValue(); - assertTrue(more); - key = trr.getCurrentKey(); - r = trr.getCurrentValue(); - checkResult(r, key, "aaa".getBytes(), "value aaa".getBytes()); - - more = trr.nextKeyValue(); - assertTrue(more); - key = trr.getCurrentKey(); - r = trr.getCurrentValue(); - checkResult(r, key, "bbb".getBytes(), "value bbb".getBytes()); - - // no more data - more = trr.nextKeyValue(); - assertFalse(more); - } - /** * Create a table that IOE's on first scanner next call * @@ -321,70 +315,85 @@ public class TestTableInputFormat { } /** - * Run test assuming no errors using newer mapreduce api - * - * @throws IOException - * @throws InterruptedException + * Verify the example we present in javadocs on TableInputFormatBase */ @Test - public void testTableRecordReaderMapreduce() throws IOException, - InterruptedException { - Table table = createTable("table1-mr".getBytes()); - runTestMapreduce(table); + public void testExtensionOfTableInputFormatBase() throws IOException { + LOG.info("testing use of an InputFormat taht extends InputFormatBase"); + final Table table = createTable(Bytes.toBytes("exampleTable"), + new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") }); + final JobConf job = MapreduceTestingShim.getJobConf(mrCluster); + job.setInputFormat(ExampleTIF.class); + job.setOutputFormat(NullOutputFormat.class); + job.setMapperClass(ExampleVerifier.class); + job.setNumReduceTasks(0); + LOG.debug("submitting job."); + final RunningJob run = JobClient.runJob(job); + assertTrue("job failed!", run.isSuccessful()); + assertEquals("Saw the wrong number of instances of the filtered-for row.", 2, run.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":row", "aaa").getCounter()); + assertEquals("Saw any instances of the filtered out row.", 0, run.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":row", "bbb").getCounter()); + assertEquals("Saw the wrong number of instances of columnA.", 1, run.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":family", "columnA").getCounter()); + assertEquals("Saw the wrong number of instances of columnB.", 1, run.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":family", "columnB").getCounter()); + assertEquals("Saw the wrong count of values for the filtered-for row.", 2, run.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":value", "value aaa").getCounter()); + assertEquals("Saw the wrong count of values for the filtered-out row.", 0, run.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":value", "value bbb").getCounter()); } - /** - * Run test assuming Scanner IOException failure using newer mapreduce api - * - * @throws IOException - * @throws InterruptedException - */ - @Test - public void testTableRecordReaderScannerFailMapreduce() throws IOException, - InterruptedException { - Table htable = createIOEScannerTable("table2-mr".getBytes(), 1); - runTestMapreduce(htable); + public static class ExampleVerifier implements TableMap { + + @Override + public void configure(JobConf conf) { + } + + @Override + public void map(ImmutableBytesWritable key, Result value, + OutputCollector output, + Reporter reporter) throws IOException { + for (Cell cell : value.listCells()) { + reporter.getCounter(TestTableInputFormat.class.getName() + ":row", + Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength())) + .increment(1l); + reporter.getCounter(TestTableInputFormat.class.getName() + ":family", + Bytes.toString(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength())) + .increment(1l); + reporter.getCounter(TestTableInputFormat.class.getName() + ":value", + Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength())) + .increment(1l); + } + } + + @Override + public void close() { + } + } - /** - * Run test assuming Scanner IOException failure using newer mapreduce api - * - * @throws IOException - * @throws InterruptedException - */ - @Test(expected = IOException.class) - public void testTableRecordReaderScannerFailMapreduceTwice() throws IOException, - InterruptedException { - Table htable = createIOEScannerTable("table3-mr".getBytes(), 2); - runTestMapreduce(htable); - } + public static class ExampleTIF extends TableInputFormatBase implements JobConfigurable { - /** - * Run test assuming UnknownScannerException (which is a type of - * DoNotRetryIOException) using newer mapreduce api - * - * @throws InterruptedException - * @throws org.apache.hadoop.hbase.DoNotRetryIOException - */ - @Test - public void testTableRecordReaderScannerTimeoutMapreduce() - throws IOException, InterruptedException { - Table htable = createDNRIOEScannerTable("table4-mr".getBytes(), 1); - runTestMapreduce(htable); - } + @Override + public void configure(JobConf job) { + try { + HTable exampleTable = new HTable(HBaseConfiguration.create(job), + Bytes.toBytes("exampleTable")); + // mandatory + setHTable(exampleTable); + byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"), + Bytes.toBytes("columnB") }; + // mandatory + setInputColumns(inputColumns); + Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*")); + // optional + setRowFilter(exampleFilter); + } catch (IOException exception) { + throw new RuntimeException("Failed to configure for job.", exception); + } + } - /** - * Run test assuming UnknownScannerException (which is a type of - * DoNotRetryIOException) using newer mapreduce api - * - * @throws InterruptedException - * @throws org.apache.hadoop.hbase.DoNotRetryIOException - */ - @Test(expected = org.apache.hadoop.hbase.DoNotRetryIOException.class) - public void testTableRecordReaderScannerTimeoutMapreduceTwice() - throws IOException, InterruptedException { - Table htable = createDNRIOEScannerTable("table5-mr".getBytes(), 2); - runTestMapreduce(htable); } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/MapreduceTestingShim.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/MapreduceTestingShim.java index dee42771ade..b080d7f4264 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/MapreduceTestingShim.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/MapreduceTestingShim.java @@ -52,6 +52,8 @@ abstract public class MapreduceTestingShim { abstract public JobContext newJobContext(Configuration jobConf) throws IOException; + + abstract public Job newJob(Configuration conf) throws IOException; abstract public JobConf obtainJobConf(MiniMRCluster cluster); @@ -66,6 +68,10 @@ abstract public class MapreduceTestingShim { return instance.obtainJobConf(cluster); } + public static Job createJob(Configuration conf) throws IOException { + return instance.newJob(conf); + } + public static String getMROutputDirProp() { return instance.obtainMROutputDirProp(); } @@ -84,6 +90,20 @@ abstract public class MapreduceTestingShim { "Failed to instantiate new JobContext(jobConf, new JobID())", e); } } + + @Override + public Job newJob(Configuration conf) throws IOException { + // Implementing: + // return new Job(conf); + Constructor c; + try { + c = Job.class.getConstructor(Configuration.class); + return c.newInstance(conf); + } catch (Exception e) { + throw new IllegalStateException( + "Failed to instantiate new Job(conf)", e); + } + } public JobConf obtainJobConf(MiniMRCluster cluster) { if (cluster == null) return null; @@ -110,11 +130,16 @@ abstract public class MapreduceTestingShim { private static class MapreduceV2Shim extends MapreduceTestingShim { public JobContext newJobContext(Configuration jobConf) { + return newJob(jobConf); + } + + @Override + public Job newJob(Configuration jobConf) { // Implementing: // return Job.getInstance(jobConf); try { Method m = Job.class.getMethod("getInstance", Configuration.class); - return (JobContext) m.invoke(null, jobConf); // static method, then arg + return (Job) m.invoke(null, jobConf); // static method, then arg } catch (Exception e) { e.printStackTrace(); throw new IllegalStateException( diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestTableInputFormat.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestTableInputFormat.java new file mode 100644 index 00000000000..26029619e46 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestTableInputFormat.java @@ -0,0 +1,462 @@ +/** + * + * 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.hbase.mapreduce; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.*; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; +import org.apache.hadoop.hbase.filter.Filter; +import org.apache.hadoop.hbase.filter.RegexStringComparator; +import org.apache.hadoop.hbase.filter.RowFilter; +import org.apache.hadoop.hbase.io.ImmutableBytesWritable; +import org.apache.hadoop.hbase.testclassification.LargeTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.io.NullWritable; +import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.mapred.JobConfigurable; +import org.apache.hadoop.mapred.MiniMRCluster; +import org.apache.hadoop.mapreduce.InputFormat; +import org.apache.hadoop.mapreduce.Job; +import org.apache.hadoop.mapreduce.Mapper.Context; +import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** + * This tests the TableInputFormat and its recovery semantics + * + */ +@Category(LargeTests.class) +public class TestTableInputFormat { + + private static final Log LOG = LogFactory.getLog(TestTableInputFormat.class); + + private final static HBaseTestingUtility UTIL = new HBaseTestingUtility(); + private static MiniMRCluster mrCluster; + static final byte[] FAMILY = Bytes.toBytes("family"); + + private static final byte[][] columns = new byte[][] { FAMILY }; + + @BeforeClass + public static void beforeClass() throws Exception { + UTIL.startMiniCluster(); + mrCluster = UTIL.startMiniMapReduceCluster(); + } + + @AfterClass + public static void afterClass() throws Exception { + UTIL.shutdownMiniMapReduceCluster(); + UTIL.shutdownMiniCluster(); + } + + @Before + public void before() throws IOException { + LOG.info("before"); + UTIL.ensureSomeRegionServersAvailable(1); + LOG.info("before done"); + } + + /** + * Setup a table with two rows and values. + * + * @param tableName + * @return + * @throws IOException + */ + public static Table createTable(byte[] tableName) throws IOException { + return createTable(tableName, new byte[][] { FAMILY }); + } + + /** + * Setup a table with two rows and values per column family. + * + * @param tableName + * @return + * @throws IOException + */ + public static Table createTable(byte[] tableName, byte[][] families) throws IOException { + Table table = UTIL.createTable(TableName.valueOf(tableName), families); + Put p = new Put("aaa".getBytes()); + for (byte[] family : families) { + p.add(family, null, "value aaa".getBytes()); + } + table.put(p); + p = new Put("bbb".getBytes()); + for (byte[] family : families) { + p.add(family, null, "value bbb".getBytes()); + } + table.put(p); + return table; + } + + /** + * Verify that the result and key have expected values. + * + * @param r + * @param key + * @param expectedKey + * @param expectedValue + * @return + */ + static boolean checkResult(Result r, ImmutableBytesWritable key, + byte[] expectedKey, byte[] expectedValue) { + assertEquals(0, key.compareTo(expectedKey)); + Map vals = r.getFamilyMap(FAMILY); + byte[] value = vals.values().iterator().next(); + assertTrue(Arrays.equals(value, expectedValue)); + return true; // if succeed + } + + /** + * Create table data and run tests on specified htable using the + * o.a.h.hbase.mapreduce API. + * + * @param table + * @throws IOException + * @throws InterruptedException + */ + static void runTestMapreduce(Table table) throws IOException, + InterruptedException { + org.apache.hadoop.hbase.mapreduce.TableRecordReaderImpl trr = + new org.apache.hadoop.hbase.mapreduce.TableRecordReaderImpl(); + Scan s = new Scan(); + s.setStartRow("aaa".getBytes()); + s.setStopRow("zzz".getBytes()); + s.addFamily(FAMILY); + trr.setScan(s); + trr.setHTable(table); + + trr.initialize(null, null); + Result r = new Result(); + ImmutableBytesWritable key = new ImmutableBytesWritable(); + + boolean more = trr.nextKeyValue(); + assertTrue(more); + key = trr.getCurrentKey(); + r = trr.getCurrentValue(); + checkResult(r, key, "aaa".getBytes(), "value aaa".getBytes()); + + more = trr.nextKeyValue(); + assertTrue(more); + key = trr.getCurrentKey(); + r = trr.getCurrentValue(); + checkResult(r, key, "bbb".getBytes(), "value bbb".getBytes()); + + // no more data + more = trr.nextKeyValue(); + assertFalse(more); + } + + /** + * Create a table that IOE's on first scanner next call + * + * @throws IOException + */ + static Table createIOEScannerTable(byte[] name, final int failCnt) + throws IOException { + // build up a mock scanner stuff to fail the first time + Answer a = new Answer() { + int cnt = 0; + + @Override + public ResultScanner answer(InvocationOnMock invocation) throws Throwable { + // first invocation return the busted mock scanner + if (cnt++ < failCnt) { + // create mock ResultScanner that always fails. + Scan scan = mock(Scan.class); + doReturn("bogus".getBytes()).when(scan).getStartRow(); // avoid npe + ResultScanner scanner = mock(ResultScanner.class); + // simulate TimeoutException / IOException + doThrow(new IOException("Injected exception")).when(scanner).next(); + return scanner; + } + + // otherwise return the real scanner. + return (ResultScanner) invocation.callRealMethod(); + } + }; + + Table htable = spy(createTable(name)); + doAnswer(a).when(htable).getScanner((Scan) anyObject()); + return htable; + } + + /** + * Create a table that throws a DoNoRetryIOException on first scanner next + * call + * + * @throws IOException + */ + static Table createDNRIOEScannerTable(byte[] name, final int failCnt) + throws IOException { + // build up a mock scanner stuff to fail the first time + Answer a = new Answer() { + int cnt = 0; + + @Override + public ResultScanner answer(InvocationOnMock invocation) throws Throwable { + // first invocation return the busted mock scanner + if (cnt++ < failCnt) { + // create mock ResultScanner that always fails. + Scan scan = mock(Scan.class); + doReturn("bogus".getBytes()).when(scan).getStartRow(); // avoid npe + ResultScanner scanner = mock(ResultScanner.class); + + invocation.callRealMethod(); // simulate UnknownScannerException + doThrow( + new UnknownScannerException("Injected simulated TimeoutException")) + .when(scanner).next(); + return scanner; + } + + // otherwise return the real scanner. + return (ResultScanner) invocation.callRealMethod(); + } + }; + + Table htable = spy(createTable(name)); + doAnswer(a).when(htable).getScanner((Scan) anyObject()); + return htable; + } + + /** + * Run test assuming no errors using newer mapreduce api + * + * @throws IOException + * @throws InterruptedException + */ + @Test + public void testTableRecordReaderMapreduce() throws IOException, + InterruptedException { + Table table = createTable("table1-mr".getBytes()); + runTestMapreduce(table); + } + + /** + * Run test assuming Scanner IOException failure using newer mapreduce api + * + * @throws IOException + * @throws InterruptedException + */ + @Test + public void testTableRecordReaderScannerFailMapreduce() throws IOException, + InterruptedException { + Table htable = createIOEScannerTable("table2-mr".getBytes(), 1); + runTestMapreduce(htable); + } + + /** + * Run test assuming Scanner IOException failure using newer mapreduce api + * + * @throws IOException + * @throws InterruptedException + */ + @Test(expected = IOException.class) + public void testTableRecordReaderScannerFailMapreduceTwice() throws IOException, + InterruptedException { + Table htable = createIOEScannerTable("table3-mr".getBytes(), 2); + runTestMapreduce(htable); + } + + /** + * Run test assuming UnknownScannerException (which is a type of + * DoNotRetryIOException) using newer mapreduce api + * + * @throws InterruptedException + * @throws org.apache.hadoop.hbase.DoNotRetryIOException + */ + @Test + public void testTableRecordReaderScannerTimeoutMapreduce() + throws IOException, InterruptedException { + Table htable = createDNRIOEScannerTable("table4-mr".getBytes(), 1); + runTestMapreduce(htable); + } + + /** + * Run test assuming UnknownScannerException (which is a type of + * DoNotRetryIOException) using newer mapreduce api + * + * @throws InterruptedException + * @throws org.apache.hadoop.hbase.DoNotRetryIOException + */ + @Test(expected = org.apache.hadoop.hbase.DoNotRetryIOException.class) + public void testTableRecordReaderScannerTimeoutMapreduceTwice() + throws IOException, InterruptedException { + Table htable = createDNRIOEScannerTable("table5-mr".getBytes(), 2); + runTestMapreduce(htable); + } + + /** + * Verify the example we present in javadocs on TableInputFormatBase + */ + @Test + public void testExtensionOfTableInputFormatBase() + throws IOException, InterruptedException, ClassNotFoundException { + LOG.info("testing use of an InputFormat taht extends InputFormatBase"); + final Table htable = createTable(Bytes.toBytes("exampleTable"), + new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") }); + testInputFormat(ExampleTIF.class); + } + + @Test + public void testDeprecatedExtensionOfTableInputFormatBase() + throws IOException, InterruptedException, ClassNotFoundException { + LOG.info("testing use of an InputFormat taht extends InputFormatBase, " + + "using the approach documented in 0.98."); + final Table htable = createTable(Bytes.toBytes("exampleDeprecatedTable"), + new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") }); + testInputFormat(ExampleDeprecatedTIF.class); + } + + void testInputFormat(Class clazz) + throws IOException, InterruptedException, ClassNotFoundException { + final Job job = MapreduceTestingShim.createJob(UTIL.getConfiguration()); + job.setInputFormatClass(clazz); + job.setOutputFormatClass(NullOutputFormat.class); + job.setMapperClass(ExampleVerifier.class); + job.setNumReduceTasks(0); + + LOG.debug("submitting job."); + assertTrue("job failed!", job.waitForCompletion(true)); + assertEquals("Saw the wrong number of instances of the filtered-for row.", 2, job.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":row", "aaa").getValue()); + assertEquals("Saw any instances of the filtered out row.", 0, job.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":row", "bbb").getValue()); + assertEquals("Saw the wrong number of instances of columnA.", 1, job.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":family", "columnA").getValue()); + assertEquals("Saw the wrong number of instances of columnB.", 1, job.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":family", "columnB").getValue()); + assertEquals("Saw the wrong count of values for the filtered-for row.", 2, job.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":value", "value aaa").getValue()); + assertEquals("Saw the wrong count of values for the filtered-out row.", 0, job.getCounters() + .findCounter(TestTableInputFormat.class.getName() + ":value", "value bbb").getValue()); + } + + public static class ExampleVerifier extends TableMapper { + + @Override + public void map(ImmutableBytesWritable key, Result value, Context context) + throws IOException { + for (Cell cell : value.listCells()) { + context.getCounter(TestTableInputFormat.class.getName() + ":row", + Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength())) + .increment(1l); + context.getCounter(TestTableInputFormat.class.getName() + ":family", + Bytes.toString(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength())) + .increment(1l); + context.getCounter(TestTableInputFormat.class.getName() + ":value", + Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength())) + .increment(1l); + } + } + + } + + public static class ExampleDeprecatedTIF extends TableInputFormatBase implements JobConfigurable { + + @Override + public void configure(JobConf job) { + try { + HTable exampleTable = new HTable(HBaseConfiguration.create(job), + Bytes.toBytes("exampleDeprecatedTable")); + // mandatory + setHTable(exampleTable); + byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"), + Bytes.toBytes("columnB") }; + // optional + Scan scan = new Scan(); + for (byte[] family : inputColumns) { + scan.addFamily(family); + } + Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*")); + scan.setFilter(exampleFilter); + setScan(scan); + } catch (IOException exception) { + throw new RuntimeException("Failed to configure for job.", exception); + } + } + + } + + public static class ExampleTIF extends TableInputFormatBase implements JobConfigurable { + + private JobConf job; + + @Override + public void configure(JobConf job) { + this.job = job; + byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"), + Bytes.toBytes("columnB") }; + //optional + Scan scan = new Scan(); + for (byte[] family : inputColumns) { + scan.addFamily(family); + } + Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*")); + scan.setFilter(exampleFilter); + setScan(scan); + } + + @Override + protected void initialize() { + if (job == null) { + throw new IllegalStateException("must have already gotten the JobConf before initialize " + + "is called."); + } + try { + Connection connection = ConnectionFactory.createConnection(HBaseConfiguration.create(job)); + TableName tableName = TableName.valueOf("exampleTable"); + // mandatory + initializeTable(connection, tableName); + } catch (IOException exception) { + throw new RuntimeException("Failed to initialize.", exception); + } + } + + } +} + From f286797fbf063a929b0dc841c6071edb8eae2dd6 Mon Sep 17 00:00:00 2001 From: Aditya Kishore Date: Fri, 13 Feb 2015 18:35:33 -0800 Subject: [PATCH 018/329] HBASE-13010 HFileOutputFormat2 partitioner's path is hard-coded as '/tmp' Signed-off-by: Andrew Purtell --- .../hadoop/hbase/mapreduce/HFileOutputFormat2.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/HFileOutputFormat2.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/HFileOutputFormat2.java index f69f21f4b45..f2d5c6f5102 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/HFileOutputFormat2.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/HFileOutputFormat2.java @@ -584,17 +584,17 @@ public class HFileOutputFormat2 */ static void configurePartitioner(Job job, List splitPoints) throws IOException { - + Configuration conf = job.getConfiguration(); // create the partitions file - FileSystem fs = FileSystem.get(job.getConfiguration()); - Path partitionsPath = new Path("/tmp", "partitions_" + UUID.randomUUID()); + FileSystem fs = FileSystem.get(conf); + Path partitionsPath = new Path(conf.get("hadoop.tmp.dir"), "partitions_" + UUID.randomUUID()); fs.makeQualified(partitionsPath); - writePartitions(job.getConfiguration(), partitionsPath, splitPoints); + writePartitions(conf, partitionsPath, splitPoints); fs.deleteOnExit(partitionsPath); // configure job to use it job.setPartitionerClass(TotalOrderPartitioner.class); - TotalOrderPartitioner.setPartitionFile(job.getConfiguration(), partitionsPath); + TotalOrderPartitioner.setPartitionFile(conf, partitionsPath); } /** From 6f904fe4caa026d4e0d050d97b2d8a1be6ca0785 Mon Sep 17 00:00:00 2001 From: Lars Hofhansl Date: Fri, 13 Feb 2015 22:18:51 -0800 Subject: [PATCH 019/329] HBASE-12971 Replication stuck due to large default value for replication.source.maxretriesmultiplier. --- .../regionserver/HBaseInterClusterReplicationEndpoint.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/HBaseInterClusterReplicationEndpoint.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/HBaseInterClusterReplicationEndpoint.java index 397044d6f7f..95c253d6dab 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/HBaseInterClusterReplicationEndpoint.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/regionserver/HBaseInterClusterReplicationEndpoint.java @@ -76,9 +76,9 @@ public class HBaseInterClusterReplicationEndpoint extends HBaseReplicationEndpoi super.init(context); this.conf = HBaseConfiguration.create(ctx.getConfiguration()); decorateConf(); - this.maxRetriesMultiplier = this.conf.getInt("replication.source.maxretriesmultiplier", 10); + this.maxRetriesMultiplier = this.conf.getInt("replication.source.maxretriesmultiplier", 300); this.socketTimeoutMultiplier = this.conf.getInt("replication.source.socketTimeoutMultiplier", - maxRetriesMultiplier * maxRetriesMultiplier); + maxRetriesMultiplier); // TODO: This connection is replication specific or we should make it particular to // replication and make replication specific settings such as compression or codec to use // passing Cells. From 332515ed346e1ffc104ce3bac986bb7030747a03 Mon Sep 17 00:00:00 2001 From: Sean Busbey Date: Fri, 13 Feb 2015 15:47:11 -0600 Subject: [PATCH 020/329] HBASE-13028 Cleanup MapReduce InputFormats --- .../hadoop/hbase/mapred/TableInputFormat.java | 18 +- .../hbase/mapred/TableInputFormatBase.java | 188 +++++++++++++++--- .../hbase/mapreduce/TableInputFormat.java | 4 +- .../hbase/mapreduce/TableInputFormatBase.java | 119 +++++++---- .../hbase/mapred/TestTableInputFormat.java | 72 ++++++- .../hbase/mapreduce/TestTableInputFormat.java | 63 ++++-- 6 files changed, 365 insertions(+), 99 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormat.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormat.java index 368510fd78a..814daeaa1c7 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormat.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormat.java @@ -28,7 +28,6 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; -import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.mapred.FileInputFormat; import org.apache.hadoop.mapred.JobConf; @@ -50,6 +49,15 @@ public class TableInputFormat extends TableInputFormatBase implements public static final String COLUMN_LIST = "hbase.mapred.tablecolumns"; public void configure(JobConf job) { + try { + initialize(job); + } catch (Exception e) { + LOG.error(StringUtils.stringifyException(e)); + } + } + + @Override + protected void initialize(JobConf job) throws IOException { Path[] tableNames = FileInputFormat.getInputPaths(job); String colArg = job.get(COLUMN_LIST); String[] colNames = colArg.split(" "); @@ -58,12 +66,8 @@ public class TableInputFormat extends TableInputFormatBase implements m_cols[i] = Bytes.toBytes(colNames[i]); } setInputColumns(m_cols); - try { - Connection connection = ConnectionFactory.createConnection(job); - setHTable((HTable) connection.getTable(TableName.valueOf(tableNames[0].getName()))); - } catch (Exception e) { - LOG.error(StringUtils.stringifyException(e)); - } + Connection connection = ConnectionFactory.createConnection(job); + initializeTable(connection, TableName.valueOf(tableNames[0].getName())); } public void validateInput(JobConf job) throws IOException { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java index d98b5f4b522..b5b79d2d49a 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java @@ -18,6 +18,7 @@ */ package org.apache.hadoop.hbase.mapred; +import java.io.Closeable; import java.io.IOException; import org.apache.commons.logging.Log; @@ -25,6 +26,8 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Table; @@ -40,28 +43,35 @@ import org.apache.hadoop.mapred.Reporter; * A Base for {@link TableInputFormat}s. Receives a {@link HTable}, a * byte[] of input columns and optionally a {@link Filter}. * Subclasses may use other TableRecordReader implementations. + * + * Subclasses MUST ensure initializeTable(Connection, TableName) is called for an instance to + * function properly. Each of the entry points to this class used by the MapReduce framework, + * {@link #getRecordReader(InputSplit, JobConf, Reporter)} and {@link #getSplits(JobConf, int)}, + * will call {@link #initialize(JobConf)} as a convenient centralized location to handle + * retrieving the necessary configuration information. If your subclass overrides either of these + * methods, either call the parent version or call initialize yourself. + * *

* An example of a subclass: *

- *   class ExampleTIF extends TableInputFormatBase implements JobConfigurable {
+ *   class ExampleTIF extends TableInputFormatBase {
  *
  *     {@literal @}Override
- *     public void configure(JobConf job) {
- *       try {
- *         HTable exampleTable = new HTable(HBaseConfiguration.create(job),
- *           Bytes.toBytes("exampleTable"));
- *         // mandatory
- *         setHTable(exampleTable);
- *         byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"),
- *           Bytes.toBytes("columnB") };
- *         // mandatory
- *         setInputColumns(inputColumns);
- *         // optional, by default we'll get everything for the given columns.
- *         Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*"));
- *         setRowFilter(exampleFilter);
- *       } catch (IOException exception) {
- *         throw new RuntimeException("Failed to configure for job.", exception);
- *       }
+ *     protected void initialize(JobConf context) throws IOException {
+ *       // We are responsible for the lifecycle of this connection until we hand it over in
+ *       // initializeTable.
+ *       Connection connection =
+ *          ConnectionFactory.createConnection(HBaseConfiguration.create(job));
+ *       TableName tableName = TableName.valueOf("exampleTable");
+ *       // mandatory. once passed here, TableInputFormatBase will handle closing the connection.
+ *       initializeTable(connection, tableName);
+ *       byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"),
+ *         Bytes.toBytes("columnB") };
+ *       // mandatory
+ *       setInputColumns(inputColumns);
+ *       // optional, by default we'll get everything for the given columns.
+ *       Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*"));
+ *       setRowFilter(exampleFilter);
  *     }
  *   }
  * 
@@ -74,9 +84,17 @@ implements InputFormat { private static final Log LOG = LogFactory.getLog(TableInputFormatBase.class); private byte [][] inputColumns; private HTable table; + private Connection connection; private TableRecordReader tableRecordReader; private Filter rowFilter; + private static final String NOT_INITIALIZED = "The input format instance has not been properly " + + "initialized. Ensure you call initializeTable either in your constructor or initialize " + + "method"; + private static final String INITIALIZATION_ERROR = "Cannot create a record reader because of a" + + " previous error. Please look at the previous logs lines from" + + " the task's full log for more details."; + /** * Builds a TableRecordReader. If no TableRecordReader was provided, uses * the default. @@ -87,19 +105,63 @@ implements InputFormat { public RecordReader getRecordReader( InputSplit split, JobConf job, Reporter reporter) throws IOException { - TableSplit tSplit = (TableSplit) split; - TableRecordReader trr = this.tableRecordReader; - // if no table record reader was provided use default - if (trr == null) { - trr = new TableRecordReader(); + // In case a subclass uses the deprecated approach or calls initializeTable directly + if (table == null) { + initialize(job); } + // null check in case our child overrides getTable to not throw. + try { + if (getTable() == null) { + // initialize() must not have been implemented in the subclass. + throw new IOException(INITIALIZATION_ERROR); + } + } catch (IllegalStateException exception) { + throw new IOException(INITIALIZATION_ERROR, exception); + } + + TableSplit tSplit = (TableSplit) split; + // if no table record reader was provided use default + final TableRecordReader trr = this.tableRecordReader == null ? new TableRecordReader() : + this.tableRecordReader; trr.setStartRow(tSplit.getStartRow()); trr.setEndRow(tSplit.getEndRow()); trr.setHTable(this.table); trr.setInputColumns(this.inputColumns); trr.setRowFilter(this.rowFilter); trr.init(); - return trr; + return new RecordReader() { + + @Override + public void close() throws IOException { + trr.close(); + closeTable(); + } + + @Override + public ImmutableBytesWritable createKey() { + return trr.createKey(); + } + + @Override + public Result createValue() { + return trr.createValue(); + } + + @Override + public long getPos() throws IOException { + return trr.getPos(); + } + + @Override + public float getProgress() throws IOException { + return trr.getProgress(); + } + + @Override + public boolean next(ImmutableBytesWritable key, Result value) throws IOException { + return trr.next(key, value); + } + }; } /** @@ -123,8 +185,18 @@ implements InputFormat { */ public InputSplit[] getSplits(JobConf job, int numSplits) throws IOException { if (this.table == null) { - throw new IOException("No table was provided"); + initialize(job); } + // null check in case our child overrides getTable to not throw. + try { + if (getTable() == null) { + // initialize() must not have been implemented in the subclass. + throw new IOException(INITIALIZATION_ERROR); + } + } catch (IllegalStateException exception) { + throw new IOException(INITIALIZATION_ERROR, exception); + } + byte [][] startKeys = this.table.getStartKeys(); if (startKeys == null || startKeys.length == 0) { throw new IOException("Expecting at least one region"); @@ -151,6 +223,22 @@ implements InputFormat { return splits; } + /** + * Allows subclasses to initialize the table information. + * + * @param connection The Connection to the HBase cluster. MUST be unmanaged. We will close. + * @param tableName The {@link TableName} of the table to process. + * @throws IOException + */ + protected void initializeTable(Connection connection, TableName tableName) throws IOException { + if (table != null || connection != null) { + LOG.warn("initializeTable called multiple times. Overwriting connection and table " + + "reference; TableInputFormatBase will not close these old references when done."); + } + this.table = (HTable) connection.getTable(tableName); + this.connection = connection; + } + /** * @param inputColumns to be passed in {@link Result} to the map task. */ @@ -160,8 +248,20 @@ implements InputFormat { /** * Allows subclasses to get the {@link HTable}. + * @deprecated use {@link #getTable()} */ - protected Table getHTable() { + @Deprecated + protected HTable getHTable() { + return (HTable) getTable(); + } + + /** + * Allows subclasses to get the {@link Table}. + */ + protected Table getTable() { + if (table == null) { + throw new IllegalStateException(NOT_INITIALIZED); + } return this.table; } @@ -169,7 +269,9 @@ implements InputFormat { * Allows subclasses to set the {@link HTable}. * * @param table to get the data from + * @deprecated use {@link #initializeTable(Connection,TableName)} */ + @Deprecated protected void setHTable(HTable table) { this.table = table; } @@ -192,4 +294,40 @@ implements InputFormat { protected void setRowFilter(Filter rowFilter) { this.rowFilter = rowFilter; } + + /** + * Handle subclass specific set up. + * Each of the entry points used by the MapReduce framework, + * {@link #getRecordReader(InputSplit, JobConf, Reporter)} and {@link #getSplits(JobConf, int)}, + * will call {@link #initialize(JobConf)} as a convenient centralized location to handle + * retrieving the necessary configuration information and calling + * {@link #initializeTable(Connection, TableName)}. + * + * Subclasses should implement their initialize call such that it is safe to call multiple times. + * The current TableInputFormatBase implementation relies on a non-null table reference to decide + * if an initialize call is needed, but this behavior may change in the future. In particular, + * it is critical that initializeTable not be called multiple times since this will leak + * Connection instances. + * + */ + protected void initialize(JobConf job) throws IOException { + } + + /** + * Close the Table and related objects that were initialized via + * {@link #initializeTable(Connection, TableName)}. + * + * @throws IOException + */ + protected void closeTable() throws IOException { + close(table, connection); + table = null; + connection = null; + } + + private void close(Closeable... closables) throws IOException { + for (Closeable c : closables) { + if(c != null) { c.close(); } + } + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormat.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormat.java index 8896eb08389..bc2537b7517 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormat.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormat.java @@ -175,7 +175,9 @@ implements Configurable { } @Override - protected void initialize() { + protected void initialize(JobContext context) throws IOException { + // Do we have to worry about mis-matches between the Configuration from setConf and the one + // in this context? TableName tableName = TableName.valueOf(conf.get(INPUT_TABLE)); try { initializeTable(ConnectionFactory.createConnection(new Configuration(conf)), tableName); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java index adfe493096d..6c42d7f446a 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java @@ -64,16 +64,28 @@ import org.apache.hadoop.util.StringUtils; * A base for {@link TableInputFormat}s. Receives a {@link Connection}, a {@link TableName}, * an {@link Scan} instance that defines the input columns etc. Subclasses may use * other TableRecordReader implementations. + * + * Subclasses MUST ensure initializeTable(Connection, TableName) is called for an instance to + * function properly. Each of the entry points to this class used by the MapReduce framework, + * {@link #createRecordReader(InputSplit, TaskAttemptContext)} and {@link #getSplits(JobContext)}, + * will call {@link #initialize(JobContext)} as a convenient centralized location to handle + * retrieving the necessary configuration information. If your subclass overrides either of these + * methods, either call the parent version or call initialize yourself. + * *

* An example of a subclass: *

- *   class ExampleTIF extends TableInputFormatBase implements JobConfigurable {
- *
- *     private JobConf job;
+ *   class ExampleTIF extends TableInputFormatBase {
  *
  *     {@literal @}Override
- *     public void configure(JobConf job) {
- *       this.job = job;
+ *     protected void initialize(JobContext context) throws IOException {
+ *       // We are responsible for the lifecycle of this connection until we hand it over in
+ *       // initializeTable.
+ *       Connection connection = ConnectionFactory.createConnection(HBaseConfiguration.create(
+ *              job.getConfiguration()));
+ *       TableName tableName = TableName.valueOf("exampleTable");
+ *       // mandatory. once passed here, TableInputFormatBase will handle closing the connection.
+ *       initializeTable(connection, tableName);
  *       byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"),
  *         Bytes.toBytes("columnB") };
  *       // optional, by default we'll get everything for the table.
@@ -85,23 +97,6 @@ import org.apache.hadoop.util.StringUtils;
  *       scan.setFilter(exampleFilter);
  *       setScan(scan);
  *     }
- *
- *     {@literal @}Override
- *     protected void initialize() {
- *       if (job == null) {
- *         throw new IllegalStateException("must have already gotten the JobConf before " +
- *             "initialize is called.");
- *       }
- *       try {
- *         Connection connection =
- *            ConnectionFactory.createConnection(HBaseConfiguration.create(job));
- *         TableName tableName = TableName.valueOf("exampleTable");
- *         // mandatory
- *         initializeTable(connection, tableName);
- *       } catch (IOException exception) {
- *         throw new RuntimeException("Failed to initialize.", exception);
- *       }
- *     }
  *   }
  * 
*/ @@ -122,6 +117,13 @@ extends InputFormat { final Log LOG = LogFactory.getLog(TableInputFormatBase.class); + private static final String NOT_INITIALIZED = "The input format instance has not been properly " + + "initialized. Ensure you call initializeTable either in your constructor or initialize " + + "method"; + private static final String INITIALIZATION_ERROR = "Cannot create a record reader because of a" + + " previous error. Please look at the previous logs lines from" + + " the task's full log for more details."; + /** Holds the details for the internal scanner. * * @see Scan */ @@ -158,14 +160,18 @@ extends InputFormat { public RecordReader createRecordReader( InputSplit split, TaskAttemptContext context) throws IOException { + // Just in case a subclass is relying on JobConfigurable magic. if (table == null) { - initialize(); + initialize(context); } - if (getTable() == null) { - // initialize() must not have been implemented in the subclass. - throw new IOException("Cannot create a record reader because of a" + - " previous error. Please look at the previous logs lines from" + - " the task's full log for more details."); + // null check in case our child overrides getTable to not throw. + try { + if (getTable() == null) { + // initialize() must not have been implemented in the subclass. + throw new IOException(INITIALIZATION_ERROR); + } + } catch (IllegalStateException exception) { + throw new IOException(INITIALIZATION_ERROR, exception); } TableSplit tSplit = (TableSplit) split; LOG.info("Input split length: " + StringUtils.humanReadableInt(tSplit.getLength()) + " bytes."); @@ -230,14 +236,20 @@ extends InputFormat { public List getSplits(JobContext context) throws IOException { boolean closeOnFinish = false; + // Just in case a subclass is relying on JobConfigurable magic. if (table == null) { - initialize(); + initialize(context); closeOnFinish = true; } - if (getTable() == null) { - // initialize() wasn't implemented, so the table is null. - throw new IOException("No table was provided."); + // null check in case our child overrides getTable to not throw. + try { + if (getTable() == null) { + // initialize() must not have been implemented in the subclass. + throw new IOException(INITIALIZATION_ERROR); + } + } catch (IllegalStateException exception) { + throw new IOException(INITIALIZATION_ERROR, exception); } try { @@ -334,6 +346,10 @@ extends InputFormat { } } + /** + * @deprecated mistakenly made public in 0.98.7. scope will change to package-private + */ + @Deprecated public String reverseDNS(InetAddress ipAddress) throws NamingException, UnknownHostException { String hostName = this.reverseDNSCacheMap.get(ipAddress); if (hostName == null) { @@ -366,7 +382,7 @@ extends InputFormat { * @see org.apache.hadoop.mapreduce.InputFormat#getSplits( * org.apache.hadoop.mapreduce.JobContext) */ - public List calculateRebalancedSplits(List list, JobContext context, + private List calculateRebalancedSplits(List list, JobContext context, long average) throws IOException { List resultList = new ArrayList(); Configuration conf = context.getConfiguration(); @@ -440,6 +456,7 @@ extends InputFormat { * @param isText It determines to use text key mode or binary key mode * @return The split point in the region. */ + @InterfaceAudience.Private public static byte[] getSplitKey(byte[] start, byte[] end, boolean isText) { byte upperLimitByte; byte lowerLimitByte; @@ -519,8 +536,6 @@ extends InputFormat { } /** - * - * * Test if the given region is to be included in the InputSplit while splitting * the regions of a table. *

@@ -547,7 +562,7 @@ extends InputFormat { /** * Allows subclasses to get the {@link HTable}. * - * @deprecated + * @deprecated use {@link #getTable()} */ @Deprecated protected HTable getHTable() { @@ -559,7 +574,7 @@ extends InputFormat { */ protected RegionLocator getRegionLocator() { if (regionLocator == null) { - initialize(); + throw new IllegalStateException(NOT_INITIALIZED); } return regionLocator; } @@ -569,7 +584,7 @@ extends InputFormat { */ protected Table getTable() { if (table == null) { - initialize(); + throw new IllegalStateException(NOT_INITIALIZED); } return table; } @@ -579,7 +594,7 @@ extends InputFormat { */ protected Admin getAdmin() { if (admin == null) { - initialize(); + throw new IllegalStateException(NOT_INITIALIZED); } return admin; } @@ -587,6 +602,9 @@ extends InputFormat { /** * Allows subclasses to set the {@link HTable}. * + * Will attempt to reuse the underlying Connection for our own needs, including + * retreiving an Admin interface to the HBase cluster. + * * @param table The table to get the data from. * @throws IOException * @deprecated Use {@link #initializeTable(Connection, TableName)} instead. @@ -623,6 +641,10 @@ extends InputFormat { * @throws IOException */ protected void initializeTable(Connection connection, TableName tableName) throws IOException { + if (table != null || connection != null) { + LOG.warn("initializeTable called multiple times. Overwriting connection and table " + + "reference; TableInputFormatBase will not close these old references when done."); + } this.table = connection.getTable(tableName); this.regionLocator = connection.getRegionLocator(tableName); this.admin = connection.getAdmin(); @@ -659,12 +681,21 @@ extends InputFormat { } /** - * This method will be called when any of the following are referenced, but not yet initialized: - * admin, regionLocator, table. Subclasses will have the opportunity to call - * {@link #initializeTable(Connection, TableName)} + * Handle subclass specific set up. + * Each of the entry points used by the MapReduce framework, + * {@link #createRecordReader(InputSplit, TaskAttemptContext)} and {@link #getSplits(JobContext)}, + * will call {@link #initialize(JobContext)} as a convenient centralized location to handle + * retrieving the necessary configuration information and calling + * {@link #initializeTable(Connection, TableName)}. + * + * Subclasses should implement their initialize call such that it is safe to call multiple times. + * The current TableInputFormatBase implementation relies on a non-null table reference to decide + * if an initialize call is needed, but this behavior may change in the future. In particular, + * it is critical that initializeTable not be called multiple times since this will leak + * Connection instances. + * */ - protected void initialize() { - + protected void initialize(JobContext context) throws IOException { } /** diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapred/TestTableInputFormat.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapred/TestTableInputFormat.java index 234a2e80170..d7dd8ec0612 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapred/TestTableInputFormat.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapred/TestTableInputFormat.java @@ -36,6 +36,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.*; import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; @@ -52,6 +54,7 @@ import org.apache.hadoop.hbase.testclassification.LargeTests; import org.apache.hadoop.hbase.testclassification.MapReduceTests; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.io.NullWritable; +import org.apache.hadoop.mapred.InputFormat; import org.apache.hadoop.mapred.JobClient; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.mapred.JobConfigurable; @@ -322,8 +325,30 @@ public class TestTableInputFormat { LOG.info("testing use of an InputFormat taht extends InputFormatBase"); final Table table = createTable(Bytes.toBytes("exampleTable"), new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") }); + testInputFormat(ExampleTIF.class); + } + + @Test + public void testDeprecatedExtensionOfTableInputFormatBase() throws IOException { + LOG.info("testing use of an InputFormat taht extends InputFormatBase, " + + "as it was given in 0.98."); + final Table table = createTable(Bytes.toBytes("exampleDeprecatedTable"), + new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") }); + testInputFormat(ExampleDeprecatedTIF.class); + } + + @Test + public void testJobConfigurableExtensionOfTableInputFormatBase() throws IOException { + LOG.info("testing use of an InputFormat taht extends InputFormatBase, " + + "using JobConfigurable."); + final Table table = createTable(Bytes.toBytes("exampleJobConfigurableTable"), + new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") }); + testInputFormat(ExampleJobConfigurableTIF.class); + } + + void testInputFormat(Class clazz) throws IOException { final JobConf job = MapreduceTestingShim.getJobConf(mrCluster); - job.setInputFormat(ExampleTIF.class); + job.setInputFormat(clazz); job.setOutputFormat(NullOutputFormat.class); job.setMapperClass(ExampleVerifier.class); job.setNumReduceTasks(0); @@ -373,13 +398,13 @@ public class TestTableInputFormat { } - public static class ExampleTIF extends TableInputFormatBase implements JobConfigurable { + public static class ExampleDeprecatedTIF extends TableInputFormatBase implements JobConfigurable { @Override public void configure(JobConf job) { try { HTable exampleTable = new HTable(HBaseConfiguration.create(job), - Bytes.toBytes("exampleTable")); + Bytes.toBytes("exampleDeprecatedTable")); // mandatory setHTable(exampleTable); byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"), @@ -396,5 +421,46 @@ public class TestTableInputFormat { } + public static class ExampleJobConfigurableTIF extends ExampleTIF implements JobConfigurable { + + @Override + public void configure(JobConf job) { + try { + initialize(job); + } catch (IOException exception) { + throw new RuntimeException("Failed to initialize.", exception); + } + } + + @Override + protected void initialize(JobConf job) throws IOException { + initialize(job, "exampleJobConfigurableTable"); + } + } + + + public static class ExampleTIF extends TableInputFormatBase { + + @Override + protected void initialize(JobConf job) throws IOException { + initialize(job, "exampleTable"); + } + + protected void initialize(JobConf job, String table) throws IOException { + Connection connection = ConnectionFactory.createConnection(HBaseConfiguration.create(job)); + TableName tableName = TableName.valueOf(table); + // mandatory + initializeTable(connection, tableName); + byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"), + Bytes.toBytes("columnB") }; + // mandatory + setInputColumns(inputColumns); + Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*")); + // optional + setRowFilter(exampleFilter); + } + + } + } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestTableInputFormat.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestTableInputFormat.java index 26029619e46..566a6429c91 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestTableInputFormat.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/mapreduce/TestTableInputFormat.java @@ -56,6 +56,7 @@ import org.apache.hadoop.mapred.JobConfigurable; import org.apache.hadoop.mapred.MiniMRCluster; import org.apache.hadoop.mapreduce.InputFormat; import org.apache.hadoop.mapreduce.Job; +import org.apache.hadoop.mapreduce.JobContext; import org.apache.hadoop.mapreduce.Mapper.Context; import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat; import org.junit.AfterClass; @@ -342,6 +343,16 @@ public class TestTableInputFormat { testInputFormat(ExampleTIF.class); } + @Test + public void testJobConfigurableExtensionOfTableInputFormatBase() + throws IOException, InterruptedException, ClassNotFoundException { + LOG.info("testing use of an InputFormat taht extends InputFormatBase, " + + "using JobConfigurable."); + final Table htable = createTable(Bytes.toBytes("exampleJobConfigurableTable"), + new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") }); + testInputFormat(ExampleJobConfigurableTIF.class); + } + @Test public void testDeprecatedExtensionOfTableInputFormatBase() throws IOException, InterruptedException, ClassNotFoundException { @@ -422,13 +433,43 @@ public class TestTableInputFormat { } - public static class ExampleTIF extends TableInputFormatBase implements JobConfigurable { - private JobConf job; + public static class ExampleJobConfigurableTIF extends TableInputFormatBase + implements JobConfigurable { @Override public void configure(JobConf job) { - this.job = job; + try { + Connection connection = ConnectionFactory.createConnection(HBaseConfiguration.create(job)); + TableName tableName = TableName.valueOf("exampleJobConfigurableTable"); + // mandatory + initializeTable(connection, tableName); + byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"), + Bytes.toBytes("columnB") }; + //optional + Scan scan = new Scan(); + for (byte[] family : inputColumns) { + scan.addFamily(family); + } + Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*")); + scan.setFilter(exampleFilter); + setScan(scan); + } catch (IOException exception) { + throw new RuntimeException("Failed to initialize.", exception); + } + } + } + + + public static class ExampleTIF extends TableInputFormatBase { + + @Override + protected void initialize(JobContext job) throws IOException { + Connection connection = ConnectionFactory.createConnection(HBaseConfiguration.create( + job.getConfiguration())); + TableName tableName = TableName.valueOf("exampleTable"); + // mandatory + initializeTable(connection, tableName); byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") }; //optional @@ -441,22 +482,6 @@ public class TestTableInputFormat { setScan(scan); } - @Override - protected void initialize() { - if (job == null) { - throw new IllegalStateException("must have already gotten the JobConf before initialize " + - "is called."); - } - try { - Connection connection = ConnectionFactory.createConnection(HBaseConfiguration.create(job)); - TableName tableName = TableName.valueOf("exampleTable"); - // mandatory - initializeTable(connection, tableName); - } catch (IOException exception) { - throw new RuntimeException("Failed to initialize.", exception); - } - } - } } From 9b2e4ed064e5a832af12616167ce9dee794cdb33 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Sun, 15 Feb 2015 11:02:40 -0800 Subject: [PATCH 021/329] HBASE-13044 Configuration option for disabling coprocessor loading --- .../src/main/resources/hbase-default.xml | 16 ++ .../hbase/coprocessor/CoprocessorHost.java | 11 ++ .../hbase/master/MasterCoprocessorHost.java | 12 +- .../regionserver/RegionCoprocessorHost.java | 8 + .../RegionServerCoprocessorHost.java | 15 +- .../TestCoprocessorConfiguration.java | 172 ++++++++++++++++++ 6 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestCoprocessorConfiguration.java diff --git a/hbase-common/src/main/resources/hbase-default.xml b/hbase-common/src/main/resources/hbase-default.xml index 6dcd80d0fa0..af6822b40eb 100644 --- a/hbase-common/src/main/resources/hbase-default.xml +++ b/hbase-common/src/main/resources/hbase-default.xml @@ -993,6 +993,22 @@ possible configurations would overwhelm and obscure the important. as part of the table details, region names, etc. When this is set to false, the keys are hidden. + + hbase.coprocessor.enabled + true + Enables or disables coprocessor loading. If 'false' + (disabled), any other coprocessor related configuration will be ignored. + + + + hbase.coprocessor.user.enabled + true + Enables or disables user (aka. table) coprocessor loading. + If 'false' (disabled), any table coprocessor attributes in table + descriptors will be ignored. If "hbase.coprocessor.enabled" is 'false' + this setting has no effect. + + hbase.coprocessor.region.classes diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java index eeb941ab268..237f617ce4b 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java @@ -73,6 +73,11 @@ public abstract class CoprocessorHost { "hbase.coprocessor.wal.classes"; public static final String ABORT_ON_ERROR_KEY = "hbase.coprocessor.abortonerror"; public static final boolean DEFAULT_ABORT_ON_ERROR = true; + public static final String COPROCESSORS_ENABLED_CONF_KEY = "hbase.coprocessor.enabled"; + public static final boolean DEFAULT_COPROCESSORS_ENABLED = true; + public static final String USER_COPROCESSORS_ENABLED_CONF_KEY = + "hbase.coprocessor.user.enabled"; + public static final boolean DEFAULT_USER_COPROCESSORS_ENABLED = true; private static final Log LOG = LogFactory.getLog(CoprocessorHost.class); protected Abortable abortable; @@ -123,6 +128,12 @@ public abstract class CoprocessorHost { * Called by constructor. */ protected void loadSystemCoprocessors(Configuration conf, String confKey) { + boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY, + DEFAULT_COPROCESSORS_ENABLED); + if (!coprocessorsEnabled) { + return; + } + Class implClass = null; // load default coprocessors from configure file diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java index 29971728010..9f003ec288d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java @@ -22,6 +22,8 @@ package org.apache.hadoop.hbase.master; import java.io.IOException; import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Coprocessor; import org.apache.hadoop.hbase.HColumnDescriptor; @@ -48,6 +50,8 @@ import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Quotas; public class MasterCoprocessorHost extends CoprocessorHost { + private static final Log LOG = LogFactory.getLog(MasterCoprocessorHost.class); + /** * Coprocessor environment extension providing access to master related * services. @@ -70,10 +74,16 @@ public class MasterCoprocessorHost private MasterServices masterServices; - MasterCoprocessorHost(final MasterServices services, final Configuration conf) { + public MasterCoprocessorHost(final MasterServices services, final Configuration conf) { super(services); this.conf = conf; this.masterServices = services; + // Log the state of coprocessor loading here; should appear only once or + // twice in the daemon log, depending on HBase version, because there is + // only one MasterCoprocessorHost instance in the master process + boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY, + DEFAULT_COPROCESSORS_ENABLED); + LOG.info("System coprocessor loading is " + (coprocessorsEnabled ? "enabled" : "disabled")); loadSystemCoprocessors(conf, MASTER_COPROCESSOR_CONF_KEY); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionCoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionCoprocessorHost.java index a32a478edb5..d10141c9af0 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionCoprocessorHost.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionCoprocessorHost.java @@ -328,6 +328,14 @@ public class RegionCoprocessorHost } void loadTableCoprocessors(final Configuration conf) { + boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY, + DEFAULT_COPROCESSORS_ENABLED); + boolean tableCoprocessorsEnabled = conf.getBoolean(USER_COPROCESSORS_ENABLED_CONF_KEY, + DEFAULT_USER_COPROCESSORS_ENABLED); + if (!(coprocessorsEnabled && tableCoprocessorsEnabled)) { + return; + } + // scan the table attributes for coprocessor load specifications // initialize the coprocessors List configured = new ArrayList(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerCoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerCoprocessorHost.java index 43a3f32757a..ab8e948b35d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerCoprocessorHost.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerCoprocessorHost.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.util.Comparator; import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; @@ -44,6 +46,8 @@ import org.apache.hadoop.hbase.replication.ReplicationEndpoint; public class RegionServerCoprocessorHost extends CoprocessorHost { + private static final Log LOG = LogFactory.getLog(RegionServerCoprocessorHost.class); + private RegionServerServices rsServices; public RegionServerCoprocessorHost(RegionServerServices rsServices, @@ -51,7 +55,16 @@ public class RegionServerCoprocessorHost extends super(rsServices); this.rsServices = rsServices; this.conf = conf; - // load system default cp's from configuration. + // Log the state of coprocessor loading here; should appear only once or + // twice in the daemon log, depending on HBase version, because there is + // only one RegionServerCoprocessorHost instance in the RS process + boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY, + DEFAULT_COPROCESSORS_ENABLED); + boolean tableCoprocessorsEnabled = conf.getBoolean(USER_COPROCESSORS_ENABLED_CONF_KEY, + DEFAULT_USER_COPROCESSORS_ENABLED); + LOG.info("System coprocessor loading is " + (coprocessorsEnabled ? "enabled" : "disabled")); + LOG.info("Table coprocessor loading is " + + ((coprocessorsEnabled && tableCoprocessorsEnabled) ? "enabled" : "disabled")); loadSystemCoprocessors(conf, REGIONSERVER_COPROCESSOR_CONF_KEY); } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestCoprocessorConfiguration.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestCoprocessorConfiguration.java new file mode 100644 index 00000000000..fb2f20cfbc8 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestCoprocessorConfiguration.java @@ -0,0 +1,172 @@ +/* + * + * 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.hbase.coprocessor; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.Coprocessor; +import org.apache.hadoop.hbase.CoprocessorEnvironment; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.master.MasterCoprocessorHost; +import org.apache.hadoop.hbase.master.MasterServices; +import org.apache.hadoop.hbase.regionserver.HRegion; +import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost; +import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost; +import org.apache.hadoop.hbase.regionserver.RegionServerServices; +import org.apache.hadoop.hbase.testclassification.CoprocessorTests; +import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +/** + * Tests for global coprocessor loading configuration + */ +@Category({CoprocessorTests.class, SmallTests.class}) +public class TestCoprocessorConfiguration { + + private static final Configuration CONF = HBaseConfiguration.create(); + static { + CONF.setStrings(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, + SystemCoprocessor.class.getName()); + CONF.setStrings(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, + SystemCoprocessor.class.getName()); + CONF.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, + SystemCoprocessor.class.getName()); + } + private static final TableName TABLENAME = TableName.valueOf("TestCoprocessorConfiguration"); + private static final HRegionInfo REGIONINFO = new HRegionInfo(TABLENAME); + private static final HTableDescriptor TABLEDESC = new HTableDescriptor(TABLENAME); + static { + try { + TABLEDESC.addCoprocessor(TableCoprocessor.class.getName()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + // use atomic types in case coprocessor loading is ever multithreaded, also + // so we can mutate them even though they are declared final here + private static final AtomicBoolean systemCoprocessorLoaded = new AtomicBoolean(); + private static final AtomicBoolean tableCoprocessorLoaded = new AtomicBoolean(); + + public static class SystemCoprocessor implements Coprocessor { + @Override + public void start(CoprocessorEnvironment env) throws IOException { + systemCoprocessorLoaded.set(true); + } + + @Override + public void stop(CoprocessorEnvironment env) throws IOException { } + } + + public static class TableCoprocessor implements Coprocessor { + @Override + public void start(CoprocessorEnvironment env) throws IOException { + tableCoprocessorLoaded.set(true); + } + + @Override + public void stop(CoprocessorEnvironment env) throws IOException { } + } + + @Test + public void testRegionCoprocessorHostDefaults() throws Exception { + Configuration conf = new Configuration(CONF); + HRegion region = mock(HRegion.class); + when(region.getRegionInfo()).thenReturn(REGIONINFO); + when(region.getTableDesc()).thenReturn(TABLEDESC); + RegionServerServices rsServices = mock(RegionServerServices.class); + systemCoprocessorLoaded.set(false); + tableCoprocessorLoaded.set(false); + new RegionCoprocessorHost(region, rsServices, conf); + assertEquals("System coprocessors loading default was not honored", + systemCoprocessorLoaded.get(), + CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED); + assertEquals("Table coprocessors loading default was not honored", + tableCoprocessorLoaded.get(), + CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED && + CoprocessorHost.DEFAULT_USER_COPROCESSORS_ENABLED); + } + + @Test + public void testRegionServerCoprocessorHostDefaults() throws Exception { + Configuration conf = new Configuration(CONF); + RegionServerServices rsServices = mock(RegionServerServices.class); + systemCoprocessorLoaded.set(false); + new RegionServerCoprocessorHost(rsServices, conf); + assertEquals("System coprocessors loading default was not honored", + systemCoprocessorLoaded.get(), + CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED); + } + + @Test + public void testMasterCoprocessorHostDefaults() throws Exception { + Configuration conf = new Configuration(CONF); + MasterServices masterServices = mock(MasterServices.class); + systemCoprocessorLoaded.set(false); + new MasterCoprocessorHost(masterServices, conf); + assertEquals("System coprocessors loading default was not honored", + systemCoprocessorLoaded.get(), + CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED); + } + + @Test + public void testRegionCoprocessorHostAllDisabled() throws Exception { + Configuration conf = new Configuration(CONF); + conf.setBoolean(CoprocessorHost.COPROCESSORS_ENABLED_CONF_KEY, false); + HRegion region = mock(HRegion.class); + when(region.getRegionInfo()).thenReturn(REGIONINFO); + when(region.getTableDesc()).thenReturn(TABLEDESC); + RegionServerServices rsServices = mock(RegionServerServices.class); + systemCoprocessorLoaded.set(false); + tableCoprocessorLoaded.set(false); + new RegionCoprocessorHost(region, rsServices, conf); + assertFalse("System coprocessors should not have been loaded", + systemCoprocessorLoaded.get()); + assertFalse("Table coprocessors should not have been loaded", + tableCoprocessorLoaded.get()); + } + + @Test + public void testRegionCoprocessorHostTableLoadingDisabled() throws Exception { + Configuration conf = new Configuration(CONF); + conf.setBoolean(CoprocessorHost.COPROCESSORS_ENABLED_CONF_KEY, true); // if defaults change + conf.setBoolean(CoprocessorHost.USER_COPROCESSORS_ENABLED_CONF_KEY, false); + HRegion region = mock(HRegion.class); + when(region.getRegionInfo()).thenReturn(REGIONINFO); + when(region.getTableDesc()).thenReturn(TABLEDESC); + RegionServerServices rsServices = mock(RegionServerServices.class); + systemCoprocessorLoaded.set(false); + tableCoprocessorLoaded.set(false); + new RegionCoprocessorHost(region, rsServices, conf); + assertTrue("System coprocessors should have been loaded", + systemCoprocessorLoaded.get()); + assertFalse("Table coprocessors should not have been loaded", + tableCoprocessorLoaded.get()); + } +} From 6d72a993eee492d69bcbe3d6756f765afe8d8007 Mon Sep 17 00:00:00 2001 From: Bhupendra Date: Mon, 16 Feb 2015 18:51:14 +0530 Subject: [PATCH 022/329] HBASE-13049 wal_roll ruby command doesn't work Signed-off-by: Matteo Bertozzi --- hbase-shell/src/main/ruby/hbase/admin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb index 35ee36cd760..445a3903f1d 100644 --- a/hbase-shell/src/main/ruby/hbase/admin.rb +++ b/hbase-shell/src/main/ruby/hbase/admin.rb @@ -84,7 +84,7 @@ module Hbase #---------------------------------------------------------------------------------------------- # Requests a regionserver's WAL roll def wal_roll(server_name) - @admin.rollWALWriter(server_name) + @admin.rollWALWriter(ServerName.valueOf(server_name)) end # TODO remove older hlog_roll version alias :hlog_roll :wal_roll From e99091e97c374a026ba690cf9d6a4592f3c3fbdd Mon Sep 17 00:00:00 2001 From: zhangduo Date: Sat, 14 Feb 2015 08:36:38 +0800 Subject: [PATCH 023/329] HBASE-13011 TestLoadIncrementalHFiles is flakey when using AsyncRpcClient as client implementation Added comment to AsyncRpcChannel data members Signed-off-by: stack --- .../apache/hadoop/hbase/ipc/AsyncCall.java | 9 +- .../hadoop/hbase/ipc/AsyncRpcChannel.java | 300 ++++++++---------- .../hadoop/hbase/ipc/AsyncRpcClient.java | 59 ++-- .../hbase/ipc/AsyncServerResponseHandler.java | 16 +- 4 files changed, 180 insertions(+), 204 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncCall.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncCall.java index c35238ce9f6..68a494d7689 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncCall.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncCall.java @@ -72,7 +72,7 @@ public class AsyncCall extends DefaultPromise { this.responseDefaultType = responseDefaultType; this.startTime = EnvironmentEdgeManager.currentTime(); - this.rpcTimeout = controller.getCallTimeout(); + this.rpcTimeout = controller.hasCallTimeout() ? controller.getCallTimeout() : 0; } /** @@ -84,9 +84,10 @@ public class AsyncCall extends DefaultPromise { return this.startTime; } - @Override public String toString() { - return "callId: " + this.id + " methodName: " + this.method.getName() + " param {" + - (this.param != null ? ProtobufUtil.getShortTextFormat(this.param) : "") + "}"; + @Override + public String toString() { + return "callId: " + this.id + " methodName: " + this.method.getName() + " param {" + + (this.param != null ? ProtobufUtil.getShortTextFormat(this.param) : "") + "}"; } /** diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcChannel.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcChannel.java index 054c9b5690b..ffb2dcfbc6d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcChannel.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcChannel.java @@ -17,9 +17,6 @@ */ package org.apache.hadoop.hbase.ipc; -import com.google.protobuf.Descriptors; -import com.google.protobuf.Message; -import com.google.protobuf.RpcCallback; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufOutputStream; @@ -31,6 +28,23 @@ import io.netty.util.Timeout; import io.netty.util.TimerTask; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import javax.security.sasl.SaslException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HConstants; @@ -56,18 +70,9 @@ import org.apache.hadoop.security.token.TokenSelector; import org.apache.htrace.Span; import org.apache.htrace.Trace; -import javax.security.sasl.SaslException; -import java.io.IOException; -import java.net.ConnectException; -import java.net.InetSocketAddress; -import java.net.SocketException; -import java.nio.ByteBuffer; -import java.security.PrivilegedExceptionAction; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.concurrent.TimeUnit; +import com.google.protobuf.Descriptors; +import com.google.protobuf.Message; +import com.google.protobuf.RpcCallback; /** * Netty RPC channel @@ -97,8 +102,6 @@ public class AsyncRpcChannel { final String serviceName; final InetSocketAddress address; - ConcurrentSkipListMap calls = new ConcurrentSkipListMap<>(); - private int ioFailureCounter = 0; private int connectFailureCounter = 0; @@ -108,15 +111,18 @@ public class AsyncRpcChannel { private Token token; private String serverPrincipal; - volatile boolean shouldCloseConnection = false; - private IOException closeException; + + // NOTE: closed and connected flags below are only changed when a lock on pendingCalls + private final Map pendingCalls = new HashMap(); + private boolean connected = false; + private boolean closed = false; private Timeout cleanupTimer; private final TimerTask timeoutTask = new TimerTask() { - @Override public void run(Timeout timeout) throws Exception { - cleanupTimer = null; - cleanupCalls(false); + @Override + public void run(Timeout timeout) throws Exception { + cleanupCalls(); } }; @@ -213,15 +219,20 @@ public class AsyncRpcChannel { ch.pipeline() .addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)); ch.pipeline().addLast(new AsyncServerResponseHandler(this)); - try { writeChannelHeader(ch).addListener(new GenericFutureListener() { - @Override public void operationComplete(ChannelFuture future) throws Exception { + @Override + public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { close(future.cause()); return; } - for (AsyncCall call : calls.values()) { + List callsToWrite; + synchronized (pendingCalls) { + connected = true; + callsToWrite = new ArrayList(pendingCalls.values()); + } + for (AsyncCall call : callsToWrite) { writeRequest(call); } } @@ -240,17 +251,18 @@ public class AsyncRpcChannel { */ private SaslClientHandler getSaslHandler(final Bootstrap bootstrap) throws IOException { return new SaslClientHandler(authMethod, token, serverPrincipal, client.fallbackAllowed, - client.conf.get("hbase.rpc.protection", - SaslUtil.QualityOfProtection.AUTHENTICATION.name().toLowerCase()), - new SaslClientHandler.SaslExceptionHandler() { - @Override public void handle(int retryCount, Random random, Throwable cause) { + client.conf.get("hbase.rpc.protection", SaslUtil.QualityOfProtection.AUTHENTICATION.name() + .toLowerCase()), new SaslClientHandler.SaslExceptionHandler() { + @Override + public void handle(int retryCount, Random random, Throwable cause) { try { // Handle Sasl failure. Try to potentially get new credentials handleSaslConnectionFailure(retryCount, cause, ticket.getUGI()); // Try to reconnect AsyncRpcClient.WHEEL_TIMER.newTimeout(new TimerTask() { - @Override public void run(Timeout timeout) throws Exception { + @Override + public void run(Timeout timeout) throws Exception { connect(bootstrap); } }, random.nextInt(reloginMaxBackoff) + 1, TimeUnit.MILLISECONDS); @@ -259,10 +271,11 @@ public class AsyncRpcChannel { } } }, new SaslClientHandler.SaslSuccessfulConnectHandler() { - @Override public void onSuccess(Channel channel) { - startHBaseConnection(channel); - } - }); + @Override + public void onSuccess(Channel channel) { + startHBaseConnection(channel); + } + }); } /** @@ -295,66 +308,50 @@ public class AsyncRpcChannel { public Promise callMethod(final Descriptors.MethodDescriptor method, final PayloadCarryingRpcController controller, final Message request, final Message responsePrototype) { - if (shouldCloseConnection) { - Promise promise = channel.eventLoop().newPromise(); - promise.setFailure(new ConnectException()); - return promise; - } - - final AsyncCall call = new AsyncCall(channel.eventLoop(), client.callIdCnt.getAndIncrement(), - method, request, controller, responsePrototype); - + final AsyncCall call = + new AsyncCall(channel.eventLoop(), client.callIdCnt.getAndIncrement(), method, request, + controller, responsePrototype); controller.notifyOnCancel(new RpcCallback() { @Override public void run(Object parameter) { - calls.remove(call.id); + // TODO: do not need to call AsyncCall.setFailed? + synchronized (pendingCalls) { + pendingCalls.remove(call.id); + } } }); + // TODO: this should be handled by PayloadCarryingRpcController. if (controller.isCanceled()) { // To finish if the call was cancelled before we set the notification (race condition) call.cancel(true); return call; } - calls.put(call.id, call); - - // check again, see https://issues.apache.org/jira/browse/HBASE-12951 - if (shouldCloseConnection) { - Promise promise = channel.eventLoop().newPromise(); - promise.setFailure(new ConnectException()); - return promise; + synchronized (pendingCalls) { + if (closed) { + Promise promise = channel.eventLoop().newPromise(); + promise.setFailure(new ConnectException()); + return promise; + } + pendingCalls.put(call.id, call); + // Add timeout for cleanup if none is present + if (cleanupTimer == null && call.getRpcTimeout() > 0) { + cleanupTimer = + AsyncRpcClient.WHEEL_TIMER.newTimeout(timeoutTask, call.getRpcTimeout(), + TimeUnit.MILLISECONDS); + } + if (!connected) { + return call; + } } - - // Add timeout for cleanup if none is present - if (cleanupTimer == null) { - cleanupTimer = AsyncRpcClient.WHEEL_TIMER.newTimeout(timeoutTask, call.getRpcTimeout(), - TimeUnit.MILLISECONDS); - } - - if(channel.isActive()) { - writeRequest(call); - } - + writeRequest(call); return call; } - /** - * Calls method and returns a promise - * @param method to call - * @param controller to run call with - * @param request to send - * @param responsePrototype for response message - * @return Promise to listen to result - * @throws java.net.ConnectException on connection failures - */ - public Promise callMethodWithPromise( - final Descriptors.MethodDescriptor method, final PayloadCarryingRpcController controller, - final Message request, final Message responsePrototype) throws ConnectException { - if (shouldCloseConnection || !channel.isOpen()) { - throw new ConnectException(); + AsyncCall removePendingCall(int id) { + synchronized (pendingCalls) { + return pendingCalls.remove(id); } - - return this.callMethod(method, controller, request, responsePrototype); } /** @@ -400,10 +397,6 @@ public class AsyncRpcChannel { */ private void writeRequest(final AsyncCall call) { try { - if (shouldCloseConnection) { - return; - } - final RPCProtos.RequestHeader.Builder requestHeaderBuilder = RPCProtos.RequestHeader .newBuilder(); requestHeaderBuilder.setCallId(call.id) @@ -439,25 +432,12 @@ public class AsyncRpcChannel { IPCUtil.write(out, rh, call.param, cellBlock); } - channel.writeAndFlush(b).addListener(new CallWriteListener(this,call)); + channel.writeAndFlush(b).addListener(new CallWriteListener(this, call.id)); } catch (IOException e) { - if (!shouldCloseConnection) { - close(e); - } + close(e); } } - /** - * Fail a call - * - * @param call to fail - * @param cause of fail - */ - void failCall(AsyncCall call, IOException cause) { - calls.remove(call.id); - call.setFailed(cause); - } - /** * Set up server authorization * @@ -550,18 +530,22 @@ public class AsyncRpcChannel { * @param e exception on close */ public void close(final Throwable e) { - client.removeConnection(ConnectionId.hashCode(ticket,serviceName,address)); + client.removeConnection(this); // Move closing from the requesting thread to the channel thread channel.eventLoop().execute(new Runnable() { @Override public void run() { - if (shouldCloseConnection) { - return; + List toCleanup; + synchronized (pendingCalls) { + if (closed) { + return; + } + closed = true; + toCleanup = new ArrayList(pendingCalls.values()); + pendingCalls.clear(); } - - shouldCloseConnection = true; - + IOException closeException = null; if (e != null) { if (e instanceof IOException) { closeException = (IOException) e; @@ -569,16 +553,19 @@ public class AsyncRpcChannel { closeException = new IOException(e); } } - // log the info if (LOG.isDebugEnabled() && closeException != null) { - LOG.debug(name + ": closing ipc connection to " + address + ": " + - closeException.getMessage()); + LOG.debug(name + ": closing ipc connection to " + address, closeException); + } + if (cleanupTimer != null) { + cleanupTimer.cancel(); + cleanupTimer = null; + } + for (AsyncCall call : toCleanup) { + call.setFailed(closeException != null ? closeException : new ConnectionClosingException( + "Call id=" + call.id + " on server " + address + " aborted: connection is closing")); } - - cleanupCalls(true); channel.disconnect().addListener(ChannelFutureListener.CLOSE); - if (LOG.isDebugEnabled()) { LOG.debug(name + ": closed"); } @@ -591,64 +578,37 @@ public class AsyncRpcChannel { * * @param cleanAll true if all calls should be cleaned, false for only the timed out calls */ - public void cleanupCalls(boolean cleanAll) { - // Cancel outstanding timers - if (cleanupTimer != null) { - cleanupTimer.cancel(); - cleanupTimer = null; - } - - if (cleanAll) { - for (AsyncCall call : calls.values()) { - synchronized (call) { - // Calls can be done on another thread so check before failing them - if(!call.isDone()) { - if (closeException == null) { - failCall(call, new ConnectionClosingException("Call id=" + call.id + - " on server " + address + " aborted: connection is closing")); - } else { - failCall(call, closeException); - } - } - } - } - } else { - for (AsyncCall call : calls.values()) { - long waitTime = EnvironmentEdgeManager.currentTime() - call.getStartTime(); + private void cleanupCalls() { + List toCleanup = new ArrayList(); + long currentTime = EnvironmentEdgeManager.currentTime(); + long nextCleanupTaskDelay = -1L; + synchronized (pendingCalls) { + for (Iterator iter = pendingCalls.values().iterator(); iter.hasNext();) { + AsyncCall call = iter.next(); long timeout = call.getRpcTimeout(); - if (timeout > 0 && waitTime >= timeout) { - synchronized (call) { - // Calls can be done on another thread so check before failing them - if (!call.isDone()) { - closeException = new CallTimeoutException("Call id=" + call.id + - ", waitTime=" + waitTime + ", rpcTimeout=" + timeout); - failCall(call, closeException); + if (timeout > 0) { + if (currentTime - call.getStartTime() >= timeout) { + iter.remove(); + toCleanup.add(call); + } else { + if (nextCleanupTaskDelay < 0 || timeout < nextCleanupTaskDelay) { + nextCleanupTaskDelay = timeout; } } - } else { - // We expect the call to be ordered by timeout. It may not be the case, but stopping - // at the first valid call allows to be sure that we still have something to do without - // spending too much time by reading the full list. - break; } } - - if (!calls.isEmpty()) { - AsyncCall firstCall = calls.firstEntry().getValue(); - - final long newTimeout; - long maxWaitTime = EnvironmentEdgeManager.currentTime() - firstCall.getStartTime(); - if (maxWaitTime < firstCall.getRpcTimeout()) { - newTimeout = firstCall.getRpcTimeout() - maxWaitTime; - } else { - newTimeout = 0; - } - - closeException = null; - cleanupTimer = AsyncRpcClient.WHEEL_TIMER.newTimeout(timeoutTask, - newTimeout, TimeUnit.MILLISECONDS); + if (nextCleanupTaskDelay > 0) { + cleanupTimer = + AsyncRpcClient.WHEEL_TIMER.newTimeout(timeoutTask, nextCleanupTaskDelay, + TimeUnit.MILLISECONDS); + } else { + cleanupTimer = null; } } + for (AsyncCall call : toCleanup) { + call.setFailed(new CallTimeoutException("Call id=" + call.id + ", waitTime=" + + (currentTime - call.getRpcTimeout()) + ", rpcTimeout=" + call.getRpcTimeout())); + } } /** @@ -745,6 +705,10 @@ public class AsyncRpcChannel { }); } + public int getConnectionHashCode() { + return ConnectionId.hashCode(ticket, serviceName, address); + } + @Override public String toString() { return this.address.toString() + "/" + this.serviceName + "/" + this.ticket; @@ -755,20 +719,22 @@ public class AsyncRpcChannel { */ private static final class CallWriteListener implements ChannelFutureListener { private final AsyncRpcChannel rpcChannel; - private final AsyncCall call; + private final int id; - public CallWriteListener(AsyncRpcChannel asyncRpcChannel, AsyncCall call) { + public CallWriteListener(AsyncRpcChannel asyncRpcChannel, int id) { this.rpcChannel = asyncRpcChannel; - this.call = call; + this.id = id; } - @Override public void operationComplete(ChannelFuture future) throws Exception { + @Override + public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { - if(!this.call.isDone()) { + AsyncCall call = rpcChannel.removePendingCall(id); + if (call != null) { if (future.cause() instanceof IOException) { - rpcChannel.failCall(call, (IOException) future.cause()); + call.setFailed((IOException) future.cause()); } else { - rpcChannel.failCall(call, new IOException(future.cause())); + call.setFailed(new IOException(future.cause())); } } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcClient.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcClient.java index 30b622ad906..192e583946a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcClient.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcClient.java @@ -17,12 +17,6 @@ */ package org.apache.hadoop.hbase.ipc; -import com.google.common.annotations.VisibleForTesting; -import com.google.protobuf.Descriptors; -import com.google.protobuf.Message; -import com.google.protobuf.RpcCallback; -import com.google.protobuf.RpcChannel; -import com.google.protobuf.RpcController; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.Channel; @@ -38,6 +32,16 @@ import io.netty.util.HashedWheelTimer; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.CellScanner; import org.apache.hadoop.hbase.HConstants; @@ -49,13 +53,12 @@ import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.hbase.util.PoolMap; import org.apache.hadoop.hbase.util.Threads; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import com.google.common.annotations.VisibleForTesting; +import com.google.protobuf.Descriptors; +import com.google.protobuf.Message; +import com.google.protobuf.RpcCallback; +import com.google.protobuf.RpcChannel; +import com.google.protobuf.RpcController; /** * Netty client for the requests and responses @@ -169,16 +172,16 @@ public class AsyncRpcClient extends AbstractRpcClient { * @throws InterruptedException if call is interrupted * @throws java.io.IOException if a connection failure is encountered */ - @Override protected Pair call(PayloadCarryingRpcController pcrc, + @Override + protected Pair call(PayloadCarryingRpcController pcrc, Descriptors.MethodDescriptor md, Message param, Message returnType, User ticket, InetSocketAddress addr) throws IOException, InterruptedException { - final AsyncRpcChannel connection = createRpcChannel(md.getService().getName(), addr, ticket); - Promise promise = connection.callMethodWithPromise(md, pcrc, param, returnType); - + Promise promise = connection.callMethod(md, pcrc, param, returnType); + long timeout = pcrc.hasCallTimeout() ? pcrc.getCallTimeout() : 0; try { - Message response = promise.get(); + Message response = timeout > 0 ? promise.get(timeout, TimeUnit.MILLISECONDS) : promise.get(); return new Pair<>(response, pcrc.cellScanner()); } catch (ExecutionException e) { if (e.getCause() instanceof IOException) { @@ -186,6 +189,8 @@ public class AsyncRpcClient extends AbstractRpcClient { } else { throw new IOException(e.getCause()); } + } catch (TimeoutException e) { + throw new CallTimeoutException(promise.toString()); } } @@ -337,12 +342,20 @@ public class AsyncRpcClient extends AbstractRpcClient { /** * Remove connection from pool - * - * @param connectionHashCode of connection */ - public void removeConnection(int connectionHashCode) { + public void removeConnection(AsyncRpcChannel connection) { + int connectionHashCode = connection.getConnectionHashCode(); synchronized (connections) { - this.connections.remove(connectionHashCode); + // we use address as cache key, so we should check here to prevent removing the + // wrong connection + AsyncRpcChannel connectionInPool = this.connections.get(connectionHashCode); + if (connectionInPool == connection) { + this.connections.remove(connectionHashCode); + } else if (LOG.isDebugEnabled()) { + LOG.debug(String.format("%s already removed, expected instance %08x, actual %08x", + connection.toString(), System.identityHashCode(connection), + System.identityHashCode(connectionInPool))); + } } } @@ -399,4 +412,4 @@ public class AsyncRpcClient extends AbstractRpcClient { this.rpcClient.callMethod(md, pcrc, param, returnType, this.ticket, this.isa, done); } } -} \ No newline at end of file +} diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncServerResponseHandler.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncServerResponseHandler.java index d71bf5ec54a..a900140c25d 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncServerResponseHandler.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncServerResponseHandler.java @@ -17,11 +17,13 @@ */ package org.apache.hadoop.hbase.ipc; -import com.google.protobuf.Message; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; + +import java.io.IOException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.CellScanner; @@ -29,7 +31,7 @@ import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.protobuf.generated.RPCProtos; import org.apache.hadoop.ipc.RemoteException; -import java.io.IOException; +import com.google.protobuf.Message; /** * Handles Hbase responses @@ -52,16 +54,12 @@ public class AsyncServerResponseHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf inBuffer = (ByteBuf) msg; ByteBufInputStream in = new ByteBufInputStream(inBuffer); - - if (channel.shouldCloseConnection) { - return; - } int totalSize = inBuffer.readableBytes(); try { // Read the header RPCProtos.ResponseHeader responseHeader = RPCProtos.ResponseHeader.parseDelimitedFrom(in); int id = responseHeader.getCallId(); - AsyncCall call = channel.calls.get(id); + AsyncCall call = channel.removePendingCall(id); if (call == null) { // So we got a response for which we have no corresponding 'call' here on the client-side. // We probably timed out waiting, cleaned up all references, and now the server decides @@ -85,7 +83,7 @@ public class AsyncServerResponseHandler extends ChannelInboundHandlerAdapter { equals(FatalConnectionException.class.getName())) { channel.close(re); } else { - channel.failCall(call, re); + call.setFailed(re); } } else { Message value = null; @@ -104,13 +102,11 @@ public class AsyncServerResponseHandler extends ChannelInboundHandlerAdapter { } call.setSuccess(value, cellBlockScanner); } - channel.calls.remove(id); } catch (IOException e) { // Treat this as a fatal condition and close this connection channel.close(e); } finally { inBuffer.release(); - channel.cleanupCalls(false); } } From 3e35bbdee464ec11fc83f912586505514fc8c04a Mon Sep 17 00:00:00 2001 From: tedyu Date: Mon, 16 Feb 2015 17:26:06 -0800 Subject: [PATCH 024/329] HBASE-13055 HRegion FIXED_OVERHEAD missed one boolean (Duo Zhang) --- .../main/java/org/apache/hadoop/hbase/regionserver/HRegion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java index aa65ddd6109..510933cd104 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java @@ -6734,7 +6734,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // ClassSize.ARRAY + 45 * ClassSize.REFERENCE + 2 * Bytes.SIZEOF_INT + (12 * Bytes.SIZEOF_LONG) + - 4 * Bytes.SIZEOF_BOOLEAN); + 5 * Bytes.SIZEOF_BOOLEAN); // woefully out of date - currently missing: // 1 x HashMap - coprocessorServiceHandlers From 0d880c99b59eb6e62b3db99154a0b53bd371aef8 Mon Sep 17 00:00:00 2001 From: stack Date: Mon, 16 Feb 2015 20:49:02 -0800 Subject: [PATCH 025/329] HBASE-13047 Add "HBase Configuration" link missing on the table details pages (Vikas Vishwakarma) --- hbase-server/src/main/resources/hbase-webapps/master/table.jsp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hbase-server/src/main/resources/hbase-webapps/master/table.jsp b/hbase-server/src/main/resources/hbase-webapps/master/table.jsp index 58e5da44d70..db48b6c79aa 100644 --- a/hbase-server/src/main/resources/hbase-webapps/master/table.jsp +++ b/hbase-server/src/main/resources/hbase-webapps/master/table.jsp @@ -187,6 +187,9 @@
  • Log Level
  • Debug Dump
  • Metrics Dump
  • + <% if (HBaseConfiguration.isShowConfInServlet()) { %> +
  • HBase Configuration
  • + <% } %> From d3621d24ded796dc0f50ca4cccfec400837a9e51 Mon Sep 17 00:00:00 2001 From: stack Date: Mon, 16 Feb 2015 21:00:00 -0800 Subject: [PATCH 026/329] HBASE-13041 TestEnableTableHandler should not call AssignmentManager#assign concurrently with master (Andrey Stepachev) --- .../handler/TestEnableTableHandler.java | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/handler/TestEnableTableHandler.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/handler/TestEnableTableHandler.java index d3d62394485..6a60fc0fd86 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/handler/TestEnableTableHandler.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/handler/TestEnableTableHandler.java @@ -18,8 +18,13 @@ */ package org.apache.hadoop.hbase.master.handler; +import java.util.ArrayList; import java.util.List; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HConstants; @@ -81,7 +86,6 @@ public class TestEnableTableHandler { admin.enableTable(tableName); TEST_UTIL.waitTableEnabled(tableName); - // disable once more admin.disableTable(tableName); @@ -91,21 +95,36 @@ public class TestEnableTableHandler { rs.getRegionServer().stop("stop"); cluster.waitForRegionServerToStop(rs.getRegionServer().getServerName(), 10000); - EnableTableHandler handler = - new EnableTableHandler(m, tableName, m.getAssignmentManager(), m.getTableLockManager(), - true); - handler.prepare(); - handler.process(); + LOG.debug("Now enabling table " + tableName); + admin.enableTable(tableName); assertTrue(admin.isTableEnabled(tableName)); JVMClusterUtil.RegionServerThread rs2 = cluster.startRegionServer(); - m.getAssignmentManager().assign(admin.getTableRegions(tableName)); + cluster.waitForRegionServerToStart(rs2.getRegionServer().getServerName().getHostname(), + rs2.getRegionServer().getServerName().getPort(), 60000); + + List regions = TEST_UTIL.getHBaseAdmin().getTableRegions(tableName); + assertEquals(1, regions.size()); + for (HRegionInfo region : regions) { + TEST_UTIL.getHBaseAdmin().assign(region.getEncodedNameAsBytes()); + } + LOG.debug("Waiting for table assigned " + tableName); TEST_UTIL.waitUntilAllRegionsAssigned(tableName); List onlineRegions = admin.getOnlineRegions( rs2.getRegionServer().getServerName()); - assertEquals(1, onlineRegions.size()); - assertEquals(tableName, onlineRegions.get(0).getTable()); + ArrayList tableRegions = filterTableRegions(tableName, onlineRegions); + assertEquals(1, tableRegions.size()); + } + + private ArrayList filterTableRegions(final TableName tableName, + List onlineRegions) { + return Lists.newArrayList(Iterables.filter(onlineRegions, new Predicate() { + @Override + public boolean apply(HRegionInfo input) { + return input.getTable().equals(tableName); + } + })); } /** From bfc2bc7fa2101eafa8fd63a86223e1a15f74d434 Mon Sep 17 00:00:00 2001 From: Misty Stanley-Jones Date: Tue, 10 Feb 2015 14:38:41 +1000 Subject: [PATCH 027/329] HBASE-12412 update the ref guide example 2 in HBase APIs chapter with the new API modifyFamily in master --- src/main/asciidoc/_chapters/hbase_apis.adoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/asciidoc/_chapters/hbase_apis.adoc b/src/main/asciidoc/_chapters/hbase_apis.adoc index d73de619a90..85dbad14181 100644 --- a/src/main/asciidoc/_chapters/hbase_apis.adoc +++ b/src/main/asciidoc/_chapters/hbase_apis.adoc @@ -111,6 +111,13 @@ public static void upgradeFrom0 (Configuration config) { newColumn.setMaxVersions(HConstants.ALL_VERSIONS); admin.addColumn(tableName, newColumn); + // Update existing column family + HColumnDescriptor existingColumn = new HColumnDescriptor(CF_DEFAULT); + existingColumn.setCompactionCompressionType(Algorithm.GZ); + existingColumn.setMaxVersions(HConstants.ALL_VERSIONS); + table_assetmeta.modifyFamily(existingColumn) + admin.modifyTable(tableName, table_assetmeta); + // Disable an existing table admin.disableTable(tableName); From ca25a6a870e7a31fa2fd68445cb753aa34edb0a0 Mon Sep 17 00:00:00 2001 From: Dima Spivak Date: Thu, 29 Jan 2015 23:21:31 -0600 Subject: [PATCH 028/329] HBASE-12869 Add a REST API implementation of the ClusterManager interface Signed-off-by: stack --- hbase-it/pom.xml | 5 + .../hadoop/hbase/RESTApiClusterManager.java | 350 ++++++++++++++++++ 2 files changed, 355 insertions(+) create mode 100644 hbase-it/src/test/java/org/apache/hadoop/hbase/RESTApiClusterManager.java diff --git a/hbase-it/pom.xml b/hbase-it/pom.xml index 4522f9c5872..4b9f85058d7 100644 --- a/hbase-it/pom.xml +++ b/hbase-it/pom.xml @@ -202,6 +202,11 @@ com.google.guava guava + + com.sun.jersey + jersey-client + ${jersey.version} + com.yammer.metrics metrics-core diff --git a/hbase-it/src/test/java/org/apache/hadoop/hbase/RESTApiClusterManager.java b/hbase-it/src/test/java/org/apache/hadoop/hbase/RESTApiClusterManager.java new file mode 100644 index 00000000000..28fac4ee2bf --- /dev/null +++ b/hbase-it/src/test/java/org/apache/hadoop/hbase/RESTApiClusterManager.java @@ -0,0 +1,350 @@ +/** + * 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.hbase; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.conf.Configured; +import org.apache.hadoop.util.ReflectionUtils; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.map.ObjectMapper; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.xml.ws.http.HTTPException; +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +/** + * A ClusterManager implementation designed to control Cloudera Manager (http://www.cloudera.com) + * clusters via REST API. This API uses HTTP GET requests against the cluster manager server to + * retrieve information and POST/PUT requests to perform actions. As a simple example, to retrieve a + * list of hosts from a CM server with login credentials admin:admin, a simple curl command would be + * curl -X POST -H "Content-Type:application/json" -u admin:admin \ + * "http://this.is.my.server.com:7180/api/v8/hosts" + * + * This command would return a JSON result, which would need to be parsed to retrieve relevant + * information. This action and many others are covered by this class. + * + * A note on nomenclature: while the ClusterManager interface uses a ServiceType enum when + * referring to things like RegionServers and DataNodes, cluster managers often use different + * terminology. As an example, Cloudera Manager (http://www.cloudera.com) would refer to a + * RegionServer as a "role" of the HBase "service." It would further refer to "hbase" as the + * "serviceType." Apache Ambari (http://ambari.apache.org) would call the RegionServer a + * "component" of the HBase "service." + * + * This class will defer to the ClusterManager terminology in methods that it implements from + * that interface, but uses Cloudera Manager's terminology when dealing with its API directly. + */ +public class RESTApiClusterManager extends Configured implements ClusterManager { + // Properties that need to be in the Configuration object to interact with the REST API cluster + // manager. Most easily defined in hbase-site.xml, but can also be passed on the command line. + private static final String REST_API_CLUSTER_MANAGER_HOSTNAME = + "hbase.it.clustermanager.restapi.hostname"; + private static final String REST_API_CLUSTER_MANAGER_USERNAME = + "hbase.it.clustermanager.restapi.username"; + private static final String REST_API_CLUSTER_MANAGER_PASSWORD = + "hbase.it.clustermanager.restapi.password"; + private static final String REST_API_CLUSTER_MANAGER_CLUSTER_NAME = + "hbase.it.clustermanager.restapi.clustername"; + + // Some default values for the above properties. + private static final String DEFAULT_SERVER_HOSTNAME = "http://localhost:7180"; + private static final String DEFAULT_SERVER_USERNAME = "admin"; + private static final String DEFAULT_SERVER_PASSWORD = "admin"; + private static final String DEFAULT_CLUSTER_NAME = "Cluster 1"; + + // Fields for the hostname, username, password, and cluster name of the cluster management server + // to be used. + private String serverHostname; + private String serverUsername; + private String serverPassword; + private String clusterName; + + // Each version of Cloudera Manager supports a particular API versions. Version 6 of this API + // provides all the features needed by this class. + private static final String API_VERSION = "v6"; + + // Client instances are expensive, so use the same one for all our REST queries. + private Client client = Client.create(); + + // An instance of HBaseClusterManager is used for methods like the kill, resume, and suspend + // because cluster managers don't tend to implement these operations. + private ClusterManager hBaseClusterManager; + + private static final Log LOG = LogFactory.getLog(RESTApiClusterManager.class); + + RESTApiClusterManager() { + hBaseClusterManager = ReflectionUtils.newInstance(HBaseClusterManager.class, + new IntegrationTestingUtility().getConfiguration()); + } + + @Override + public void setConf(Configuration conf) { + super.setConf(conf); + if (conf == null) { + // Configured gets passed null before real conf. Why? I don't know. + return; + } + serverHostname = conf.get(REST_API_CLUSTER_MANAGER_HOSTNAME, DEFAULT_SERVER_HOSTNAME); + serverUsername = conf.get(REST_API_CLUSTER_MANAGER_USERNAME, DEFAULT_SERVER_USERNAME); + serverPassword = conf.get(REST_API_CLUSTER_MANAGER_PASSWORD, DEFAULT_SERVER_PASSWORD); + clusterName = conf.get(REST_API_CLUSTER_MANAGER_CLUSTER_NAME, DEFAULT_CLUSTER_NAME); + + // Add filter to Client instance to enable server authentication. + client.addFilter(new HTTPBasicAuthFilter(serverUsername, serverPassword)); + } + + @Override + public void start(ServiceType service, String hostname, int port) throws IOException { + performClusterManagerCommand(service, hostname, RoleCommand.START); + } + + @Override + public void stop(ServiceType service, String hostname, int port) throws IOException { + performClusterManagerCommand(service, hostname, RoleCommand.STOP); + } + + @Override + public void restart(ServiceType service, String hostname, int port) throws IOException { + performClusterManagerCommand(service, hostname, RoleCommand.RESTART); + } + + @Override + public boolean isRunning(ServiceType service, String hostname, int port) throws IOException { + String serviceName = getServiceName(roleServiceType.get(service)); + String hostId = getHostId(hostname); + String roleState = getRoleState(serviceName, service.toString(), hostId); + String healthSummary = getHealthSummary(serviceName, service.toString(), hostId); + boolean isRunning = false; + + // Use Yoda condition to prevent NullPointerException. roleState will be null if the "service + // type" does not exist on the specified hostname. + if ("STARTED".equals(roleState) && "GOOD".equals(healthSummary)) { + isRunning = true; + } + + return isRunning; + } + + @Override + public void kill(ServiceType service, String hostname, int port) throws IOException { + hBaseClusterManager.kill(service, hostname, port); + } + + @Override + public void suspend(ServiceType service, String hostname, int port) throws IOException { + hBaseClusterManager.kill(service, hostname, port); + } + + @Override + public void resume(ServiceType service, String hostname, int port) throws IOException { + hBaseClusterManager.kill(service, hostname, port); + } + + + // Convenience method to execute command against role on hostname. Only graceful commands are + // supported since cluster management APIs don't tend to let you SIGKILL things. + private void performClusterManagerCommand(ServiceType role, String hostname, RoleCommand command) + throws IOException { + LOG.info("Performing " + command + " command against " + role + " on " + hostname + "..."); + String serviceName = getServiceName(roleServiceType.get(role)); + String hostId = getHostId(hostname); + String roleName = getRoleName(serviceName, role.toString(), hostId); + doRoleCommand(serviceName, roleName, command); + } + + // Performing a command (e.g. starting or stopping a role) requires a POST instead of a GET. + private void doRoleCommand(String serviceName, String roleName, RoleCommand roleCommand) { + URI uri = UriBuilder.fromUri(serverHostname) + .path("api") + .path(API_VERSION) + .path("clusters") + .path(clusterName) + .path("services") + .path(serviceName) + .path("roleCommands") + .path(roleCommand.toString()) + .build(); + String body = "{ \"items\": [ \"" + roleName + "\" ] }"; + LOG.info("Executing POST against " + uri + " with body " + body + "..."); + ClientResponse response = client.resource(uri) + .type(MediaType.APPLICATION_JSON) + .post(ClientResponse.class, body); + + int statusCode = response.getStatus(); + if (statusCode != Response.Status.OK.getStatusCode()) { + throw new HTTPException(statusCode); + } + } + + // Possible healthSummary values include "GOOD" and "BAD." + private String getHealthSummary(String serviceName, String roleType, String hostId) + throws IOException { + return getRolePropertyValue(serviceName, roleType, hostId, "healthSummary"); + } + + // This API uses a hostId to execute host-specific commands; get one from a hostname. + private String getHostId(String hostname) throws IOException { + String hostId = null; + + URI uri = UriBuilder.fromUri(serverHostname) + .path("api") + .path(API_VERSION) + .path("hosts") + .build(); + JsonNode hosts = getJsonNodeFromURIGet(uri); + if (hosts != null) { + // Iterate through the list of hosts, stopping once you've reached the requested hostname. + for (JsonNode host : hosts) { + if (host.get("hostname").getTextValue().equals(hostname)) { + hostId = host.get("hostId").getTextValue(); + break; + } + } + } else { + hostId = null; + } + + return hostId; + } + + // Execute GET against URI, returning a JsonNode object to be traversed. + private JsonNode getJsonNodeFromURIGet(URI uri) throws IOException { + LOG.info("Executing GET against " + uri + "..."); + ClientResponse response = client.resource(uri) + .accept(MediaType.APPLICATION_JSON_TYPE) + .get(ClientResponse.class); + + int statusCode = response.getStatus(); + if (statusCode != Response.Status.OK.getStatusCode()) { + throw new HTTPException(statusCode); + } + // This API folds information as the value to an "items" attribute. + return new ObjectMapper().readTree(response.getEntity(String.class)).get("items"); + } + + // This API assigns a unique role name to each host's instance of a role. + private String getRoleName(String serviceName, String roleType, String hostId) + throws IOException { + return getRolePropertyValue(serviceName, roleType, hostId, "name"); + } + + // Get the value of a property from a role on a particular host. + private String getRolePropertyValue(String serviceName, String roleType, String hostId, + String property) throws IOException { + String roleValue = null; + URI uri = UriBuilder.fromUri(serverHostname) + .path("api") + .path(API_VERSION) + .path("clusters") + .path(clusterName) + .path("services") + .path(serviceName) + .path("roles") + .build(); + JsonNode roles = getJsonNodeFromURIGet(uri); + if (roles != null) { + // Iterate through the list of roles, stopping once the requested one is found. + for (JsonNode role : roles) { + if (role.get("hostRef").get("hostId").getTextValue().equals(hostId) && + role.get("type") + .getTextValue() + .toLowerCase() + .equals(roleType.toLowerCase())) { + roleValue = role.get(property).getTextValue(); + break; + } + } + } + + return roleValue; + } + + // Possible roleState values include "STARTED" and "STOPPED." + private String getRoleState(String serviceName, String roleType, String hostId) + throws IOException { + return getRolePropertyValue(serviceName, roleType, hostId, "roleState"); + } + + // Convert a service (e.g. "HBASE," "HDFS") into a service name (e.g. "HBASE-1," "HDFS-1"). + private String getServiceName(Service service) throws IOException { + String serviceName = null; + URI uri = UriBuilder.fromUri(serverHostname) + .path("api") + .path(API_VERSION) + .path("clusters") + .path(clusterName) + .path("services") + .build(); + JsonNode services = getJsonNodeFromURIGet(uri); + if (services != null) { + // Iterate through the list of services, stopping once the requested one is found. + for (JsonNode serviceEntry : services) { + if (serviceEntry.get("type").getTextValue().equals(service.toString())) { + serviceName = serviceEntry.get("name").getTextValue(); + break; + } + } + } + + return serviceName; + } + + /* + * Some enums to guard against bad calls. + */ + + // The RoleCommand enum is used by the doRoleCommand method to guard against non-existent methods + // being invoked on a given role. + private enum RoleCommand { + START, STOP, RESTART; + + // APIs tend to take commands in lowercase, so convert them to save the trouble later. + @Override + public String toString() { + return name().toLowerCase(); + } + } + + // ClusterManager methods take a "ServiceType" object (e.g. "HBASE_MASTER," "HADOOP_NAMENODE"). + // These "service types," which cluster managers call "roles" or "components," need to be mapped + // to their corresponding service (e.g. "HBase," "HDFS") in order to be controlled. + private static Map roleServiceType = new HashMap(); + static { + roleServiceType.put(ServiceType.HADOOP_NAMENODE, Service.HDFS); + roleServiceType.put(ServiceType.HADOOP_DATANODE, Service.HDFS); + roleServiceType.put(ServiceType.HADOOP_JOBTRACKER, Service.MAPREDUCE); + roleServiceType.put(ServiceType.HADOOP_TASKTRACKER, Service.MAPREDUCE); + roleServiceType.put(ServiceType.HBASE_MASTER, Service.HBASE); + roleServiceType.put(ServiceType.HBASE_REGIONSERVER, Service.HBASE); + } + + private enum Service { + HBASE, HDFS, MAPREDUCE + } +} \ No newline at end of file From 54d70e61bf06109ec8f129fb6b21461aa51d20d0 Mon Sep 17 00:00:00 2001 From: stack Date: Tue, 17 Feb 2015 12:10:18 -0800 Subject: [PATCH 029/329] HBASE-13040 Possible failure of TestHMasterRPCException (Zhang Duo) --- .../hbase/master/TestHMasterRPCException.java | 117 +++++++++++------- 1 file changed, 70 insertions(+), 47 deletions(-) diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestHMasterRPCException.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestHMasterRPCException.java index 2419918a34b..37d69409ab2 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestHMasterRPCException.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestHMasterRPCException.java @@ -19,15 +19,18 @@ package org.apache.hadoop.hbase.master; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.net.SocketTimeoutException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.*; import org.apache.hadoop.hbase.CoordinatedStateManager; import org.apache.hadoop.hbase.CoordinatedStateManagerFactory; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.ipc.RpcClient; import org.apache.hadoop.hbase.ipc.RpcClientFactory; import org.apache.hadoop.hbase.protobuf.ProtobufUtil; @@ -36,60 +39,80 @@ import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsMasterRunningRe import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.testclassification.MasterTests; import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.zookeeper.ZKUtil; +import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; +import org.apache.zookeeper.KeeperException; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import com.google.protobuf.BlockingRpcChannel; import com.google.protobuf.ServiceException; -@Category({MasterTests.class, MediumTests.class}) +@Category({ MasterTests.class, MediumTests.class }) public class TestHMasterRPCException { - @Test - public void testRPCException() throws Exception { - HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); - TEST_UTIL.startMiniZKCluster(); - Configuration conf = TEST_UTIL.getConfiguration(); + private static final Log LOG = LogFactory.getLog(TestHMasterRPCException.class); + + private final HBaseTestingUtility testUtil = HBaseTestingUtility.createLocalHTU(); + + private HMaster master; + + private RpcClient rpcClient; + + @Before + public void setUp() throws Exception { + Configuration conf = testUtil.getConfiguration(); conf.set(HConstants.MASTER_PORT, "0"); + conf.setInt(HConstants.ZK_SESSION_TIMEOUT, 2000); + testUtil.startMiniZKCluster(); + CoordinatedStateManager cp = CoordinatedStateManagerFactory.getCoordinatedStateManager(conf); - HMaster hm = new HMaster(conf, cp); - ServerName sm = hm.getServerName(); - RpcClient rpcClient = RpcClientFactory.createClient(conf, HConstants.CLUSTER_ID_DEFAULT); - try { - int i = 0; - //retry the RPC a few times; we have seen SocketTimeoutExceptions if we - //try to connect too soon. Retry on SocketTimeoutException. - while (i < 20) { - try { - BlockingRpcChannel channel = - rpcClient.createBlockingRpcChannel(sm, User.getCurrent(), 0); - MasterProtos.MasterService.BlockingInterface stub = - MasterProtos.MasterService.newBlockingStub(channel); - stub.isMasterRunning(null, IsMasterRunningRequest.getDefaultInstance()); - fail(); - } catch (ServiceException ex) { - IOException ie = ProtobufUtil.getRemoteException(ex); - if (!(ie instanceof SocketTimeoutException)) { - if (ie.getMessage().startsWith("org.apache.hadoop.hbase.ipc." + - "ServerNotRunningYetException: Server is not running yet")) { - // Done. Got the exception we wanted. - System.out.println("Expected exception: " + ie.getMessage()); - return; - } else { - throw ex; - } - } else { - System.err.println("Got SocketTimeoutException. Will retry. "); - } - } catch (Throwable t) { - fail("Unexpected throwable: " + t); - } - Thread.sleep(100); - i++; - } - fail(); - } finally { + ZooKeeperWatcher watcher = testUtil.getZooKeeperWatcher(); + ZKUtil.createWithParents(watcher, watcher.getMasterAddressZNode(), Bytes.toBytes("fake:123")); + master = new HMaster(conf, cp); + rpcClient = RpcClientFactory.createClient(conf, HConstants.CLUSTER_ID_DEFAULT); + } + + @After + public void tearDown() throws IOException { + if (rpcClient != null) { rpcClient.close(); } + if (master != null) { + master.stopMaster(); + } + testUtil.shutdownMiniZKCluster(); } -} \ No newline at end of file + + @Test + public void testRPCException() throws IOException, InterruptedException, KeeperException { + ServerName sm = master.getServerName(); + boolean fakeZNodeDelete = false; + for (int i = 0; i < 20; i++) { + try { + BlockingRpcChannel channel = rpcClient.createBlockingRpcChannel(sm, User.getCurrent(), 0); + MasterProtos.MasterService.BlockingInterface stub = + MasterProtos.MasterService.newBlockingStub(channel); + assertTrue(stub.isMasterRunning(null, IsMasterRunningRequest.getDefaultInstance()) + .getIsMasterRunning()); + return; + } catch (ServiceException ex) { + IOException ie = ProtobufUtil.getRemoteException(ex); + // No SocketTimeoutException here. RpcServer is already started after the construction of + // HMaster. + assertTrue(ie.getMessage().startsWith( + "org.apache.hadoop.hbase.ipc.ServerNotRunningYetException: Server is not running yet")); + LOG.info("Expected exception: ", ie); + if (!fakeZNodeDelete) { + testUtil.getZooKeeperWatcher().getRecoverableZooKeeper() + .delete(testUtil.getZooKeeperWatcher().getMasterAddressZNode(), -1); + fakeZNodeDelete = true; + } + } + Thread.sleep(1000); + } + } +} From b92ff24b0e6e69d6f70919ea577e77353de3fbb1 Mon Sep 17 00:00:00 2001 From: tedyu Date: Tue, 17 Feb 2015 12:39:00 -0800 Subject: [PATCH 030/329] HBASE-12948 Calling Increment#addColumn on the same column multiple times produces wrong result (hongyu bi) --- .../hadoop/hbase/regionserver/HRegion.java | 21 +++++---- .../hbase/client/TestFromClientSide.java | 43 +++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java index 510933cd104..79cb7db0890 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java @@ -3543,7 +3543,7 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // protected void checkReadsEnabled() throws IOException { if (!this.writestate.readsEnabled) { - throw new IOException ("The region's reads are disabled. Cannot serve the request"); + throw new IOException("The region's reads are disabled. Cannot serve the request"); } } @@ -6556,17 +6556,19 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // // Iterate the input columns and update existing values if they were // found, otherwise add new column initialized to the increment amount int idx = 0; - for (Cell cell: family.getValue()) { + List edits = family.getValue(); + for (int i = 0; i < edits.size(); i++) { + Cell cell = edits.get(i); long amount = Bytes.toLong(CellUtil.cloneValue(cell)); boolean noWriteBack = (amount == 0); List newTags = new ArrayList(); // Carry forward any tags that might have been added by a coprocessor if (cell.getTagsLength() > 0) { - Iterator i = CellUtil.tagsIterator(cell.getTagsArray(), + Iterator itr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(), cell.getTagsLength()); - while (i.hasNext()) { - newTags.add(i.next()); + while (itr.hasNext()) { + newTags.add(itr.next()); } } @@ -6584,13 +6586,14 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver { // } // Carry tags forward from previous version if (c.getTagsLength() > 0) { - Iterator i = CellUtil.tagsIterator(c.getTagsArray(), + Iterator itr = CellUtil.tagsIterator(c.getTagsArray(), c.getTagsOffset(), c.getTagsLength()); - while (i.hasNext()) { - newTags.add(i.next()); + while (itr.hasNext()) { + newTags.add(itr.next()); } } - idx++; + if (i < ( edits.size() - 1) && !CellUtil.matchingQualifier(cell, edits.get(i + 1))) + idx++; } // Append new incremented KeyValue to list diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java index e44792aa083..67e33b21875 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java @@ -4606,6 +4606,49 @@ public class TestFromClientSide { assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 2); } + @Test + public void testIncrementOnSameColumn() throws Exception { + LOG.info("Starting testIncrementOnSameColumn"); + final byte[] TABLENAME = Bytes.toBytes("testIncrementOnSameColumn"); + HTable ht = TEST_UTIL.createTable(TABLENAME, FAMILY); + + byte[][] QUALIFIERS = + new byte[][] { Bytes.toBytes("A"), Bytes.toBytes("B"), Bytes.toBytes("C") }; + + Increment inc = new Increment(ROW); + for (int i = 0; i < QUALIFIERS.length; i++) { + inc.addColumn(FAMILY, QUALIFIERS[i], 1); + inc.addColumn(FAMILY, QUALIFIERS[i], 1); + } + ht.increment(inc); + + // Verify expected results + Result r = ht.get(new Get(ROW)); + Cell[] kvs = r.rawCells(); + assertEquals(3, kvs.length); + assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[0], 1); + assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[1], 1); + assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 1); + + // Now try multiple columns again + inc = new Increment(ROW); + for (int i = 0; i < QUALIFIERS.length; i++) { + inc.addColumn(FAMILY, QUALIFIERS[i], 1); + inc.addColumn(FAMILY, QUALIFIERS[i], 1); + } + ht.increment(inc); + + // Verify + r = ht.get(new Get(ROW)); + kvs = r.rawCells(); + assertEquals(3, kvs.length); + assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[0], 2); + assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[1], 2); + assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 2); + + ht.close(); + } + @Test public void testIncrement() throws Exception { LOG.info("Starting testIncrement"); From f1f8c0d43000e69b8cddaf84b73c9645ef257ba9 Mon Sep 17 00:00:00 2001 From: Misty Stanley-Jones Date: Thu, 12 Feb 2015 14:48:05 +1000 Subject: [PATCH 031/329] HBASE-12425 Document the region split process --- src/main/asciidoc/_chapters/architecture.adoc | 25 ++++++++++++++++++ .../resources/images/region_split_process.png | Bin 0 -> 338255 bytes 2 files changed, 25 insertions(+) create mode 100644 src/main/site/resources/images/region_split_process.png diff --git a/src/main/asciidoc/_chapters/architecture.adoc b/src/main/asciidoc/_chapters/architecture.adoc index bae4a23ee4d..6de72081b46 100644 --- a/src/main/asciidoc/_chapters/architecture.adoc +++ b/src/main/asciidoc/_chapters/architecture.adoc @@ -858,6 +858,31 @@ For a RegionServer hosting data that can comfortably fit into cache, or if your The compressed BlockCache is disabled by default. To enable it, set `hbase.block.data.cachecompressed` to `true` in _hbase-site.xml_ on all RegionServers. +[[regionserver_splitting_implementation]] +=== RegionServer Splitting Implementation + +As write requests are handled by the region server, they accumulate in an in-memory storage system called the _memstore_. Once the memstore fills, its content are written to disk as additional store files. This event is called a _memstore flush_. As store files accumulate, the RegionServer will <> them into fewer, larger files. After each flush or compaction finishes, the amount of data stored in the region has changed. The RegionServer consults the region split policy to determine if the region has grown too large or should be split for another policy-specific reason. A region split request is enqueued if the policy recommends it. + +Logically, the process of splitting a region is simple. We find a suitable point in the keyspace of the region where we should divide the region in half, then split the region's data into two new regions at that point. The details of the process however are not simple. When a split happens, the newly created _daughter regions_ do not rewrite all the data into new files immediately. Instead, they create small files similar to symbolic link files, named link:http://www.google.com/url?q=http%3A%2F%2Fhbase.apache.org%2Fapidocs%2Forg%2Fapache%2Fhadoop%2Fhbase%2Fio%2FReference.html&sa=D&sntz=1&usg=AFQjCNEkCbADZ3CgKHTtGYI8bJVwp663CA[Reference files], which point to either the top or bottom part of the parent store file according to the split point. The reference file is used just like a regular data file, but only half of the records are considered. The region can only be split if there are no more references to the immutable data files of the parent region. Those reference files are cleaned gradually by compactions, so that the region will stop referring to its parents files, and can be split further. + +Although splitting the region is a local decision made by the RegionServer, the split process itself must coordinate with many actors. The RegionServer notifies the Master before and after the split, updates the `.META.` table so that clients can discover the new daughter regions, and rearranges the directory structure and data files in HDFS. Splitting is a multi-task process. To enable rollback in case of an error, the RegionServer keeps an in-memory journal about the execution state. The steps taken by the RegionServer to execute the split are illustrated in <>. Each step is labeled with its step number. Actions from RegionServers or Master are shown in red, while actions from the clients are show in green. + +[[regionserver_split_process_image]] +.RegionServer Split Process +image::region_split_process.png[Region Split Process] + +. The RegionServer decides locally to split the region, and prepares the split. *THE SPLIT TRANSACTION IS STARTED.* As a first step, the RegionServer acquires a shared read lock on the table to prevent schema modifications during the splitting process. Then it creates a znode in zookeeper under `/hbase/region-in-transition/region-name`, and sets the znode's state to `SPLITTING`. +. The Master learns about this znode, since it has a watcher for the parent `region-in-transition` znode. +. The RegionServer creates a sub-directory named `.splits` under the parent’s `region` directory in HDFS. +. The RegionServer closes the parent region and marks the region as offline in its local data structures. *THE SPLITTING REGION IS NOW OFFLINE.* At this point, client requests coming to the parent region will throw `NotServingRegionException`. The client will retry with some backoff. The closing region is flushed. +. The RegionServer creates region directories under the `.splits` directory, for daughter regions A and B, and creates necessary data structures. Then it splits the store files, in the sense that it creates two link:http://www.google.com/url?q=http%3A%2F%2Fhbase.apache.org%2Fapidocs%2Forg%2Fapache%2Fhadoop%2Fhbase%2Fio%2FReference.html&sa=D&sntz=1&usg=AFQjCNEkCbADZ3CgKHTtGYI8bJVwp663CA[Reference] files per store file in the parent region. Those reference files will point to the parent regions'files. +. The RegionServer creates the actual region directory in HDFS, and moves the reference files for each daughter. +. The RegionServer sends a `Put` request to the `.META.` table, to set the parent as offline in the `.META.` table and add information about daughter regions. At this point, there won’t be individual entries in `.META.` for the daughters. Clients will see that the parent region is split if they scan `.META.`, but won’t know about the daughters until they appear in `.META.`. Also, if this `Put` to `.META`. succeeds, the parent will be effectively split. If the RegionServer fails before this RPC succeeds, Master and the next Region Server opening the region will clean dirty state about the region split. After the `.META.` update, though, the region split will be rolled-forward by Master. +. The RegionServer opens daughters A and B in parallel. +. The RegionServer adds the daughters A and B to `.META.`, together with information that it hosts the regions. *THE SPLIT REGIONS (DAUGHTERS WITH REFERENCES TO PARENT) ARE NOW ONLINE.* After this point, clients can discover the new regions and issue requests to them. Clients cache the `.META.` entries locally, but when they make requests to the RegionServer or `.META.`, their caches will be invalidated, and they will learn about the new regions from `.META.`. +. The RegionServer updates znode `/hbase/region-in-transition/region-name` in ZooKeeper to state `SPLIT`, so that the master can learn about it. The balancer can freely re-assign the daughter regions to other region servers if necessary. *THE SPLIT TRANSACTION IS NOW FINISHED.* +. After the split, `.META.` and HDFS will still contain references to the parent region. Those references will be removed when compactions in daughter regions rewrite the data files. Garbage collection tasks in the master periodically check whether the daughter regions still refer to the parent region's files. If not, the parent region will be removed. + [[wal]] === Write Ahead Log (WAL) diff --git a/src/main/site/resources/images/region_split_process.png b/src/main/site/resources/images/region_split_process.png new file mode 100644 index 0000000000000000000000000000000000000000..27176173c852d512e0213f97a782d28222d2b11f GIT binary patch literal 338255 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%0A%{#s9|1FwTDk)NA~v)}M=r=nS_^ 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�!`Day|biPK3!Hm(3@xqBS;9-@UJH|0a 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 uAE!33dl5?}~sQoeW4fUHv(8dk7KD2pykF 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 j;0oLn40yKVYit`tt4*W>|3uYp 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
    e-LiCtDwpXx-UYaW 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�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 Date: Tue, 17 Feb 2015 13:38:35 -0800 Subject: [PATCH 032/329] HBASE-13059 Set executable bit for scripts in dev-support (Dima Spivak) --- dev-support/checkstyle_report.py | 0 dev-support/findHangingTests.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 dev-support/checkstyle_report.py mode change 100644 => 100755 dev-support/findHangingTests.py diff --git a/dev-support/checkstyle_report.py b/dev-support/checkstyle_report.py old mode 100644 new mode 100755 diff --git a/dev-support/findHangingTests.py b/dev-support/findHangingTests.py old mode 100644 new mode 100755 From 7b045d143ab91089c1c13f3095389fb5de4111f5 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Tue, 17 Feb 2015 18:09:27 +0530 Subject: [PATCH 033/329] HBASE-13050 Empty Namespace validation Signed-off-by: Matteo Bertozzi --- .../java/org/apache/hadoop/hbase/TableName.java | 16 ++++++++-------- .../apache/hadoop/hbase/util/TestTableName.java | 15 +++++++++++---- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java index 51671579c9c..245bef569eb 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/TableName.java @@ -208,21 +208,21 @@ public final class TableName implements Comparable { /** * Valid namespace characters are [a-zA-Z_0-9] */ - public static void isLegalNamespaceName(byte[] namespaceName, int offset, int length) { - for (int i = offset; i < length; i++) { + public static void isLegalNamespaceName(final byte[] namespaceName, + final int start, + final int end) { + if(end - start < 1) { + throw new IllegalArgumentException("Namespace name must not be empty"); + } + for (int i = start; i < end; i++) { if (Character.isLetterOrDigit(namespaceName[i])|| namespaceName[i] == '_') { continue; } throw new IllegalArgumentException("Illegal character <" + namespaceName[i] + "> at " + i + ". Namespaces can only contain " + "'alphanumeric characters': i.e. [a-zA-Z_0-9]: " + Bytes.toString(namespaceName, - offset, length)); + start, end)); } - if(offset == length) - throw new IllegalArgumentException("Illegal character <" + namespaceName[offset] + - "> at " + offset + ". Namespaces can only contain " + - "'alphanumeric characters': i.e. [a-zA-Z_0-9]: " + Bytes.toString(namespaceName, - offset, length)); } public byte[] getName() { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestTableName.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestTableName.java index 94070f3f8bc..f585f47f9f6 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestTableName.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestTableName.java @@ -23,7 +23,6 @@ import java.util.Map; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; @@ -53,8 +52,8 @@ public class TestTableName extends TestWatcher { public TableName getTableName() { return tableName; } - - String emptyTableNames[] ={"", " "}; + + String emptyNames[] ={"", " "}; String invalidNamespace[] = {":a", "%:a"}; String legalTableNames[] = { "foo", "with-dash_under.dot", "_under_start_ok", "with-dash.with_underscore", "02-01-2012.my_table_01-02", "xyz._mytable_", "9_9_0.table_02" @@ -73,9 +72,17 @@ public class TestTableName extends TestWatcher { } } + @Test(expected = IllegalArgumentException.class) + public void testEmptyNamespaceName() { + for (String nn : emptyNames) { + TableName.isLegalNamespaceName(Bytes.toBytes(nn)); + fail("invalid Namespace name " + nn + " should have failed with IllegalArgumentException"); + } + } + @Test(expected = IllegalArgumentException.class) public void testEmptyTableName() { - for (String tn : emptyTableNames) { + for (String tn : emptyNames) { TableName.isLegalFullyQualifiedTableName(Bytes.toBytes(tn)); fail("invalid tablename " + tn + " should have failed with IllegalArgumentException"); } From d64ce3106378ad505389ab7704127c498f92be37 Mon Sep 17 00:00:00 2001 From: tedyu Date: Wed, 18 Feb 2015 06:23:59 -0800 Subject: [PATCH 034/329] HBASE-13061 RegionStates can remove wrong region from server holdings (Andrey Stepachev) --- .../java/org/apache/hadoop/hbase/master/RegionStates.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/RegionStates.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/RegionStates.java index e5214ca6f24..5b81e0d5c6f 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/RegionStates.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/RegionStates.java @@ -426,7 +426,9 @@ public class RegionStates { if (oldServerName == null) { oldServerName = oldAssignments.remove(encodedName); } - if (oldServerName != null && serverHoldings.containsKey(oldServerName)) { + if (oldServerName != null + && !oldServerName.equals(serverName) + && serverHoldings.containsKey(oldServerName)) { LOG.info("Offlined " + hri.getShortNameToLog() + " from " + oldServerName); removeFromServerHoldings(oldServerName, hri); } From cecc475d2f194eed67edf16cd84fbf9716b8d5c5 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Wed, 18 Feb 2015 08:08:46 -0800 Subject: [PATCH 035/329] HBASE-12102 Duplicate keys in HBase.RegionServer metrics JSON (Ravi Kishore Valeti) --- .../apache/hadoop/hbase/ipc/MetricsHBaseServerSourceImpl.java | 3 +-- .../apache/hadoop/hbase/master/MetricsMasterSourceImpl.java | 3 +-- .../hbase/regionserver/MetricsRegionAggregateSourceImpl.java | 3 +-- .../hbase/regionserver/MetricsRegionServerSourceImpl.java | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSourceImpl.java b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSourceImpl.java index 2f5e5cf289e..04cf9536428 100644 --- a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSourceImpl.java +++ b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/ipc/MetricsHBaseServerSourceImpl.java @@ -110,8 +110,7 @@ public class MetricsHBaseServerSourceImpl extends BaseSourceImpl @Override public void getMetrics(MetricsCollector metricsCollector, boolean all) { - MetricsRecordBuilder mrb = metricsCollector.addRecord(metricsName) - .setContext(metricsContext); + MetricsRecordBuilder mrb = metricsCollector.addRecord(metricsName); if (wrapper != null) { mrb.addGauge(Interns.info(QUEUE_SIZE_NAME, QUEUE_SIZE_DESC), wrapper.getTotalQueueSize()) diff --git a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/master/MetricsMasterSourceImpl.java b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/master/MetricsMasterSourceImpl.java index d4c90dcb88e..02463f618c5 100644 --- a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/master/MetricsMasterSourceImpl.java +++ b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/master/MetricsMasterSourceImpl.java @@ -68,8 +68,7 @@ public class MetricsMasterSourceImpl @Override public void getMetrics(MetricsCollector metricsCollector, boolean all) { - MetricsRecordBuilder metricsRecordBuilder = metricsCollector.addRecord(metricsName) - .setContext(metricsContext); + MetricsRecordBuilder metricsRecordBuilder = metricsCollector.addRecord(metricsName); // masterWrapper can be null because this function is called inside of init. if (masterWrapper != null) { diff --git a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionAggregateSourceImpl.java b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionAggregateSourceImpl.java index 5cb2cec4141..ab7255e8f03 100644 --- a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionAggregateSourceImpl.java +++ b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionAggregateSourceImpl.java @@ -80,8 +80,7 @@ public class MetricsRegionAggregateSourceImpl extends BaseSourceImpl public void getMetrics(MetricsCollector collector, boolean all) { - MetricsRecordBuilder mrb = collector.addRecord(metricsName) - .setContext(metricsContext); + MetricsRecordBuilder mrb = collector.addRecord(metricsName); if (regionSources != null) { lock.readLock().lock(); diff --git a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionServerSourceImpl.java b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionServerSourceImpl.java index 2c1dcd325fe..4cd73a9ec2e 100644 --- a/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionServerSourceImpl.java +++ b/hbase-hadoop2-compat/src/main/java/org/apache/hadoop/hbase/regionserver/MetricsRegionServerSourceImpl.java @@ -176,8 +176,7 @@ public class MetricsRegionServerSourceImpl @Override public void getMetrics(MetricsCollector metricsCollector, boolean all) { - MetricsRecordBuilder mrb = metricsCollector.addRecord(metricsName) - .setContext(metricsContext); + MetricsRecordBuilder mrb = metricsCollector.addRecord(metricsName); // rsWrap can be null because this function is called inside of init. if (rsWrap != null) { From 20bc74dff0cf1e1834e99d1f2499a3f5e4c38a36 Mon Sep 17 00:00:00 2001 From: zhangduo Date: Thu, 19 Feb 2015 00:27:24 +0800 Subject: [PATCH 036/329] HBASE-13066 Fix typo in AsyncRpcChannel Signed-off-by: stack --- .../main/java/org/apache/hadoop/hbase/ipc/AsyncRpcChannel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcChannel.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcChannel.java index ffb2dcfbc6d..b3e01f3e093 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcChannel.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/AsyncRpcChannel.java @@ -607,7 +607,7 @@ public class AsyncRpcChannel { } for (AsyncCall call : toCleanup) { call.setFailed(new CallTimeoutException("Call id=" + call.id + ", waitTime=" - + (currentTime - call.getRpcTimeout()) + ", rpcTimeout=" + call.getRpcTimeout())); + + (currentTime - call.getStartTime()) + ", rpcTimeout=" + call.getRpcTimeout())); } } From 1b84101fe36bbe71d9c0e9833dd40a8460aef28d Mon Sep 17 00:00:00 2001 From: stack Date: Wed, 18 Feb 2015 09:47:15 -0800 Subject: [PATCH 037/329] HBASE-13065 Increasing -Xmx when running TestDistributedLogSplitting (Zhang Duo) --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 132215d61f0..92313b950aa 100644 --- a/pom.xml +++ b/pom.xml @@ -1086,9 +1086,9 @@ true 900 - - 1900m - 1900m + + 2800 + 2800 -enableassertions -XX:MaxDirectMemorySize=1G -Xmx${surefire.Xmx} -XX:MaxPermSize=256m -Djava.security.egd=file:/dev/./urandom -Djava.net.preferIPv4Stack=true -Djava.awt.headless=true From 14bb352b02d645c60d12e52c2be6be5321134b7a Mon Sep 17 00:00:00 2001 From: Devaraj Das Date: Wed, 18 Feb 2015 10:23:02 -0800 Subject: [PATCH 038/329] HBASE-13036. Meta scanner should use its own threadpool --- .../hbase/client/ConnectionManager.java | 103 ++++++++++++------ .../apache/hadoop/hbase/client/HTable.java | 1 + .../hbase/client/TestMetaWithReplicas.java | 20 ++++ 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java index ce6ed78eb09..b1f43d4b740 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionManager.java @@ -579,6 +579,9 @@ final class ConnectionManager { // thread executor shared by all HTableInterface instances created // by this connection private volatile ExecutorService batchPool = null; + // meta thread executor shared by all HTableInterface instances created + // by this connection + private volatile ExecutorService metaLookupPool = null; private volatile boolean cleanupPool = false; private final Configuration conf; @@ -765,52 +768,84 @@ final class ConnectionManager { private ExecutorService getBatchPool() { if (batchPool == null) { - // shared HTable thread executor not yet initialized synchronized (this) { if (batchPool == null) { - int maxThreads = conf.getInt("hbase.hconnection.threads.max", 256); - int coreThreads = conf.getInt("hbase.hconnection.threads.core", 256); - if (maxThreads == 0) { - maxThreads = Runtime.getRuntime().availableProcessors() * 8; - } - if (coreThreads == 0) { - coreThreads = Runtime.getRuntime().availableProcessors() * 8; - } - long keepAliveTime = conf.getLong("hbase.hconnection.threads.keepalivetime", 60); - LinkedBlockingQueue workQueue = - new LinkedBlockingQueue(maxThreads * - conf.getInt(HConstants.HBASE_CLIENT_MAX_TOTAL_TASKS, - HConstants.DEFAULT_HBASE_CLIENT_MAX_TOTAL_TASKS)); - ThreadPoolExecutor tpe = new ThreadPoolExecutor( - coreThreads, - maxThreads, - keepAliveTime, - TimeUnit.SECONDS, - workQueue, - Threads.newDaemonThreadFactory(toString() + "-shared-")); - tpe.allowCoreThreadTimeOut(true); - this.batchPool = tpe; + this.batchPool = getThreadPool(conf.getInt("hbase.hconnection.threads.max", 256), + conf.getInt("hbase.hconnection.threads.core", 256), "-shared-"); + this.cleanupPool = true; } - this.cleanupPool = true; } } return this.batchPool; } + private ExecutorService getThreadPool(int maxThreads, int coreThreads, String nameHint) { + // shared HTable thread executor not yet initialized + if (maxThreads == 0) { + maxThreads = Runtime.getRuntime().availableProcessors() * 8; + } + if (coreThreads == 0) { + coreThreads = Runtime.getRuntime().availableProcessors() * 8; + } + long keepAliveTime = conf.getLong("hbase.hconnection.threads.keepalivetime", 60); + LinkedBlockingQueue workQueue = + new LinkedBlockingQueue(maxThreads * + conf.getInt(HConstants.HBASE_CLIENT_MAX_TOTAL_TASKS, + HConstants.DEFAULT_HBASE_CLIENT_MAX_TOTAL_TASKS)); + ThreadPoolExecutor tpe = new ThreadPoolExecutor( + coreThreads, + maxThreads, + keepAliveTime, + TimeUnit.SECONDS, + workQueue, + Threads.newDaemonThreadFactory(toString() + "-shared-")); + tpe.allowCoreThreadTimeOut(true); + return tpe; + } + + private ExecutorService getMetaLookupPool() { + if (this.metaLookupPool == null) { + synchronized (this) { + if (this.metaLookupPool == null) { + //The meta lookup can happen on replicas of the meta (if the appropriate configs + //are enabled).In a replicated-meta setup, the number '3' is assumed as the max + //number of replicas by default (unless it is configured to be of a higher value). + //In a non-replicated-meta setup, only one thread would be active. + this.metaLookupPool = getThreadPool( + conf.getInt("hbase.hconnection.meta.lookup.threads.max", 3), + conf.getInt("hbase.hconnection.meta.lookup.threads.max.core", 3), + "-metaLookup-shared-"); + } + } + } + return this.metaLookupPool; + } + + protected ExecutorService getCurrentMetaLookupPool() { + return metaLookupPool; + } + protected ExecutorService getCurrentBatchPool() { return batchPool; } - private void shutdownBatchPool() { + private void shutdownPools() { if (this.cleanupPool && this.batchPool != null && !this.batchPool.isShutdown()) { - this.batchPool.shutdown(); - try { - if (!this.batchPool.awaitTermination(10, TimeUnit.SECONDS)) { - this.batchPool.shutdownNow(); - } - } catch (InterruptedException e) { - this.batchPool.shutdownNow(); + shutdownBatchPool(this.batchPool); + } + if (this.metaLookupPool != null && !this.metaLookupPool.isShutdown()) { + shutdownBatchPool(this.metaLookupPool); + } + } + + private void shutdownBatchPool(ExecutorService pool) { + pool.shutdown(); + try { + if (!pool.awaitTermination(10, TimeUnit.SECONDS)) { + pool.shutdownNow(); } + } catch (InterruptedException e) { + pool.shutdownNow(); } } @@ -1206,7 +1241,7 @@ final class ConnectionManager { ReversedClientScanner rcs = null; try { rcs = new ClientSmallReversedScanner(conf, s, TableName.META_TABLE_NAME, this, - rpcCallerFactory, rpcControllerFactory, getBatchPool(), 0); + rpcCallerFactory, rpcControllerFactory, getMetaLookupPool(), 0); regionInfoRow = rcs.next(); } finally { if (rcs != null) { @@ -2327,7 +2362,7 @@ final class ConnectionManager { return; } closeMaster(); - shutdownBatchPool(); + shutdownPools(); this.closed = true; closeZooKeeperWatcher(); this.stubs.clear(); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java index d15ab27ce6a..8ba10bfa1c4 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HTable.java @@ -1438,6 +1438,7 @@ public class HTable implements HTableInterface { terminated = this.pool.awaitTermination(60, TimeUnit.SECONDS); } while (!terminated); } catch (InterruptedException e) { + this.pool.shutdownNow(); LOG.warn("waitForTermination interrupted"); } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestMetaWithReplicas.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestMetaWithReplicas.java index 8e60353c387..b83dc81e037 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestMetaWithReplicas.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestMetaWithReplicas.java @@ -19,10 +19,12 @@ package org.apache.hadoop.hbase.client; import javax.annotation.Nullable; + import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.concurrent.ExecutorService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -38,6 +40,7 @@ import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.Waiter; +import org.apache.hadoop.hbase.client.ConnectionManager.HConnectionImplementation; import org.apache.hadoop.hbase.regionserver.StorefileRefresherChore; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.util.Bytes; @@ -220,6 +223,23 @@ public class TestMetaWithReplicas { assertTrue(Arrays.equals(r.getRow(), row)); } + @Test + public void testMetaLookupThreadPoolCreated() throws Exception { + byte[] TABLE = Bytes.toBytes("testMetaLookupThreadPoolCreated"); + byte[][] FAMILIES = new byte[][] { Bytes.toBytes("foo") }; + if (TEST_UTIL.getHBaseAdmin().tableExists(TABLE)) { + TEST_UTIL.getHBaseAdmin().disableTable(TABLE); + TEST_UTIL.getHBaseAdmin().deleteTable(TABLE); + } + Table htable = TEST_UTIL.createTable(TABLE, FAMILIES, TEST_UTIL.getConfiguration()); + byte[] row = "test".getBytes(); + HConnectionImplementation c = ((HConnectionImplementation)((HTable)htable).connection); + // check that metalookup pool would get created + c.relocateRegion(TABLE, row); + ExecutorService ex = c.getCurrentMetaLookupPool(); + assert(ex != null); + } + @Test public void testChangingReplicaCount() throws Exception { // tests changing the replica count across master restarts From 0740ec655705c9381a0b8483cb8c5331272dcdd9 Mon Sep 17 00:00:00 2001 From: Devaraj Das Date: Wed, 18 Feb 2015 10:47:29 -0800 Subject: [PATCH 039/329] HBASE-13065. Addendum. Fixes the heapsize used to run the unit tests --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 92313b950aa..399850e0c5a 100644 --- a/pom.xml +++ b/pom.xml @@ -1087,8 +1087,8 @@ 900 - 2800 - 2800 + 2800m + 2800m -enableassertions -XX:MaxDirectMemorySize=1G -Xmx${surefire.Xmx} -XX:MaxPermSize=256m -Djava.security.egd=file:/dev/./urandom -Djava.net.preferIPv4Stack=true -Djava.awt.headless=true From 39f549aaece91cf3ccc076ea171889c43b158a49 Mon Sep 17 00:00:00 2001 From: stack Date: Wed, 18 Feb 2015 11:34:50 -0800 Subject: [PATCH 040/329] HBASE-13056 Refactor table.jsp code to remove repeated code and make it easier to add new checks (Vikas Vishwakarma) --- .../resources/hbase-webapps/master/table.jsp | 64 +++++-------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/hbase-server/src/main/resources/hbase-webapps/master/table.jsp b/hbase-server/src/main/resources/hbase-webapps/master/table.jsp index db48b6c79aa..274f6605fa3 100644 --- a/hbase-server/src/main/resources/hbase-webapps/master/table.jsp +++ b/hbase-server/src/main/resources/hbase-webapps/master/table.jsp @@ -61,21 +61,21 @@ if (showFragmentation) { frags = FSUtils.getTableFragmentation(master); } + String action = request.getParameter("action"); + String key = request.getParameter("key"); %> - -<% - String action = request.getParameter("action"); - String key = request.getParameter("key"); - if ( !readOnly && action != null ) { -%> - HBase Master: <%= master.getServerName() %> + <% if ( !readOnly && action != null ) { %> + HBase Master: <%= master.getServerName() %> + <% } else { %> + Table: <%= fqtn %> + <% } %> @@ -84,11 +84,17 @@ + <% if ( !readOnly && action != null ) { %> + <% } else { %> + + <% } %> +<% +if ( !readOnly && action != null ) { +%>
    @@ -153,47 +162,6 @@ <% } else { %> - - - Table: <%= fqtn %> - - - - - - - - - - - -
    From 31f17b17f0e2d12550b97098ec45ab59c5d98d58 Mon Sep 17 00:00:00 2001 From: Ashish Singhi Date: Wed, 18 Feb 2015 11:42:20 -0800 Subject: [PATCH 041/329] HBASE-13002 Make encryption cipher configurable Signed-off-by: Andrew Purtell --- .../hadoop/hbase/security/EncryptionUtil.java | 20 ++++++------ .../hbase/security/TestEncryptionUtil.java | 4 ++- .../org/apache/hadoop/hbase/HConstants.java | 18 ++++++++--- .../hadoop/hbase/io/crypto/Encryption.java | 31 ++++++++++++++++--- .../hbase/io/crypto/TestCipherProvider.java | 8 +++-- .../hbase/io/crypto/TestEncryption.java | 7 +++-- .../hadoop/hbase/regionserver/HStore.java | 4 +-- .../wal/SecureProtobufLogWriter.java | 5 ++- .../hbase/io/hfile/TestHFileEncryption.java | 4 ++- .../TestEncryptionKeyRotation.java | 14 ++++++--- .../TestEncryptionRandomKeying.java | 4 ++- .../hadoop/hbase/util/TestEncryptionTest.java | 6 ++-- .../hbase/util/TestHBaseFsckEncryption.java | 6 ++-- 13 files changed, 93 insertions(+), 38 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java index f4bc3e9c681..e8773636ec0 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/EncryptionUtil.java @@ -63,8 +63,7 @@ public class EncryptionUtil { /** * Protect a key by encrypting it with the secret key of the given subject. - * The configuration must be set up correctly for key alias resolution. Keys - * are always wrapped using AES. + * The configuration must be set up correctly for key alias resolution. * @param conf configuration * @param subject subject key alias * @param key the key @@ -72,10 +71,12 @@ public class EncryptionUtil { */ public static byte[] wrapKey(Configuration conf, String subject, Key key) throws IOException { - // Wrap the key with AES - Cipher cipher = Encryption.getCipher(conf, "AES"); + // Wrap the key with the configured encryption algorithm. + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + Cipher cipher = Encryption.getCipher(conf, algorithm); if (cipher == null) { - throw new RuntimeException("Cipher 'AES' not available"); + throw new RuntimeException("Cipher '" + algorithm + "' not available"); } EncryptionProtos.WrappedKey.Builder builder = EncryptionProtos.WrappedKey.newBuilder(); builder.setAlgorithm(key.getAlgorithm()); @@ -100,8 +101,7 @@ public class EncryptionUtil { /** * Unwrap a key by decrypting it with the secret key of the given subject. - * The configuration must be set up correctly for key alias resolution. Keys - * are always unwrapped using AES. + * The configuration must be set up correctly for key alias resolution. * @param conf configuration * @param subject subject key alias * @param value the encrypted key bytes @@ -113,9 +113,11 @@ public class EncryptionUtil { throws IOException, KeyException { EncryptionProtos.WrappedKey wrappedKey = EncryptionProtos.WrappedKey.PARSER .parseDelimitedFrom(new ByteArrayInputStream(value)); - Cipher cipher = Encryption.getCipher(conf, "AES"); + String algorithm = conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, + HConstants.CIPHER_AES); + Cipher cipher = Encryption.getCipher(conf, algorithm); if (cipher == null) { - throw new RuntimeException("Algorithm 'AES' not available"); + throw new RuntimeException("Cipher '" + algorithm + "' not available"); } ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] iv = wrappedKey.hasIv() ? wrappedKey.getIv().toByteArray() : null; diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java index ed6f49b7dc3..f9dd30bfce2 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/security/TestEncryptionUtil.java @@ -49,7 +49,9 @@ public class TestEncryptionUtil { // generate a test key byte[] keyBytes = new byte[AES.KEY_LENGTH]; new SecureRandom().nextBytes(keyBytes); - Key key = new SecretKeySpec(keyBytes, "AES"); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + Key key = new SecretKeySpec(keyBytes, algorithm); // wrap the test key byte[] wrappedKeyBytes = EncryptionUtil.wrapKey(conf, "hbase", key); diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java index 8a07397117d..40f67f3a8c0 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java @@ -387,7 +387,7 @@ public final class HConstants { /** * The hbase:meta table's name. - * + * */ @Deprecated // for compat from 0.94 -> 0.96. public static final byte[] META_TABLE_NAME = TableName.META_TABLE_NAME.getName(); @@ -939,7 +939,7 @@ public final class HConstants { * NONE: no preference in destination of replicas * ONE_SSD: place only one replica in SSD and the remaining in default storage * and ALL_SSD: place all replica on SSD - * + * * See http://hadoop.apache.org/docs/r2.6.0/hadoop-project-dist/hadoop-hdfs/ArchivalStorage.html*/ public static final String WAL_STORAGE_POLICY = "hbase.wal.storage.policy"; public static final String DEFAULT_WAL_STORAGE_POLICY = "NONE"; @@ -1051,6 +1051,9 @@ public final class HConstants { public static final long NO_NONCE = 0; + /** Default cipher for encryption */ + public static final String CIPHER_AES = "AES"; + /** Configuration key for the crypto algorithm provider, a class name */ public static final String CRYPTO_CIPHERPROVIDER_CONF_KEY = "hbase.crypto.cipherprovider"; @@ -1074,6 +1077,13 @@ public final class HConstants { /** Configuration key for the name of the master WAL encryption key for the cluster, a string */ public static final String CRYPTO_WAL_KEY_NAME_CONF_KEY = "hbase.crypto.wal.key.name"; + /** Configuration key for the algorithm used for creating jks key, a string */ + public static final String CRYPTO_KEY_ALGORITHM_CONF_KEY = "hbase.crypto.key.algorithm"; + + /** Configuration key for the name of the alternate cipher algorithm for the cluster, a string */ + public static final String CRYPTO_ALTERNATE_KEY_ALGORITHM_CONF_KEY = + "hbase.crypto.alternate.key.algorithm"; + /** Configuration key for enabling WAL encryption, a boolean */ public static final String ENABLE_WAL_ENCRYPTION = "hbase.regionserver.wal.encryption"; @@ -1126,7 +1136,7 @@ public final class HConstants { public static final String HBASE_CLIENT_FAST_FAIL_THREASHOLD_MS = "hbase.client.fastfail.threshold"; - + public static final long HBASE_CLIENT_FAST_FAIL_THREASHOLD_MS_DEFAULT = 60000; @@ -1137,7 +1147,7 @@ public final class HConstants { 600000; public static final String HBASE_CLIENT_FAST_FAIL_INTERCEPTOR_IMPL = - "hbase.client.fast.fail.interceptor.impl"; + "hbase.client.fast.fail.interceptor.impl"; /** Config key for if the server should send backpressure and if the client should listen to * that backpressure from the server */ diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java index 3420d0a628f..ad89ca054c7 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/Encryption.java @@ -469,9 +469,8 @@ public final class Encryption { * @param iv the initialization vector, can be null * @throws IOException */ - public static void decryptWithSubjectKey(OutputStream out, InputStream in, - int outLen, String subject, Configuration conf, Cipher cipher, - byte[] iv) throws IOException { + public static void decryptWithSubjectKey(OutputStream out, InputStream in, int outLen, + String subject, Configuration conf, Cipher cipher, byte[] iv) throws IOException { Key key = getSecretKeyForSubject(subject, conf); if (key == null) { throw new IOException("No key found for subject '" + subject + "'"); @@ -479,7 +478,31 @@ public final class Encryption { Decryptor d = cipher.getDecryptor(); d.setKey(key); d.setIv(iv); // can be null - decrypt(out, in, outLen, d); + try { + decrypt(out, in, outLen, d); + } catch (IOException e) { + // If the current cipher algorithm fails to unwrap, try the alternate cipher algorithm, if one + // is configured + String alternateAlgorithm = conf.get(HConstants.CRYPTO_ALTERNATE_KEY_ALGORITHM_CONF_KEY); + if (alternateAlgorithm != null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Unable to decrypt data with current cipher algorithm '" + + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES) + + "'. Trying with the alternate cipher algorithm '" + alternateAlgorithm + + "' configured."); + } + Cipher alterCipher = Encryption.getCipher(conf, alternateAlgorithm); + if (alterCipher == null) { + throw new RuntimeException("Cipher '" + alternateAlgorithm + "' not available"); + } + d = alterCipher.getDecryptor(); + d.setKey(key); + d.setIv(iv); // can be null + decrypt(out, in, outLen, d); + } else { + throw new IOException(e); + } + } } private static ClassLoader getClassLoaderForClass(Class c) { diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestCipherProvider.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestCipherProvider.java index fdb9448c1df..dbf7fc5d26e 100644 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestCipherProvider.java +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestCipherProvider.java @@ -142,11 +142,13 @@ public class TestCipherProvider { Configuration conf = HBaseConfiguration.create(); CipherProvider provider = Encryption.getCipherProvider(conf); assertTrue(provider instanceof DefaultCipherProvider); - assertTrue(Arrays.asList(provider.getSupportedCiphers()).contains("AES")); - Cipher a = Encryption.getCipher(conf, "AES"); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + assertTrue(Arrays.asList(provider.getSupportedCiphers()).contains(algorithm)); + Cipher a = Encryption.getCipher(conf, algorithm); assertNotNull(a); assertTrue(a.getProvider() instanceof DefaultCipherProvider); - assertEquals(a.getName(), "AES"); + assertEquals(a.getName(), algorithm); assertEquals(a.getKeyLength(), AES.KEY_LENGTH); } diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestEncryption.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestEncryption.java index d36333efef2..0d38356010b 100644 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestEncryption.java +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/crypto/TestEncryption.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.testclassification.MiscTests; import org.apache.hadoop.hbase.testclassification.SmallTests; import org.apache.hadoop.hbase.util.Bytes; @@ -89,8 +90,10 @@ public class TestEncryption { LOG.info("checkTransformSymmetry: AES, plaintext length = " + plaintext.length); Configuration conf = HBaseConfiguration.create(); - Cipher aes = Encryption.getCipher(conf, "AES"); - Key key = new SecretKeySpec(keyBytes, "AES"); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + Cipher aes = Encryption.getCipher(conf, algorithm); + Key key = new SecretKeySpec(keyBytes, algorithm); Encryptor e = aes.getEncryptor(); e.setKey(key); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java index df9e4828597..7dbe55dc13a 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HStore.java @@ -318,7 +318,7 @@ public class HStore implements Store { // Use the algorithm the key wants cipher = Encryption.getCipher(conf, key.getAlgorithm()); if (cipher == null) { - throw new RuntimeException("Cipher '" + cipher + "' is not available"); + throw new RuntimeException("Cipher '" + key.getAlgorithm() + "' is not available"); } // Fail if misconfigured // We use the encryption type specified in the column schema as a sanity check on @@ -332,7 +332,7 @@ public class HStore implements Store { // Family does not provide key material, create a random key cipher = Encryption.getCipher(conf, cipherName); if (cipher == null) { - throw new RuntimeException("Cipher '" + cipher + "' is not available"); + throw new RuntimeException("Cipher '" + cipherName + "' is not available"); } key = cipher.getRandomKey(); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/SecureProtobufLogWriter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/SecureProtobufLogWriter.java index e850485bb95..c352770a1c8 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/SecureProtobufLogWriter.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/SecureProtobufLogWriter.java @@ -43,8 +43,6 @@ import org.apache.hadoop.hbase.security.User; public class SecureProtobufLogWriter extends ProtobufLogWriter { private static final Log LOG = LogFactory.getLog(SecureProtobufLogWriter.class); - private static final String DEFAULT_CIPHER = "AES"; - private Encryptor encryptor = null; @Override @@ -56,7 +54,8 @@ public class SecureProtobufLogWriter extends ProtobufLogWriter { EncryptionTest.testCipherProvider(conf); // Get an instance of our cipher - final String cipherName = conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, DEFAULT_CIPHER); + final String cipherName = + conf.get(HConstants.CRYPTO_WAL_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); Cipher cipher = Encryption.getCipher(conf, cipherName); if (cipher == null) { throw new RuntimeException("Cipher '" + cipherName + "' is not available"); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestHFileEncryption.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestHFileEncryption.java index 0cb3c3cfdf2..2d821ae48bc 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestHFileEncryption.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestHFileEncryption.java @@ -70,7 +70,9 @@ public class TestHFileEncryption { fs = FileSystem.get(conf); cryptoContext = Encryption.newContext(conf); - Cipher aes = Encryption.getCipher(conf, "AES"); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + Cipher aes = Encryption.getCipher(conf, algorithm); assertNotNull(aes); cryptoContext.setCipher(aes); byte[] key = new byte[aes.getKeyLength()]; diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionKeyRotation.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionKeyRotation.java index af86b4e0621..b791fdb716e 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionKeyRotation.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionKeyRotation.java @@ -67,9 +67,11 @@ public class TestEncryptionKeyRotation { SecureRandom rng = new SecureRandom(); byte[] keyBytes = new byte[AES.KEY_LENGTH]; rng.nextBytes(keyBytes); - initialCFKey = new SecretKeySpec(keyBytes, "AES"); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + initialCFKey = new SecretKeySpec(keyBytes, algorithm); rng.nextBytes(keyBytes); - secondCFKey = new SecretKeySpec(keyBytes, "AES"); + secondCFKey = new SecretKeySpec(keyBytes, algorithm); } @BeforeClass @@ -95,7 +97,9 @@ public class TestEncryptionKeyRotation { HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("default", "testCFKeyRotation")); HColumnDescriptor hcd = new HColumnDescriptor("cf"); - hcd.setEncryptionType("AES"); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + hcd.setEncryptionType(algorithm); hcd.setEncryptionKey(EncryptionUtil.wrapKey(conf, "hbase", initialCFKey)); htd.addFamily(hcd); @@ -154,7 +158,9 @@ public class TestEncryptionKeyRotation { HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("default", "testMasterKeyRotation")); HColumnDescriptor hcd = new HColumnDescriptor("cf"); - hcd.setEncryptionType("AES"); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + hcd.setEncryptionType(algorithm); hcd.setEncryptionKey(EncryptionUtil.wrapKey(conf, "hbase", initialCFKey)); htd.addFamily(hcd); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionRandomKeying.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionRandomKeying.java index 29a58a65ee7..ebfc89c1a7d 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionRandomKeying.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEncryptionRandomKeying.java @@ -92,7 +92,9 @@ public class TestEncryptionRandomKeying { // Specify an encryption algorithm without a key htd = new HTableDescriptor(TableName.valueOf("default", "TestEncryptionRandomKeying")); HColumnDescriptor hcd = new HColumnDescriptor("cf"); - hcd.setEncryptionType("AES"); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + hcd.setEncryptionType(algorithm); htd.addFamily(hcd); // Start the minicluster diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestEncryptionTest.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestEncryptionTest.java index d615a293205..5d2f04f543a 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestEncryptionTest.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestEncryptionTest.java @@ -75,10 +75,12 @@ public class TestEncryptionTest { public void testTestCipher() { Configuration conf = HBaseConfiguration.create(); conf.set(HConstants.CRYPTO_KEYPROVIDER_CONF_KEY, KeyProviderForTesting.class.getName()); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); try { - EncryptionTest.testEncryption(conf, "AES", null); + EncryptionTest.testEncryption(conf, algorithm, null); } catch (Exception e) { - fail("Test for cipher AES should have succeeded"); + fail("Test for cipher " + algorithm + " should have succeeded"); } try { EncryptionTest.testEncryption(conf, "foobar", null); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestHBaseFsckEncryption.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestHBaseFsckEncryption.java index 69ffa5593ee..7c289a1ee8a 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestHBaseFsckEncryption.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestHBaseFsckEncryption.java @@ -78,7 +78,9 @@ public class TestHBaseFsckEncryption { SecureRandom rng = new SecureRandom(); byte[] keyBytes = new byte[AES.KEY_LENGTH]; rng.nextBytes(keyBytes); - cfKey = new SecretKeySpec(keyBytes, "AES"); + String algorithm = + conf.get(HConstants.CRYPTO_KEY_ALGORITHM_CONF_KEY, HConstants.CIPHER_AES); + cfKey = new SecretKeySpec(keyBytes,algorithm); // Start the minicluster TEST_UTIL.startMiniCluster(3); @@ -86,7 +88,7 @@ public class TestHBaseFsckEncryption { // Create the table htd = new HTableDescriptor(TableName.valueOf("default", "TestHBaseFsckEncryption")); HColumnDescriptor hcd = new HColumnDescriptor("cf"); - hcd.setEncryptionType("AES"); + hcd.setEncryptionType(algorithm); hcd.setEncryptionKey(EncryptionUtil.wrapKey(conf, conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, User.getCurrent().getShortName()), cfKey)); From 18402cc850b143bc6f88d90e62c42b9ef4131ca6 Mon Sep 17 00:00:00 2001 From: tedyu Date: Thu, 19 Feb 2015 08:28:13 -0800 Subject: [PATCH 042/329] HBASE-13072 BucketCache.evictBlock returns true if block does not exist (Duo Zhang) --- .../hbase/io/hfile/bucket/BucketCache.java | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/bucket/BucketCache.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/bucket/BucketCache.java index d3b303adf73..7dda0e6ccfd 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/bucket/BucketCache.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/bucket/BucketCache.java @@ -451,30 +451,36 @@ public class BucketCache implements BlockCache, HeapSize { this.heapSize.addAndGet(-1 * removedBlock.getData().heapSize()); } BucketEntry bucketEntry = backingMap.get(cacheKey); - if (bucketEntry != null) { - IdLock.Entry lockEntry = null; - try { - lockEntry = offsetLock.getLockEntry(bucketEntry.offset()); - if (bucketEntry.equals(backingMap.remove(cacheKey))) { - bucketAllocator.freeBlock(bucketEntry.offset()); - realCacheSize.addAndGet(-1 * bucketEntry.getLength()); - blocksByHFile.remove(cacheKey.getHfileName(), cacheKey); - if (removedBlock == null) { - this.blockNumber.decrementAndGet(); - } - } else { - return false; - } - } catch (IOException ie) { - LOG.warn("Failed evicting block " + cacheKey); + if (bucketEntry == null) { + if (removedBlock != null) { + cacheStats.evicted(0); + return true; + } else { return false; - } finally { - if (lockEntry != null) { - offsetLock.releaseLockEntry(lockEntry); - } } } - cacheStats.evicted(bucketEntry == null? 0: bucketEntry.getCachedTime()); + IdLock.Entry lockEntry = null; + try { + lockEntry = offsetLock.getLockEntry(bucketEntry.offset()); + if (bucketEntry.equals(backingMap.remove(cacheKey))) { + bucketAllocator.freeBlock(bucketEntry.offset()); + realCacheSize.addAndGet(-1 * bucketEntry.getLength()); + blocksByHFile.remove(cacheKey.getHfileName(), cacheKey); + if (removedBlock == null) { + this.blockNumber.decrementAndGet(); + } + } else { + return false; + } + } catch (IOException ie) { + LOG.warn("Failed evicting block " + cacheKey); + return false; + } finally { + if (lockEntry != null) { + offsetLock.releaseLockEntry(lockEntry); + } + } + cacheStats.evicted(bucketEntry.getCachedTime()); return true; } From 365054c110467d0628019761791281875631f4be Mon Sep 17 00:00:00 2001 From: Sean Busbey Date: Thu, 19 Feb 2015 13:59:27 -0600 Subject: [PATCH 043/329] HBASE-13075 TableInputFormatBase spuriously warning about multiple initializeTable calls --- .../org/apache/hadoop/hbase/mapred/TableInputFormatBase.java | 2 +- .../org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java index b5b79d2d49a..dd58d5c31e1 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapred/TableInputFormatBase.java @@ -231,7 +231,7 @@ implements InputFormat { * @throws IOException */ protected void initializeTable(Connection connection, TableName tableName) throws IOException { - if (table != null || connection != null) { + if (this.table != null || this.connection != null) { LOG.warn("initializeTable called multiple times. Overwriting connection and table " + "reference; TableInputFormatBase will not close these old references when done."); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java index 6c42d7f446a..e27251a8542 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.java @@ -641,7 +641,7 @@ extends InputFormat { * @throws IOException */ protected void initializeTable(Connection connection, TableName tableName) throws IOException { - if (table != null || connection != null) { + if (this.table != null || this.connection != null) { LOG.warn("initializeTable called multiple times. Overwriting connection and table " + "reference; TableInputFormatBase will not close these old references when done."); } From 03d8918142681d4c8abe40e8c8fb32307756d8a8 Mon Sep 17 00:00:00 2001 From: tedyu Date: Thu, 19 Feb 2015 16:37:35 -0800 Subject: [PATCH 044/329] HBASE-13069 Thrift Http Server returns an error code of 500 instead of 401 when authentication fails (Srikanth Srungarapu) --- .../java/org/apache/hadoop/hbase/thrift/ThriftHttpServlet.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftHttpServlet.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftHttpServlet.java index 25c6da3fbdb..f3bed0a8923 100644 --- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftHttpServlet.java +++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftHttpServlet.java @@ -83,6 +83,7 @@ public class ThriftHttpServlet extends TServlet { // Send a 401 to the client response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().println("Authentication Error: " + e.getMessage()); + return; } } String doAsUserFromQuery = request.getHeader("doAs"); From 229b0774c06a3adac1d73f227529f63a8b6d87e2 Mon Sep 17 00:00:00 2001 From: tedyu Date: Fri, 20 Feb 2015 08:35:29 -0800 Subject: [PATCH 045/329] HBASE-13070 Fix TestCacheOnWrite (Duo Zhang) --- .../hbase/io/hfile/TestCacheOnWrite.java | 147 ++++++++++-------- 1 file changed, 82 insertions(+), 65 deletions(-) diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestCacheOnWrite.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestCacheOnWrite.java index b13c076806b..7ec7e081aae 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestCacheOnWrite.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestCacheOnWrite.java @@ -20,8 +20,8 @@ package org.apache.hadoop.hbase.io.hfile; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; import java.util.List; -import java.util.Map; import java.util.Random; import org.apache.commons.logging.Log; @@ -37,7 +36,6 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; @@ -59,6 +57,7 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.ChecksumType; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -66,6 +65,8 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import com.google.common.collect.Lists; + /** * Tests {@link HFile} cache-on-write functionality for the following block * types: data blocks, non-root index blocks, and Bloom filter blocks. @@ -170,7 +171,7 @@ public class TestCacheOnWrite { this.blockCache = blockCache; testDescription = "[cacheOnWrite=" + cowType + ", compress=" + compress + ", encoderType=" + encoderType + ", cacheCompressedData=" + cacheCompressedData + "]"; - System.out.println(testDescription); + LOG.info(testDescription); } private static List getBlockCaches() throws IOException { @@ -185,10 +186,10 @@ public class TestCacheOnWrite { // bucket cache FileSystem.get(conf).mkdirs(TEST_UTIL.getDataTestDir()); - int[] bucketSizes = {INDEX_BLOCK_SIZE, DATA_BLOCK_SIZE, BLOOM_BLOCK_SIZE, 64 * 1024 }; + int[] bucketSizes = + { INDEX_BLOCK_SIZE, DATA_BLOCK_SIZE, BLOOM_BLOCK_SIZE, 64 * 1024, 128 * 1024 }; BlockCache bucketcache = - new BucketCache("file:" + TEST_UTIL.getDataTestDir() + "/bucket.data", - 128 * 1024 * 1024, 64 * 1024, bucketSizes, 5, 64 * 100, null); + new BucketCache("offheap", 128 * 1024 * 1024, 64 * 1024, bucketSizes, 5, 64 * 100, null); blockcaches.add(bucketcache); return blockcaches; } @@ -201,7 +202,8 @@ public class TestCacheOnWrite { for (Compression.Algorithm compress : HBaseTestingUtility.COMPRESSION_ALGORITHMS) { for (BlockEncoderTestType encoderType : BlockEncoderTestType.values()) { for (boolean cacheCompressedData : new boolean[] { false, true }) { - cowTypes.add(new Object[] { cowType, compress, encoderType, cacheCompressedData, blockache}); + cowTypes.add(new Object[] { cowType, compress, encoderType, cacheCompressedData, + blockache }); } } } @@ -210,6 +212,31 @@ public class TestCacheOnWrite { return cowTypes; } + private void clearBlockCache(BlockCache blockCache) throws InterruptedException { + if (blockCache instanceof LruBlockCache) { + ((LruBlockCache) blockCache).clearCache(); + } else { + // BucketCache may not return all cached blocks(blocks in write queue), so check it here. + for (int clearCount = 0; blockCache.getBlockCount() > 0; clearCount++) { + if (clearCount > 0) { + LOG.warn("clear block cache " + blockCache + " " + clearCount + " times, " + + blockCache.getBlockCount() + " blocks remaining"); + Thread.sleep(10); + } + for (CachedBlock block : Lists.newArrayList(blockCache)) { + BlockCacheKey key = new BlockCacheKey(block.getFilename(), block.getOffset()); + // CombinedBucketCache may need evict two times. + for (int evictCount = 0; blockCache.evictBlock(key); evictCount++) { + if (evictCount > 1) { + LOG.warn("evict block " + block + " in " + blockCache + " " + evictCount + + " times, maybe a bug here"); + } + } + } + } + } + } + @Before public void setUp() throws IOException { conf = TEST_UTIL.getConfiguration(); @@ -221,25 +248,24 @@ public class TestCacheOnWrite { conf.setBoolean(CacheConfig.CACHE_DATA_BLOCKS_COMPRESSED_KEY, cacheCompressedData); cowType.modifyConf(conf); fs = HFileSystem.get(conf); + CacheConfig.GLOBAL_BLOCK_CACHE_INSTANCE = blockCache; cacheConf = new CacheConfig(blockCache, true, true, cowType.shouldBeCached(BlockType.DATA), cowType.shouldBeCached(BlockType.LEAF_INDEX), - cowType.shouldBeCached(BlockType.BLOOM_CHUNK), false, cacheCompressedData, true, false); + cowType.shouldBeCached(BlockType.BLOOM_CHUNK), false, cacheCompressedData, false, false); } @After - public void tearDown() { - cacheConf = new CacheConfig(conf); - blockCache = cacheConf.getBlockCache(); + public void tearDown() throws IOException, InterruptedException { + clearBlockCache(blockCache); } - @Test - public void testStoreFileCacheOnWrite() throws IOException { - testStoreFileCacheOnWriteInternals(false); - testStoreFileCacheOnWriteInternals(true); + @AfterClass + public static void afterClass() throws IOException { + TEST_UTIL.cleanupTestDir(); } - protected void testStoreFileCacheOnWriteInternals(boolean useTags) throws IOException { + private void testStoreFileCacheOnWriteInternals(boolean useTags) throws IOException { writeStoreFile(useTags); readStoreFile(useTags); } @@ -323,15 +349,15 @@ public class TestCacheOnWrite { encoderType.encode ? BlockType.ENCODED_DATA : BlockType.DATA; if (useTags) { assertEquals("{" + cachedDataBlockType - + "=1550, LEAF_INDEX=173, BLOOM_CHUNK=9, INTERMEDIATE_INDEX=20}", countByType); + + "=2663, LEAF_INDEX=297, BLOOM_CHUNK=9, INTERMEDIATE_INDEX=34}", countByType); } else { assertEquals("{" + cachedDataBlockType - + "=1379, LEAF_INDEX=154, BLOOM_CHUNK=9, INTERMEDIATE_INDEX=18}", countByType); + + "=2498, LEAF_INDEX=278, BLOOM_CHUNK=9, INTERMEDIATE_INDEX=31}", countByType); } // iterate all the keyvalue from hfile while (scanner.next()) { - Cell cell = scanner.getKeyValue(); + scanner.getKeyValue(); } reader.close(); } @@ -341,18 +367,16 @@ public class TestCacheOnWrite { // Let's make half of KVs puts. return KeyValue.Type.Put; } else { - KeyValue.Type keyType = - KeyValue.Type.values()[1 + rand.nextInt(NUM_VALID_KEY_TYPES)]; - if (keyType == KeyValue.Type.Minimum || keyType == KeyValue.Type.Maximum) - { - throw new RuntimeException("Generated an invalid key type: " + keyType - + ". " + "Probably the layout of KeyValue.Type has changed."); + KeyValue.Type keyType = KeyValue.Type.values()[1 + rand.nextInt(NUM_VALID_KEY_TYPES)]; + if (keyType == KeyValue.Type.Minimum || keyType == KeyValue.Type.Maximum) { + throw new RuntimeException("Generated an invalid key type: " + keyType + ". " + + "Probably the layout of KeyValue.Type has changed."); } return keyType; } } - public void writeStoreFile(boolean useTags) throws IOException { + private void writeStoreFile(boolean useTags) throws IOException { if(useTags) { TEST_UTIL.getConfiguration().setInt("hfile.format.version", 3); } else { @@ -368,12 +392,11 @@ public class TestCacheOnWrite { .withOutputDir(storeFileParentDir).withComparator(KeyValue.COMPARATOR) .withFileContext(meta) .withBloomType(BLOOM_TYPE).withMaxKeyCount(NUM_KV).build(); - - final int rowLen = 32; + byte[] cf = Bytes.toBytes("fam"); for (int i = 0; i < NUM_KV; ++i) { - byte[] k = TestHFileWriterV2.randomOrderedKey(rand, i); - byte[] v = TestHFileWriterV2.randomValue(rand); - int cfLen = rand.nextInt(k.length - rowLen + 1); + byte[] row = TestHFileWriterV2.randomOrderedKey(rand, i); + byte[] qualifier = TestHFileWriterV2.randomRowOrQualifier(rand); + byte[] value = TestHFileWriterV2.randomValue(rand); KeyValue kv; if(useTags) { Tag t = new Tag((byte) 1, "visibility"); @@ -381,21 +404,13 @@ public class TestCacheOnWrite { tagList.add(t); Tag[] tags = new Tag[1]; tags[0] = t; - kv = new KeyValue( - k, 0, rowLen, - k, rowLen, cfLen, - k, rowLen + cfLen, k.length - rowLen - cfLen, - rand.nextLong(), - generateKeyType(rand), - v, 0, v.length, tagList); + kv = + new KeyValue(row, 0, row.length, cf, 0, cf.length, qualifier, 0, qualifier.length, + rand.nextLong(), generateKeyType(rand), value, 0, value.length, tagList); } else { - kv = new KeyValue( - k, 0, rowLen, - k, rowLen, cfLen, - k, rowLen + cfLen, k.length - rowLen - cfLen, - rand.nextLong(), - generateKeyType(rand), - v, 0, v.length); + kv = + new KeyValue(row, 0, row.length, cf, 0, cf.length, qualifier, 0, qualifier.length, + rand.nextLong(), generateKeyType(rand), value, 0, value.length); } sfw.append(kv); } @@ -404,13 +419,8 @@ public class TestCacheOnWrite { storeFilePath = sfw.getPath(); } - @Test - public void testNotCachingDataBlocksDuringCompaction() throws IOException { - testNotCachingDataBlocksDuringCompactionInternals(false); - testNotCachingDataBlocksDuringCompactionInternals(true); - } - - protected void testNotCachingDataBlocksDuringCompactionInternals(boolean useTags) throws IOException { + private void testNotCachingDataBlocksDuringCompactionInternals(boolean useTags) + throws IOException, InterruptedException { if (useTags) { TEST_UTIL.getConfiguration().setInt("hfile.format.version", 3); } else { @@ -450,7 +460,7 @@ public class TestCacheOnWrite { HConstants.LATEST_TIMESTAMP, Bytes.toBytes(valueStr), tags); p.add(kv); } else { - p.add(cfBytes, Bytes.toBytes(qualStr), ts++, Bytes.toBytes(valueStr)); + p.addColumn(cfBytes, Bytes.toBytes(qualStr), ts++, Bytes.toBytes(valueStr)); } } } @@ -459,20 +469,27 @@ public class TestCacheOnWrite { } region.flushcache(); } - LruBlockCache blockCache = - (LruBlockCache) new CacheConfig(conf).getBlockCache(); - blockCache.clearCache(); - assertEquals(0, blockCache.getBlockTypeCountsForTest().size()); + clearBlockCache(blockCache); + assertEquals(0, blockCache.getBlockCount()); region.compactStores(); LOG.debug("compactStores() returned"); - Map blockTypesInCache = - blockCache.getBlockTypeCountsForTest(); - LOG.debug("Block types in cache: " + blockTypesInCache); - assertNull(blockTypesInCache.get(BlockType.ENCODED_DATA)); - assertNull(blockTypesInCache.get(BlockType.DATA)); + for (CachedBlock block: blockCache) { + assertNotEquals(BlockType.ENCODED_DATA, block.getBlockType()); + assertNotEquals(BlockType.DATA, block.getBlockType()); + } region.close(); - blockCache.shutdown(); + } + + @Test + public void testStoreFileCacheOnWrite() throws IOException { + testStoreFileCacheOnWriteInternals(false); + testStoreFileCacheOnWriteInternals(true); + } + + @Test + public void testNotCachingDataBlocksDuringCompaction() throws IOException, InterruptedException { + testNotCachingDataBlocksDuringCompactionInternals(false); + testNotCachingDataBlocksDuringCompactionInternals(true); } } - From 9a311303a88d355f8c982d2f3cc77bd6427dfb80 Mon Sep 17 00:00:00 2001 From: tedyu Date: Fri, 20 Feb 2015 10:18:47 -0800 Subject: [PATCH 046/329] HBASE-13057 Provide client utility to easily enable and disable table replication (Ashish Singhi) --- .../client/replication/ReplicationAdmin.java | 207 ++++++++++++++++++ .../TestReplicationAdminWithClusters.java | 163 ++++++++++++++ .../src/main/ruby/hbase/replication_admin.rb | 13 ++ hbase-shell/src/main/ruby/shell.rb | 2 + .../commands/disable_table_replication.rb | 42 ++++ .../commands/enable_table_replication.rb | 42 ++++ 6 files changed, 469 insertions(+) create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/client/replication/TestReplicationAdminWithClusters.java create mode 100644 hbase-shell/src/main/ruby/shell/commands/disable_table_replication.rb create mode 100644 hbase-shell/src/main/ruby/shell/commands/enable_table_replication.rb diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/replication/ReplicationAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/replication/ReplicationAdmin.java index 2d5c5e9e63f..ca66fb3ec45 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/replication/ReplicationAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/replication/ReplicationAdmin.java @@ -38,17 +38,25 @@ import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.RegionLocator; import org.apache.hadoop.hbase.replication.ReplicationException; import org.apache.hadoop.hbase.replication.ReplicationFactory; +import org.apache.hadoop.hbase.replication.ReplicationPeer; import org.apache.hadoop.hbase.replication.ReplicationPeerConfig; +import org.apache.hadoop.hbase.replication.ReplicationPeerZKImpl; import org.apache.hadoop.hbase.replication.ReplicationPeers; import org.apache.hadoop.hbase.replication.ReplicationQueuesClient; +import org.apache.hadoop.hbase.util.Pair; +import org.apache.hadoop.hbase.zookeeper.ZKUtil; import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.data.Stat; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; @@ -501,4 +509,203 @@ public class ReplicationAdmin implements Closeable { return replicationColFams; } + + /** + * Enable a table's replication switch. + * @param tableName name of the table + * @throws IOException if a remote or network exception occurs + */ + public void enableTableRep(final TableName tableName) throws IOException { + if (tableName == null) { + throw new IllegalArgumentException("Table name cannot be null"); + } + try (Admin admin = this.connection.getAdmin()) { + if (!admin.tableExists(tableName)) { + throw new TableNotFoundException("Table '" + tableName.getNameAsString() + + "' does not exists."); + } + } + byte[][] splits = getTableSplitRowKeys(tableName); + checkAndSyncTableDescToPeers(tableName, splits); + setTableRep(tableName, true); + } + + /** + * Disable a table's replication switch. + * @param tableName name of the table + * @throws IOException if a remote or network exception occurs + */ + public void disableTableRep(final TableName tableName) throws IOException { + if (tableName == null) { + throw new IllegalArgumentException("Table name is null"); + } + try (Admin admin = this.connection.getAdmin()) { + if (!admin.tableExists(tableName)) { + throw new TableNotFoundException("Table '" + tableName.getNamespaceAsString() + + "' does not exists."); + } + } + setTableRep(tableName, false); + } + + /** + * Get the split row keys of table + * @param tableName table name + * @return array of split row keys + * @throws IOException + */ + private byte[][] getTableSplitRowKeys(TableName tableName) throws IOException { + try (RegionLocator locator = connection.getRegionLocator(tableName);) { + byte[][] startKeys = locator.getStartKeys(); + if (startKeys.length == 1) { + return null; + } + byte[][] splits = new byte[startKeys.length - 1][]; + for (int i = 1; i < startKeys.length; i++) { + splits[i - 1] = startKeys[i]; + } + return splits; + } + } + + /** + * Connect to peer and check the table descriptor on peer: + *
      + *
    1. Create the same table on peer when not exist.
    2. + *
    3. Throw exception if the table exists on peer cluster but descriptors are not same.
    4. + *
    + * @param tableName name of the table to sync to the peer + * @param splits table split keys + * @throws IOException + */ + private void checkAndSyncTableDescToPeers(final TableName tableName, final byte[][] splits) + throws IOException { + List repPeers = listValidReplicationPeers(); + if (repPeers == null || repPeers.size() <= 0) { + throw new IllegalArgumentException("Found no peer cluster for replication."); + } + for (ReplicationPeer repPeer : repPeers) { + Configuration peerConf = repPeer.getConfiguration(); + HTableDescriptor htd = null; + try (Connection conn = ConnectionFactory.createConnection(peerConf); + Admin admin = this.connection.getAdmin(); + Admin repHBaseAdmin = conn.getAdmin()) { + htd = admin.getTableDescriptor(tableName); + HTableDescriptor peerHtd = null; + if (!repHBaseAdmin.tableExists(tableName)) { + repHBaseAdmin.createTable(htd, splits); + } else { + peerHtd = repHBaseAdmin.getTableDescriptor(tableName); + if (peerHtd == null) { + throw new IllegalArgumentException("Failed to get table descriptor for table " + + tableName.getNameAsString() + " from peer cluster " + repPeer.getId()); + } else if (!peerHtd.equals(htd)) { + throw new IllegalArgumentException("Table " + tableName.getNameAsString() + + " exists in peer cluster " + repPeer.getId() + + ", but the table descriptors are not same when comapred with source cluster." + + " Thus can not enable the table's replication switch."); + } + } + } + } + } + + private List listValidReplicationPeers() { + Map peers = listPeerConfigs(); + if (peers == null || peers.size() <= 0) { + return null; + } + List validPeers = new ArrayList(peers.size()); + for (Entry peerEntry : peers.entrySet()) { + String peerId = peerEntry.getKey(); + String clusterKey = peerEntry.getValue().getClusterKey(); + Configuration peerConf = new Configuration(this.connection.getConfiguration()); + Stat s = null; + try { + ZKUtil.applyClusterKeyToConf(peerConf, clusterKey); + Pair pair = this.replicationPeers.getPeerConf(peerId); + ReplicationPeer peer = new ReplicationPeerZKImpl(peerConf, peerId, pair.getFirst()); + s = + zkw.getRecoverableZooKeeper().exists(peerConf.get(HConstants.ZOOKEEPER_ZNODE_PARENT), + null); + if (null == s) { + LOG.info(peerId + ' ' + clusterKey + " is invalid now."); + continue; + } + validPeers.add(peer); + } catch (ReplicationException e) { + LOG.warn("Failed to get valid replication peers. " + + "Error connecting to peer cluster with peerId=" + peerId); + LOG.debug("Failure details to get valid replication peers.", e); + continue; + } catch (KeeperException e) { + LOG.warn("Failed to get valid replication peers. KeeperException code=" + + e.code().intValue()); + LOG.debug("Failure details to get valid replication peers.", e); + continue; + } catch (InterruptedException e) { + LOG.warn("Failed to get valid replication peers due to InterruptedException."); + LOG.debug("Failure details to get valid replication peers.", e); + Thread.currentThread().interrupt(); + continue; + } catch (IOException e) { + LOG.warn("Failed to get valid replication peers due to IOException."); + LOG.debug("Failure details to get valid replication peers.", e); + continue; + } + } + return validPeers; + } + + /** + * Set the table's replication switch if the table's replication switch is already not set. + * @param tableName name of the table + * @param isRepEnabled is replication switch enable or disable + * @throws IOException if a remote or network exception occurs + */ + private void setTableRep(final TableName tableName, boolean isRepEnabled) throws IOException { + Admin admin = null; + try { + admin = this.connection.getAdmin(); + HTableDescriptor htd = admin.getTableDescriptor(tableName); + if (isTableRepEnabled(htd) ^ isRepEnabled) { + boolean isOnlineSchemaUpdateEnabled = + this.connection.getConfiguration() + .getBoolean("hbase.online.schema.update.enable", true); + if (!isOnlineSchemaUpdateEnabled) { + admin.disableTable(tableName); + } + for (HColumnDescriptor hcd : htd.getFamilies()) { + hcd.setScope(isRepEnabled ? HConstants.REPLICATION_SCOPE_GLOBAL + : HConstants.REPLICATION_SCOPE_LOCAL); + } + admin.modifyTable(tableName, htd); + if (!isOnlineSchemaUpdateEnabled) { + admin.enableTable(tableName); + } + } + } finally { + if (admin != null) { + try { + admin.close(); + } catch (IOException e) { + LOG.warn("Failed to close admin connection."); + LOG.debug("Details on failure to close admin connection.", e); + } + } + } + } + + /** + * @param htd table descriptor details for the table to check + * @return true if table's replication switch is enabled + */ + private boolean isTableRepEnabled(HTableDescriptor htd) { + for (HColumnDescriptor hcd : htd.getFamilies()) { + if (hcd.getScope() != HConstants.REPLICATION_SCOPE_GLOBAL) { + return false; + } + } + return true; + } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/replication/TestReplicationAdminWithClusters.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/replication/TestReplicationAdminWithClusters.java new file mode 100644 index 00000000000..b5899b814f1 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/replication/TestReplicationAdminWithClusters.java @@ -0,0 +1,163 @@ +/** + * 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.hbase.client.replication; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.TableNotFoundException; +import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.replication.TestReplicationBase; +import org.apache.hadoop.hbase.testclassification.ClientTests; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +/** + * Unit testing of ReplicationAdmin with clusters + */ +@Category({ MediumTests.class, ClientTests.class }) +public class TestReplicationAdminWithClusters extends TestReplicationBase { + + static Connection connection1; + static Connection connection2; + static Admin admin1; + static Admin admin2; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + TestReplicationBase.setUpBeforeClass(); + connection1 = ConnectionFactory.createConnection(conf1); + connection2 = ConnectionFactory.createConnection(conf2); + admin1 = connection1.getAdmin(); + admin2 = connection2.getAdmin(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + admin1.close(); + admin2.close(); + connection1.close(); + connection2.close(); + TestReplicationBase.tearDownAfterClass(); + } + + @Test(timeout = 300000) + public void testEnableReplicationWhenSlaveClusterDoesntHaveTable() throws Exception { + admin2.disableTable(tableName); + admin2.deleteTable(tableName); + assertFalse(admin2.tableExists(tableName)); + ReplicationAdmin adminExt = new ReplicationAdmin(conf1); + adminExt.enableTableRep(tableName); + assertTrue(admin2.tableExists(tableName)); + } + + @Test(timeout = 300000) + public void testEnableReplicationWhenReplicationNotEnabled() throws Exception { + HTableDescriptor table = admin1.getTableDescriptor(tableName); + for (HColumnDescriptor fam : table.getColumnFamilies()) { + fam.setScope(HConstants.REPLICATION_SCOPE_LOCAL); + } + admin1.disableTable(tableName); + admin1.modifyTable(tableName, table); + admin1.enableTable(tableName); + + admin2.disableTable(tableName); + admin2.modifyTable(tableName, table); + admin2.enableTable(tableName); + + ReplicationAdmin adminExt = new ReplicationAdmin(conf1); + adminExt.enableTableRep(tableName); + table = admin1.getTableDescriptor(tableName); + for (HColumnDescriptor fam : table.getColumnFamilies()) { + assertEquals(fam.getScope(), HConstants.REPLICATION_SCOPE_GLOBAL); + } + } + + @Test(timeout = 300000) + public void testEnableReplicationWhenTableDescriptorIsNotSameInClusters() throws Exception { + HTableDescriptor table = admin2.getTableDescriptor(tableName); + HColumnDescriptor f = new HColumnDescriptor("newFamily"); + table.addFamily(f); + admin2.disableTable(tableName); + admin2.modifyTable(tableName, table); + admin2.enableTable(tableName); + + ReplicationAdmin adminExt = new ReplicationAdmin(conf1); + try { + adminExt.enableTableRep(tableName); + fail("Exception should be thrown if table descriptors in the clusters are not same."); + } catch (RuntimeException ignored) { + + } + admin1.disableTable(tableName); + admin1.modifyTable(tableName, table); + admin1.enableTable(tableName); + adminExt.enableTableRep(tableName); + table = admin1.getTableDescriptor(tableName); + for (HColumnDescriptor fam : table.getColumnFamilies()) { + assertEquals(fam.getScope(), HConstants.REPLICATION_SCOPE_GLOBAL); + } + } + + @Test(timeout = 300000) + public void testDisableAndEnableReplication() throws Exception { + ReplicationAdmin adminExt = new ReplicationAdmin(conf1); + adminExt.disableTableRep(tableName); + HTableDescriptor table = admin1.getTableDescriptor(tableName); + for (HColumnDescriptor fam : table.getColumnFamilies()) { + assertEquals(fam.getScope(), HConstants.REPLICATION_SCOPE_LOCAL); + } + table = admin2.getTableDescriptor(tableName); + for (HColumnDescriptor fam : table.getColumnFamilies()) { + assertEquals(fam.getScope(), HConstants.REPLICATION_SCOPE_LOCAL); + } + adminExt.enableTableRep(tableName); + table = admin1.getTableDescriptor(tableName); + for (HColumnDescriptor fam : table.getColumnFamilies()) { + assertEquals(fam.getScope(), HConstants.REPLICATION_SCOPE_GLOBAL); + } + } + + @Test(timeout = 300000, expected = TableNotFoundException.class) + public void testDisableReplicationForNonExistingTable() throws Exception { + ReplicationAdmin adminExt = new ReplicationAdmin(conf1); + adminExt.disableTableRep(TableName.valueOf("nonExistingTable")); + } + + @Test(timeout = 300000, expected = TableNotFoundException.class) + public void testEnableReplicationForNonExistingTable() throws Exception { + ReplicationAdmin adminExt = new ReplicationAdmin(conf1); + adminExt.enableTableRep(TableName.valueOf("nonExistingTable")); + } + + @Test(timeout = 300000, expected = IllegalArgumentException.class) + public void testDisableReplicationWhenTableNameAsNull() throws Exception { + ReplicationAdmin adminExt = new ReplicationAdmin(conf1); + adminExt.disableTableRep(null); + } + + @Test(timeout = 300000, expected = IllegalArgumentException.class) + public void testEnableReplicationWhenTableNameAsNull() throws Exception { + ReplicationAdmin adminExt = new ReplicationAdmin(conf1); + adminExt.enableTableRep(null); + } +} diff --git a/hbase-shell/src/main/ruby/hbase/replication_admin.rb b/hbase-shell/src/main/ruby/hbase/replication_admin.rb index 2d0845f5625..b2ca8e1a6e9 100644 --- a/hbase-shell/src/main/ruby/hbase/replication_admin.rb +++ b/hbase-shell/src/main/ruby/hbase/replication_admin.rb @@ -23,6 +23,7 @@ java_import org.apache.hadoop.hbase.client.replication.ReplicationAdmin java_import org.apache.hadoop.hbase.replication.ReplicationPeerConfig java_import org.apache.hadoop.hbase.util.Bytes java_import org.apache.hadoop.hbase.zookeeper.ZKUtil +java_import org.apache.hadoop.hbase.TableName # Wrapper for org.apache.hadoop.hbase.client.replication.ReplicationAdmin @@ -157,5 +158,17 @@ module Hbase def remove_peer_tableCFs(id, tableCFs) @replication_admin.removePeerTableCFs(id, tableCFs) end + #---------------------------------------------------------------------------------------------- + # Enables a table's replication switch + def enable_tablerep(table_name) + tableName = TableName.valueOf(table_name) + @replication_admin.enableTableRep(tableName) + end + #---------------------------------------------------------------------------------------------- + # Disables a table's replication switch + def disable_tablerep(table_name) + tableName = TableName.valueOf(table_name) + @replication_admin.disableTableRep(tableName) + end end end diff --git a/hbase-shell/src/main/ruby/shell.rb b/hbase-shell/src/main/ruby/shell.rb index 5db2776357d..893079de1b0 100644 --- a/hbase-shell/src/main/ruby/shell.rb +++ b/hbase-shell/src/main/ruby/shell.rb @@ -349,6 +349,8 @@ Shell.load_command_group( list_replicated_tables append_peer_tableCFs remove_peer_tableCFs + enable_table_replication + disable_table_replication ] ) diff --git a/hbase-shell/src/main/ruby/shell/commands/disable_table_replication.rb b/hbase-shell/src/main/ruby/shell/commands/disable_table_replication.rb new file mode 100644 index 00000000000..4c46feac3cd --- /dev/null +++ b/hbase-shell/src/main/ruby/shell/commands/disable_table_replication.rb @@ -0,0 +1,42 @@ +# +# Copyright 2010 The Apache Software Foundation +# +# 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. +# + +module Shell + module Commands + class DisableTableReplication< Command + def help + return <<-EOF +Disable a table's replication switch. + +Examples: + + hbase> disable_table_replication 'table_name' +EOF + end + + def command(table_name) + format_simple_command do + replication_admin.disable_tablerep(table_name) + end + puts "The replication swith of table '#{table_name}' successfully disabled" + end + end + end +end diff --git a/hbase-shell/src/main/ruby/shell/commands/enable_table_replication.rb b/hbase-shell/src/main/ruby/shell/commands/enable_table_replication.rb new file mode 100644 index 00000000000..5d57f03fb54 --- /dev/null +++ b/hbase-shell/src/main/ruby/shell/commands/enable_table_replication.rb @@ -0,0 +1,42 @@ +# +# Copyright 2010 The Apache Software Foundation +# +# 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. +# + +module Shell + module Commands + class EnableTableReplication< Command + def help + return <<-EOF +Enable a table's replication switch. + +Examples: + + hbase> enable_table_replication 'table_name' +EOF + end + + def command(table_name) + format_simple_command do + replication_admin.enable_tablerep(table_name) + end + puts "The replication swith of table '#{table_name}' successfully enabled" + end + end + end +end From a8555f65f0551569f07f6a7d207ddc0e56fad03a Mon Sep 17 00:00:00 2001 From: Enis Soztutar Date: Fri, 20 Feb 2015 16:28:47 -0800 Subject: [PATCH 047/329] HBASE-13081 Branch precommit builds are not updating to branch head before patch application --- dev-support/test-patch.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dev-support/test-patch.sh b/dev-support/test-patch.sh index 117a872a799..3c356006af1 100755 --- a/dev-support/test-patch.sh +++ b/dev-support/test-patch.sh @@ -230,8 +230,9 @@ checkoutBranch() { echo "" if [[ $JENKINS == "true" ]] ; then if [[ "$BRANCH_NAME" != "master" ]]; then - echo "${GIT} checkout ${BRANCH_NAME}" - ${GIT} checkout ${BRANCH_NAME} + echo "origin/${BRANCH_NAME} HEAD is commit `${GIT} rev-list origin/${BRANCH_NAME} -1`" + echo "${GIT} checkout -f `${GIT} rev-list origin/${BRANCH_NAME} -1`" + ${GIT} checkout -f `${GIT} rev-list origin/${BRANCH_NAME} -1` echo "${GIT} status" ${GIT} status fi From 7af56998c5d42afc84ddf8c81126331715ef6ccf Mon Sep 17 00:00:00 2001 From: stack Date: Fri, 20 Feb 2015 21:57:30 -0800 Subject: [PATCH 048/329] HBASE-13032 Migration of states should be performed once META is assigned and onlined (Andrey Stepachev) --- .../apache/hadoop/hbase/master/HMaster.java | 16 ++-- .../hbase/master/TestTableStateManager.java | 82 +++++++++++++++++++ 2 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestTableStateManager.java diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java index 61a1c6654f9..8df50d89385 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java @@ -566,13 +566,6 @@ public class HMaster extends HRegionServer implements MasterServices, Server { this.mpmHost.loadProcedures(conf); this.mpmHost.initialize(this, this.metricsMaster); - // migrating existent table state from zk - for (Map.Entry entry : ZKDataMigrator - .queryForTableStates(getZooKeeper()).entrySet()) { - LOG.info("Converting state from zk to new states:" + entry); - tableStateManager.setTableState(entry.getKey(), entry.getValue()); - } - ZKUtil.deleteChildrenRecursively(getZooKeeper(), getZooKeeper().tableZNode); } /** @@ -711,6 +704,15 @@ public class HMaster extends HRegionServer implements MasterServices, Server { // assigned when master is shutting down if(isStopped()) return; + // migrating existent table state from zk, so splitters + // and recovery process treat states properly. + for (Map.Entry entry : ZKDataMigrator + .queryForTableStates(getZooKeeper()).entrySet()) { + LOG.info("Converting state from zk to new states:" + entry); + tableStateManager.setTableState(entry.getKey(), entry.getValue()); + } + ZKUtil.deleteChildrenRecursively(getZooKeeper(), getZooKeeper().tableZNode); + status.setStatus("Submitting log splitting work for previously failed region servers"); // Master has recovered hbase:meta region server and we put // other failed region servers in a queue to be handled later by SSH diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestTableStateManager.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestTableStateManager.java new file mode 100644 index 00000000000..e0e969ed23d --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestTableStateManager.java @@ -0,0 +1,82 @@ +/* + * Copyright The Apache Software Foundation + * + * 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.hbase.master; + +import java.io.IOException; + +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.TableState; +import org.apache.hadoop.hbase.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos; +import org.apache.hadoop.hbase.testclassification.LargeTests; +import org.apache.hadoop.hbase.testclassification.MasterTests; +import org.apache.hadoop.hbase.zookeeper.ZKUtil; +import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; +import org.apache.zookeeper.KeeperException; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +/** + * Tests the default table lock manager + */ +@Category({ MasterTests.class, LargeTests.class }) +public class TestTableStateManager { + + private final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + + @After + public void tearDown() throws Exception { + TEST_UTIL.shutdownMiniCluster(); + } + + @Test(timeout = 60000) + public void testUpgradeFromZk() throws Exception { + TableName tableName = + TableName.valueOf("testUpgradeFromZk"); + TEST_UTIL.startMiniCluster(2, 1); + TEST_UTIL.shutdownMiniHBaseCluster(); + ZooKeeperWatcher watcher = TEST_UTIL.getZooKeeperWatcher(); + setTableStateInZK(watcher, tableName, ZooKeeperProtos.DeprecatedTableState.State.DISABLED); + TEST_UTIL.restartHBaseCluster(1); + + HMaster master = TEST_UTIL.getHBaseCluster().getMaster(); + Assert.assertEquals( + master.getTableStateManager().getTableState(tableName), + TableState.State.DISABLED); + } + + private void setTableStateInZK(ZooKeeperWatcher watcher, final TableName tableName, + final ZooKeeperProtos.DeprecatedTableState.State state) + throws KeeperException, IOException { + String znode = ZKUtil.joinZNode(watcher.tableZNode, tableName.getNameAsString()); + if (ZKUtil.checkExists(watcher, znode) == -1) { + ZKUtil.createAndFailSilent(watcher, znode); + } + ZooKeeperProtos.DeprecatedTableState.Builder builder = + ZooKeeperProtos.DeprecatedTableState.newBuilder(); + builder.setState(state); + byte[] data = ProtobufUtil.prependPBMagic(builder.build().toByteArray()); + ZKUtil.setData(watcher, znode, data); + } + +} From 47d081407ed586b881490200a709bcc451a6268d Mon Sep 17 00:00:00 2001 From: stack Date: Fri, 20 Feb 2015 22:36:42 -0800 Subject: [PATCH 049/329] HBASE-13001 NullPointer in master logs for table.jsp (Vikas Vishwakarma) --- .../resources/hbase-webapps/master/table.jsp | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/hbase-server/src/main/resources/hbase-webapps/master/table.jsp b/hbase-server/src/main/resources/hbase-webapps/master/table.jsp index 274f6605fa3..110330097fe 100644 --- a/hbase-server/src/main/resources/hbase-webapps/master/table.jsp +++ b/hbase-server/src/main/resources/hbase-webapps/master/table.jsp @@ -43,15 +43,9 @@ MetaTableLocator metaTableLocator = new MetaTableLocator(); String fqtn = request.getParameter("name"); - HTable table = (HTable) master.getConnection().getTable(fqtn); + HTable table = null; String tableHeader; boolean withReplica = false; - if (table.getTableDescriptor().getRegionReplication() > 1) { - tableHeader = "

    Table Regions

    "; - withReplica = true; - } else { - tableHeader = "

    Table Regions

    NameRegion ServerStart KeyEnd KeyLocalityRequestsReplicaID
    "; - } ServerName rl = metaTableLocator.getMetaRegionLocation(master.getZooKeeper()); boolean showFragmentation = conf.getBoolean("hbase.master.ui.fragmentation.enabled", false); boolean readOnly = conf.getBoolean("hbase.master.ui.readonly", false); @@ -84,7 +78,7 @@ - <% if ( !readOnly && action != null ) { %> + <% if ( ( !readOnly && action != null ) || fqtn == null ) { %>
    NameRegion ServerStart KeyEnd KeyLocalityRequests