Skip to content

Commit 44c78f2

Browse files
committed
HBASE-22624 Should sanity check table configuration when clone snapshot to a new table
1 parent 0c8dc5d commit 44c78f2

18 files changed

+348
-295
lines changed

hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestRegionReplicaReplication.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.hadoop.hbase.util.MultiThreadedUpdater;
3030
import org.apache.hadoop.hbase.util.MultiThreadedWriter;
3131
import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
32+
import org.apache.hadoop.hbase.util.TableDescriptorChecker;
3233
import org.apache.hadoop.hbase.util.Threads;
3334
import org.apache.hadoop.hbase.util.test.LoadTestDataGenerator;
3435
import org.apache.hadoop.util.StringUtils;
@@ -94,7 +95,7 @@ public void setConf(Configuration conf) {
9495
String.format("%s.%s", TEST_NAME, LoadTestTool.OPT_COLUMN_FAMILIES),
9596
StringUtils.join(",", DEFAULT_COLUMN_FAMILIES));
9697

97-
conf.setBoolean("hbase.table.sanity.checks", true);
98+
conf.setBoolean(TableDescriptorChecker.TABLE_SANITY_CHECKS, true);
9899

99100
// enable async wal replication to region replicas for unit tests
100101
conf.setBoolean(ServerRegionReplicaUtil.REGION_REPLICA_REPLICATION_CONF_KEY, true);

hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java

Lines changed: 12 additions & 269 deletions
Large diffs are not rendered by default.

