Skip to content

Commit 64e732d

Browse files
authored
HBASE-22624 Should sanity check table configuration when clone snapshot to a new table
1 parent b22459c commit 64e732d

18 files changed

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

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)