hbase-server/src/main/java/org/apache/hadoop/hbase/master/snapshot/SnapshotManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
8686
import org.apache.hadoop.hbase.util.FSUtils;
8787
import org.apache.hadoop.hbase.util.NonceKey;
88+
import org.apache.hadoop.hbase.util.TableDescriptorChecker;
8889
import org.apache.yetus.audience.InterfaceAudience;
8990
import org.apache.yetus.audience.InterfaceStability;
9091
import org.apache.zookeeper.KeeperException;
@@ -826,6 +827,9 @@ public long restoreOrCloneSnapshot(final SnapshotDescription reqSnapshot, final
826827
TableDescriptor snapshotTableDesc = manifest.getTableDescriptor();
827828
TableName tableName = TableName.valueOf(reqSnapshot.getTable());
828829

830+
// sanity check the new table descriptor
831+
TableDescriptorChecker.sanityCheck(master.getConfiguration(), snapshotTableDesc);
832+
829833
// stop tracking "abandoned" handlers
830834
cleanupSentinels();
831835

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.util;
19+
20+
import java.io.IOException;
21+
22+
import org.apache.hadoop.conf.Configuration;
23+
import org.apache.hadoop.hbase.CompoundConfiguration;
24+
import org.apache.hadoop.hbase.DoNotRetryIOException;
25+
import org.apache.hadoop.hbase.HConstants;
26+
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
27+
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
28+
import org.apache.hadoop.hbase.client.TableDescriptor;
29+
import org.apache.hadoop.hbase.master.HMaster;
30+
import org.apache.hadoop.hbase.regionserver.DefaultStoreEngine;
31+
import org.apache.hadoop.hbase.regionserver.HStore;
32+
import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
33+
import org.apache.hadoop.hbase.regionserver.RegionSplitPolicy;
34+
import org.apache.hadoop.hbase.regionserver.compactions.ExploringCompactionPolicy;
35+
import org.apache.hadoop.hbase.regionserver.compactions.FIFOCompactionPolicy;
36+
import org.apache.yetus.audience.InterfaceAudience;
37+
import org.slf4j.Logger;
38+
import org.slf4j.LoggerFactory;
39+
40+
import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos;
41+
42+
/**
43+
* Used for master to sanity check {@link org.apache.hadoop.hbase.client.TableDescriptor}.
44+
*/
45+
@InterfaceAudience.Private
46+
public final class TableDescriptorChecker {
47+
private static Logger LOG = LoggerFactory.getLogger(TableDescriptorChecker.class);
48+
49+
public static final String TABLE_SANITY_CHECKS = "hbase.table.sanity.checks";
50+
public static final boolean DEFAULT_TABLE_SANITY_CHECKS = true;
51+
52+
private TableDescriptorChecker() {
53+
}
54+
55+
/**
56+
* Checks whether the table conforms to some sane limits, and configured
57+
* values (compression, etc) work. Throws an exception if something is wrong.
58+
*/
59+
public static void sanityCheck(final Configuration conf, final TableDescriptor td)
60+
throws IOException {
61+
sanityCheck(conf, td,
62+
conf.getBoolean(HMaster.MASTER_CHECK_COMPRESSION, HMaster.DEFAULT_MASTER_CHECK_COMPRESSION),
63+
conf.getBoolean(HMaster.MASTER_CHECK_ENCRYPTION, HMaster.DEFAULT_MASTER_CHECK_ENCRYPTION));
64+
}
65+
66+
/**
67+
* Checks whether the table conforms to some sane limits, and configured
68+
* values (compression, etc) work. Throws an exception if something is wrong.
69+
*/
70+
public static void sanityCheck(final Configuration conf, final TableDescriptor td,
71+
boolean checkCompression, boolean checkEncryption) throws IOException {
72+
// Setting this to true logs the warning instead of throwing exception
73+
boolean logWarn = false;
74+
if (!conf.getBoolean(TABLE_SANITY_CHECKS, DEFAULT_TABLE_SANITY_CHECKS)) {
75+
logWarn = true;
76+
}
77+
String tableVal = td.getValue(TABLE_SANITY_CHECKS);
78+
if (tableVal != null && !Boolean.valueOf(tableVal)) {
79+
logWarn = true;
80+
}
81+
82+
// check max file size
83+
long maxFileSizeLowerLimit = 2 * 1024 * 1024L; // 2M is the default lower limit
84+
long maxFileSize = td.getMaxFileSize();
85+
if (maxFileSize < 0) {
86+
maxFileSize = conf.getLong(HConstants.HREGION_MAX_FILESIZE, maxFileSizeLowerLimit);
87+
}
88+
if (maxFileSize < conf.getLong("hbase.hregion.max.filesize.limit", maxFileSizeLowerLimit)) {
89+
String message =
90+
"MAX_FILESIZE for table descriptor or " + "\"hbase.hregion.max.filesize\" (" +
91+
maxFileSize + ") is too small, which might cause over splitting into unmanageable " +
92+
"number of regions.";
93+
warnOrThrowExceptionForFailure(logWarn, message, null);
94+
}
95+
96+
// check flush size
97+
long flushSizeLowerLimit = 1024 * 1024L; // 1M is the default lower limit
98+
long flushSize = td.getMemStoreFlushSize();
99+
if (flushSize < 0) {
100+
flushSize = conf.getLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, flushSizeLowerLimit);
101+
}
102+
if (flushSize < conf.getLong("hbase.hregion.memstore.flush.size.limit", flushSizeLowerLimit)) {
103+
String message = "MEMSTORE_FLUSHSIZE for table descriptor or " +
104+
"\"hbase.hregion.memstore.flush.size\" (" + flushSize +
105+
") is too small, which might cause" + " very frequent flushing.";
106+
warnOrThrowExceptionForFailure(logWarn, message, null);
107+
}
108+
109+
// check that coprocessors and other specified plugin classes can be loaded
110+
try {
111+
checkClassLoading(conf, td);
112+
} catch (Exception ex) {
113+
warnOrThrowExceptionForFailure(logWarn, ex.getMessage(), null);
114+
}
115+
116+
if (checkCompression) {
117+
// check compression can be loaded
118+
try {
119+
checkCompression(td);
120+
} catch (IOException e) {
121+
warnOrThrowExceptionForFailure(logWarn, e.getMessage(), e);
122+
}
123+
}
124+
125+
if (checkEncryption) {
126+
// check encryption can be loaded
127+
try {
128+
checkEncryption(conf, td);
129+
} catch (IOException e) {
130+
warnOrThrowExceptionForFailure(logWarn, e.getMessage(), e);
131+
}
132+
}
133+
134+
// Verify compaction policy
135+
try {
136+
checkCompactionPolicy(conf, td);
137+
} catch (IOException e) {
138+
warnOrThrowExceptionForFailure(false, e.getMessage(), e);
139+
}
140+
// check that we have at least 1 CF
141+
if (td.getColumnFamilyCount() == 0) {
142+
String message = "Table should have at least one column family.";
143+
warnOrThrowExceptionForFailure(logWarn, message, null);
144+
}
145+
146+
// check that we have minimum 1 region replicas
147+
int regionReplicas = td.getRegionReplication();
148+
if (regionReplicas < 1) {
149+
String message = "Table region replication should be at least one.";
150+
warnOrThrowExceptionForFailure(logWarn, message, null);
151+
}
152+
153+
for (ColumnFamilyDescriptor hcd : td.getColumnFamilies()) {
154+
if (hcd.getTimeToLive() <= 0) {
155+
String message = "TTL for column family " + hcd.getNameAsString() + " must be positive.";
156+
warnOrThrowExceptionForFailure(logWarn, message, null);
157+
}
158+
159+
// check blockSize
160+
if (hcd.getBlocksize() < 1024 || hcd.getBlocksize() > 16 * 1024 * 1024) {
161+
String message = "Block size for column family " + hcd.getNameAsString() +
162+
" must be between 1K and 16MB.";
163+
warnOrThrowExceptionForFailure(logWarn, message, null);
164+
}
165+
166+
// check versions
167+
if (hcd.getMinVersions() < 0) {
168+
String message =
169+
"Min versions for column family " + hcd.getNameAsString() + " must be positive.";
170+
warnOrThrowExceptionForFailure(logWarn, message, null);
171+
}
172+
// max versions already being checked
173+
174+
// HBASE-13776 Setting illegal versions for ColumnFamilyDescriptor
175+
// does not throw IllegalArgumentException
176+
// check minVersions <= maxVerions
177+
if (hcd.getMinVersions() > hcd.getMaxVersions()) {
178+
String message = "Min versions for column family " + hcd.getNameAsString() +
179+
" must be less than the Max versions.";
180+
warnOrThrowExceptionForFailure(logWarn, message, null);
181+
}
182+
183+
// check replication scope
184+
checkReplicationScope(hcd);
185+
// check bloom filter type
186+
checkBloomFilterType(hcd);
187+
188+
// check data replication factor, it can be 0(default value) when user has not explicitly
189+
// set the value, in this case we use default replication factor set in the file system.
190+
if (hcd.getDFSReplication() < 0) {
191+
String message = "HFile Replication for column family " + hcd.getNameAsString() +
192+
" must be greater than zero.";
193+
warnOrThrowExceptionForFailure(logWarn, message, null);
194+
}
195+
}
196+
}
197+
198+
private static void checkReplicationScope(final ColumnFamilyDescriptor cfd) throws IOException {
199+
// check replication scope
200+
WALProtos.ScopeType scop = WALProtos.ScopeType.valueOf(cfd.getScope());
201+
if (scop == null) {
202+
String message =
203+
"Replication scope for column family " + cfd.getNameAsString() + " is " + cfd.getScope() +
204+
" which is invalid.";
205+
206+
LOG.error(message);
207+
throw new DoNotRetryIOException(message);
208+
}
209+
}
210+
211+
private static void checkCompactionPolicy(Configuration conf, TableDescriptor td)
212+
throws IOException {
213+
// FIFO compaction has some requirements
214+
// Actually FCP ignores periodic major compactions
215+
String className = td.getValue(DefaultStoreEngine.DEFAULT_COMPACTION_POLICY_CLASS_KEY);
216+
if (className == null) {
217+
className = conf.get(DefaultStoreEngine.DEFAULT_COMPACTION_POLICY_CLASS_KEY,
218+
ExploringCompactionPolicy.class.getName());
219+
}
220+
221+
int blockingFileCount = HStore.DEFAULT_BLOCKING_STOREFILE_COUNT;
222+
String sv = td.getValue(HStore.BLOCKING_STOREFILES_KEY);
223+
if (sv != null) {
224+
blockingFileCount = Integer.parseInt(sv);
225+
} else {
226+
blockingFileCount = conf.getInt(HStore.BLOCKING_STOREFILES_KEY, blockingFileCount);
227+
}
228+
229+
for (ColumnFamilyDescriptor hcd : td.getColumnFamilies()) {
230+
String compactionPolicy =
231+
hcd.getConfigurationValue(DefaultStoreEngine.DEFAULT_COMPACTION_POLICY_CLASS_KEY);
232+
if (compactionPolicy == null) {
233+
compactionPolicy = className;
234+
}
235+
if (!compactionPolicy.equals(FIFOCompactionPolicy.class.getName())) {
236+
continue;
237+
}
238+
// FIFOCompaction
239+
String message = null;
240+
241+
// 1. Check TTL
242+
if (hcd.getTimeToLive() == ColumnFamilyDescriptorBuilder.DEFAULT_TTL) {
243+
message = "Default TTL is not supported for FIFO compaction";
244+
throw new IOException(message);
245+
}
246+
247+
// 2. Check min versions
248+
if (hcd.getMinVersions() > 0) {
249+
message = "MIN_VERSION > 0 is not supported for FIFO compaction";
250+
throw new IOException(message);
251+
}
252+
253+
// 3. blocking file count
254+
sv = hcd.getConfigurationValue(HStore.BLOCKING_STOREFILES_KEY);
255+
if (sv != null) {
256+
blockingFileCount = Integer.parseInt(sv);
257+
}
258+
if (blockingFileCount < 1000) {
259+
message =
260+
"Blocking file count '" + HStore.BLOCKING_STOREFILES_KEY + "' " + blockingFileCount +
261+
" is below recommended minimum of 1000 for column family " + hcd.getNameAsString();
262+
throw new IOException(message);
263+
}
264+
}
265+
}
266+
267+
private static void checkBloomFilterType(ColumnFamilyDescriptor cfd) throws IOException {
268+
Configuration conf = new CompoundConfiguration().addStringMap(cfd.getConfiguration());
269+
try {
270+
BloomFilterUtil.getBloomFilterParam(cfd.getBloomFilterType(), conf);
271+
} catch (IllegalArgumentException e) {
272+
throw new DoNotRetryIOException("Failed to get bloom filter param", e);
273+
}
274+
}
275+
276+
private static void checkCompression(final TableDescriptor td) throws IOException {
277+
for (ColumnFamilyDescriptor cfd : td.getColumnFamilies()) {
278+
CompressionTest.testCompression(cfd.getCompressionType());
279+
CompressionTest.testCompression(cfd.getCompactionCompressionType());
280+
}
281+
}
282+
283+
private static void checkEncryption(final Configuration conf, final TableDescriptor td)
284+
throws IOException {
285+
for (ColumnFamilyDescriptor cfd : td.getColumnFamilies()) {
286+
EncryptionTest.testEncryption(conf, cfd.getEncryptionType(), cfd.getEncryptionKey());
287+
}
288+
}
289+
290+
private static void checkClassLoading(final Configuration conf, final TableDescriptor td)
291+
throws IOException {
292+
RegionSplitPolicy.getSplitPolicyClass(td, conf);
293+
RegionCoprocessorHost.testTableCoprocessorAttrs(conf, td);
294+
}
295+
296+
// HBASE-13350 - Helper method to log warning on sanity check failures if checks disabled.
297+
private static void warnOrThrowExceptionForFailure(boolean logWarn, String message,
298+
Exception cause) throws IOException {
299+
if (!logWarn) {
300+
throw new DoNotRetryIOException(message + " Set " + TABLE_SANITY_CHECKS +
301+
" to false at conf or table descriptor if you want to bypass sanity checks", cause);
302+
}
303+
LOG.warn(message);
304+
}
305+
}

hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAvoidCellReferencesIntoShippedBlocks.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ public static void setUpBeforeClass() throws Exception {
103103
Configuration conf = TEST_UTIL.getConfiguration();
104104
conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
105105
MultiRowMutationEndpoint.class.getName());
106-
conf.setBoolean("hbase.table.sanity.checks", true); // enable for below
107-
// tests
108106
conf.setInt("hbase.regionserver.handler.count", 20);
109107
conf.setInt("hbase.bucketcache.size", 400);
110108
conf.setStrings(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");

hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestBlockEvictionFromClient.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,6 @@ public static void setUpBeforeClass() throws Exception {
113113
Configuration conf = TEST_UTIL.getConfiguration();
114114
conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
115115
MultiRowMutationEndpoint.class.getName());
116-
conf.setBoolean("hbase.table.sanity.checks", true); // enable for below
117-
// tests
118116
conf.setInt("hbase.regionserver.handler.count", 20);
119117
conf.setInt("hbase.bucketcache.size", 400);
120118
conf.setStrings(HConstants.BUCKET_CACHE_IOENGINE_KEY, "offheap");

hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFromClientSide.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
import org.apache.hadoop.hbase.util.Bytes;
103103
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
104104
import org.apache.hadoop.hbase.util.NonRepeatedEnvironmentEdge;
105+
import org.apache.hadoop.hbase.util.TableDescriptorChecker;
105106
import org.junit.AfterClass;
106107
import org.junit.BeforeClass;
107108
import org.junit.ClassRule;
@@ -150,7 +151,7 @@ protected static final void initialize(Class<?>... cps) throws Exception {
150151
Configuration conf = TEST_UTIL.getConfiguration();
151152
conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
152153
Arrays.stream(cps).map(Class::getName).toArray(String[]::new));
153-
conf.setBoolean("hbase.table.sanity.checks", true); // enable for below tests
154+
conf.setBoolean(TableDescriptorChecker.TABLE_SANITY_CHECKS, true); // enable for below tests
154155
// We need more than one region server in this test
155156
TEST_UTIL.startMiniCluster(SLAVES);
156157
}

0 commit comments

Comments
 (0)