Skip to content

Commit 620ab79

Browse files
NihalJainvirajjasani
authored andcommitted
HBASE-27671 Client should not be able to restore/clone a snapshot after it has TTL expired it's TTL has expired (#5117) ( #5109)
Signed-off-by: Viraj Jasani <[email protected]>
1 parent 29c3230 commit 620ab79

File tree

7 files changed

+338
-16
lines changed

7 files changed

+338
-16
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.snapshot;
19+
20+
import org.apache.hadoop.hbase.client.SnapshotDescription;
21+
import org.apache.yetus.audience.InterfaceAudience;
22+
23+
/**
24+
* Thrown when a snapshot could not be restored/cloned because the ttl for snapshot has already
25+
* expired
26+
*/
27+
@SuppressWarnings("serial")
28+
@InterfaceAudience.Public
29+
public class SnapshotTTLExpiredException extends HBaseSnapshotException {
30+
/**
31+
* Failure when the ttl for snapshot has already expired.
32+
* @param message the full description of the failure
33+
*/
34+
public SnapshotTTLExpiredException(String message) {
35+
super(message);
36+
}
37+
38+
/**
39+
* Failure when the ttl for snapshot has already expired.
40+
* @param snapshotDescription snapshot that was attempted
41+
*/
42+
public SnapshotTTLExpiredException(SnapshotDescription snapshotDescription) {
43+
super("TTL for snapshot '" + snapshotDescription.getName() + "' has already expired.",
44+
snapshotDescription);
45+
}
46+
}

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

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919

2020
import java.io.IOException;
2121
import java.util.List;
22-
import java.util.concurrent.TimeUnit;
2322
import org.apache.hadoop.conf.Configuration;
2423
import org.apache.hadoop.hbase.ScheduledChore;
2524
import org.apache.hadoop.hbase.Stoppable;
2625
import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
26+
import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
2727
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
2828
import org.apache.yetus.audience.InterfaceAudience;
2929
import org.slf4j.Logger;
@@ -72,22 +72,14 @@ protected void chore() {
7272
for (SnapshotProtos.SnapshotDescription snapshotDescription : completedSnapshotsList) {
7373
long snapshotCreatedTime = snapshotDescription.getCreationTime();
7474
long snapshotTtl = snapshotDescription.getTtl();
75-
/*
76-
* Backward compatibility after the patch deployment on HMaster Any snapshot with ttl 0 is
77-
* to be considered as snapshot to keep FOREVER Default ttl value specified by
78-
* {@HConstants.DEFAULT_SNAPSHOT_TTL}
79-
*/
75+
long currentTime = EnvironmentEdgeManager.currentTime();
8076
if (
81-
snapshotCreatedTime > 0 && snapshotTtl > 0
82-
&& snapshotTtl < TimeUnit.MILLISECONDS.toSeconds(Long.MAX_VALUE)
77+
SnapshotDescriptionUtils.isExpiredSnapshot(snapshotTtl, snapshotCreatedTime, currentTime)
8378
) {
84-
long currentTime = EnvironmentEdgeManager.currentTime();
85-
if ((snapshotCreatedTime + TimeUnit.SECONDS.toMillis(snapshotTtl)) < currentTime) {
86-
LOG.info("Event: {} Name: {}, CreatedTime: {}, TTL: {}, currentTime: {}",
87-
DELETE_SNAPSHOT_EVENT, snapshotDescription.getName(), snapshotCreatedTime,
88-
snapshotTtl, currentTime);
89-
deleteExpiredSnapshot(snapshotDescription);
90-
}
79+
LOG.info("Event: {} Name: {}, CreatedTime: {}, TTL: {}, currentTime: {}",
80+
DELETE_SNAPSHOT_EVENT, snapshotDescription.getName(), snapshotCreatedTime, snapshotTtl,
81+
currentTime);
82+
deleteExpiredSnapshot(snapshotDescription);
9183
}
9284
}
9385
} catch (IOException e) {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252
import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper;
5353
import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
5454
import org.apache.hadoop.hbase.snapshot.SnapshotManifest;
55+
import org.apache.hadoop.hbase.snapshot.SnapshotTTLExpiredException;
5556
import org.apache.hadoop.hbase.util.CommonFSUtils;
57+
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
5658
import org.apache.hadoop.hbase.util.FSTableDescriptors;
5759
import org.apache.hadoop.hbase.util.Pair;
5860
import org.apache.yetus.audience.InterfaceAudience;
@@ -380,6 +382,14 @@ private void prepareClone(final MasterProcedureEnv env) throws IOException {
380382
throw new TableExistsException(tableName);
381383
}
382384

385+
// check whether ttl has expired for this snapshot
386+
if (
387+
SnapshotDescriptionUtils.isExpiredSnapshot(snapshot.getTtl(), snapshot.getCreationTime(),
388+
EnvironmentEdgeManager.currentTime())
389+
) {
390+
throw new SnapshotTTLExpiredException(ProtobufUtil.createSnapshotDesc(snapshot));
391+
}
392+
383393
validateSFT();
384394
}
385395

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper;
5151
import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
5252
import org.apache.hadoop.hbase.snapshot.SnapshotManifest;
53+
import org.apache.hadoop.hbase.snapshot.SnapshotTTLExpiredException;
54+
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
5355
import org.apache.hadoop.hbase.util.Pair;
5456
import org.apache.yetus.audience.InterfaceAudience;
5557
import org.slf4j.Logger;
@@ -328,6 +330,14 @@ private void prepareRestore(final MasterProcedureEnv env) throws IOException {
328330
throw new TableNotFoundException(tableName);
329331
}
330332

333+
// check whether ttl has expired for this snapshot
334+
if (
335+
SnapshotDescriptionUtils.isExpiredSnapshot(snapshot.getTtl(), snapshot.getCreationTime(),
336+
EnvironmentEdgeManager.currentTime())
337+
) {
338+
throw new SnapshotTTLExpiredException(ProtobufUtil.createSnapshotDesc(snapshot));
339+
}
340+
331341
// Check whether table is disabled.
332342
env.getMasterServices().checkTableModifiable(tableName);
333343

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,4 +455,18 @@ public ListMultimap<String, UserPermission> run() throws Exception {
455455
return snapshot.toBuilder()
456456
.setUsersAndPermissions(ShadedAccessControlUtil.toUserTablePermissions(perms)).build();
457457
}
458+
459+
/**
460+
* Method to check whether TTL has expired for specified snapshot creation time and snapshot ttl.
461+
* NOTE: For backward compatibility (after the patch deployment on HMaster), any snapshot with ttl
462+
* 0 is to be considered as snapshot to keep FOREVER. Default ttl value specified by
463+
* {@link HConstants#DEFAULT_SNAPSHOT_TTL}
464+
* @return true if ttl has expired, or, false, otherwise
465+
*/
466+
public static boolean isExpiredSnapshot(long snapshotTtl, long snapshotCreatedTime,
467+
long currentTime) {
468+
return snapshotCreatedTime > 0 && snapshotTtl > HConstants.DEFAULT_SNAPSHOT_TTL
469+
&& snapshotTtl < TimeUnit.MILLISECONDS.toSeconds(Long.MAX_VALUE)
470+
&& (snapshotCreatedTime + TimeUnit.SECONDS.toMillis(snapshotTtl)) < currentTime;
471+
}
458472
}
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
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.client;
19+
20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertFalse;
22+
import static org.junit.Assert.assertTrue;
23+
import static org.junit.Assert.fail;
24+
25+
import java.io.IOException;
26+
import java.util.HashMap;
27+
import java.util.List;
28+
import java.util.Map;
29+
import org.apache.hadoop.conf.Configuration;
30+
import org.apache.hadoop.hbase.HBaseClassTestRule;
31+
import org.apache.hadoop.hbase.HBaseTestingUtility;
32+
import org.apache.hadoop.hbase.TableName;
33+
import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
34+
import org.apache.hadoop.hbase.snapshot.SnapshotTTLExpiredException;
35+
import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
36+
import org.apache.hadoop.hbase.testclassification.ClientTests;
37+
import org.apache.hadoop.hbase.testclassification.LargeTests;
38+
import org.apache.hadoop.hbase.util.Bytes;
39+
import org.apache.hadoop.hbase.util.Threads;
40+
import org.junit.After;
41+
import org.junit.AfterClass;
42+
import org.junit.Before;
43+
import org.junit.BeforeClass;
44+
import org.junit.ClassRule;
45+
import org.junit.Test;
46+
import org.junit.experimental.categories.Category;
47+
import org.slf4j.Logger;
48+
import org.slf4j.LoggerFactory;
49+
50+
/**
51+
* Test restore/clone snapshots with TTL from the client
52+
*/
53+
@Category({ LargeTests.class, ClientTests.class })
54+
public class TestSnapshotWithTTLFromClient {
55+
56+
@ClassRule
57+
public static final HBaseClassTestRule CLASS_RULE =
58+
HBaseClassTestRule.forClass(TestSnapshotWithTTLFromClient.class);
59+
60+
private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotWithTTLFromClient.class);
61+
62+
private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
63+
private static final int NUM_RS = 2;
64+
private static final String STRING_TABLE_NAME = "test";
65+
private static final byte[] TEST_FAM = Bytes.toBytes("fam");
66+
private static final TableName TABLE_NAME = TableName.valueOf(STRING_TABLE_NAME);
67+
private static final TableName CLONED_TABLE_NAME = TableName.valueOf("clonedTable");
68+
private static final String TTL_KEY = "TTL";
69+
private static final int CHORE_INTERVAL_SECS = 30;
70+
71+
/**
72+
* Setup the config for the cluster
73+
* @throws Exception on failure
74+
*/
75+
@BeforeClass
76+
public static void setupCluster() throws Exception {
77+
setupConf(UTIL.getConfiguration());
78+
UTIL.startMiniCluster(NUM_RS);
79+
}
80+
81+
protected static void setupConf(Configuration conf) {
82+
// Enable snapshot
83+
conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
84+
85+
// Set this to high value so that cleaner chore is not triggered
86+
conf.setInt("hbase.master.cleaner.snapshot.interval", CHORE_INTERVAL_SECS * 60 * 1000);
87+
}
88+
89+
@Before
90+
public void setup() throws Exception {
91+
createTable();
92+
}
93+
94+
protected void createTable() throws Exception {
95+
UTIL.createTable(TABLE_NAME, new byte[][] { TEST_FAM });
96+
}
97+
98+
@After
99+
public void tearDown() throws Exception {
100+
UTIL.deleteTableIfAny(TABLE_NAME);
101+
UTIL.deleteTableIfAny(CLONED_TABLE_NAME);
102+
SnapshotTestingUtils.deleteAllSnapshots(UTIL.getAdmin());
103+
SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
104+
}
105+
106+
@AfterClass
107+
public static void cleanupTest() throws Exception {
108+
try {
109+
UTIL.shutdownMiniCluster();
110+
} catch (Exception e) {
111+
LOG.warn("failure shutting down cluster", e);
112+
}
113+
}
114+
115+
@Test
116+
public void testRestoreSnapshotWithTTLSuccess() throws Exception {
117+
String snapshotName = "nonExpiredTTLRestoreSnapshotTest";
118+
119+
// table should exist
120+
assertTrue(UTIL.getAdmin().tableExists(TABLE_NAME));
121+
122+
// create snapshot fo given table with specified ttl
123+
createSnapshotWithTTL(TABLE_NAME, snapshotName, CHORE_INTERVAL_SECS * 2);
124+
Admin admin = UTIL.getAdmin();
125+
126+
// Disable and drop table
127+
admin.disableTable(TABLE_NAME);
128+
admin.deleteTable(TABLE_NAME);
129+
assertFalse(UTIL.getAdmin().tableExists(TABLE_NAME));
130+
131+
// restore snapshot
132+
admin.restoreSnapshot(snapshotName);
133+
134+
// table should be created
135+
assertTrue(UTIL.getAdmin().tableExists(TABLE_NAME));
136+
}
137+
138+
@Test
139+
public void testRestoreSnapshotFailsDueToTTLExpired() throws Exception {
140+
String snapshotName = "expiredTTLRestoreSnapshotTest";
141+
142+
// table should exist
143+
assertTrue(UTIL.getAdmin().tableExists(TABLE_NAME));
144+
145+
// create snapshot fo given table with specified ttl
146+
createSnapshotWithTTL(TABLE_NAME, snapshotName, 1);
147+
Admin admin = UTIL.getAdmin();
148+
149+
// Disable and drop table
150+
admin.disableTable(TABLE_NAME);
151+
admin.deleteTable(TABLE_NAME);
152+
assertFalse(UTIL.getAdmin().tableExists(TABLE_NAME));
153+
154+
// Sleep so that TTL may expire
155+
Threads.sleep(2000);
156+
157+
// restore snapshot which has expired
158+
try {
159+
admin.restoreSnapshot(snapshotName);
160+
fail("Restore snapshot succeeded even though TTL has expired.");
161+
} catch (SnapshotTTLExpiredException e) {
162+
LOG.info("Correctly failed to restore a TTL expired snapshot table:" + e.getMessage());
163+
}
164+
165+
// table should not be created
166+
assertFalse(UTIL.getAdmin().tableExists(TABLE_NAME));
167+
}
168+
169+
@Test
170+
public void testCloneSnapshotWithTTLSuccess() throws Exception {
171+
String snapshotName = "nonExpiredTTLCloneSnapshotTest";
172+
173+
// table should exist
174+
assertTrue(UTIL.getAdmin().tableExists(TABLE_NAME));
175+
176+
// create snapshot fo given table with specified ttl
177+
createSnapshotWithTTL(TABLE_NAME, snapshotName, CHORE_INTERVAL_SECS * 2);
178+
Admin admin = UTIL.getAdmin();
179+
180+
// restore snapshot
181+
admin.cloneSnapshot(snapshotName, CLONED_TABLE_NAME);
182+
183+
// table should be created
184+
assertTrue(UTIL.getAdmin().tableExists(CLONED_TABLE_NAME));
185+
}
186+
187+
@Test
188+
public void testCloneSnapshotFailsDueToTTLExpired() throws Exception {
189+
String snapshotName = "expiredTTLCloneSnapshotTest";
190+
191+
// table should exist
192+
assertTrue(UTIL.getAdmin().tableExists(TABLE_NAME));
193+
194+
// create snapshot fo given table with specified ttl
195+
createSnapshotWithTTL(TABLE_NAME, snapshotName, 1);
196+
Admin admin = UTIL.getAdmin();
197+
198+
assertTrue(UTIL.getAdmin().tableExists(TABLE_NAME));
199+
200+
// Sleep so that TTL may expire
201+
Threads.sleep(2000);
202+
203+
// clone snapshot which has expired
204+
try {
205+
admin.cloneSnapshot(snapshotName, CLONED_TABLE_NAME);
206+
fail("Clone snapshot succeeded even though TTL has expired.");
207+
} catch (SnapshotTTLExpiredException e) {
208+
LOG.info("Correctly failed to clone a TTL expired snapshot table:" + e.getMessage());
209+
}
210+
211+
// table should not be created
212+
assertFalse(UTIL.getAdmin().tableExists(CLONED_TABLE_NAME));
213+
}
214+
215+
private void createSnapshotWithTTL(TableName tableName, final String snapshotName,
216+
final int snapshotTTL) throws IOException {
217+
Admin admin = UTIL.getAdmin();
218+
219+
// make sure we don't fail on listing snapshots
220+
SnapshotTestingUtils.assertNoSnapshots(admin);
221+
222+
// put some stuff in the table
223+
Table table = UTIL.getConnection().getTable(tableName);
224+
UTIL.loadTable(table, TEST_FAM);
225+
226+
Map<String, Object> props = new HashMap<>();
227+
props.put(TTL_KEY, snapshotTTL);
228+
229+
// take a snapshot of the table
230+
SnapshotTestingUtils.snapshot(UTIL.getAdmin(), snapshotName, tableName, SnapshotType.FLUSH, 3,
231+
props);
232+
LOG.debug("Snapshot completed.");
233+
234+
// make sure we have the snapshot with expectd TTL
235+
List<SnapshotDescription> snapshots =
236+
SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshotName, tableName);
237+
assertEquals(1, snapshots.size());
238+
assertEquals(snapshotTTL, snapshots.get(0).getTtl());
239+
}
240+
}

0 commit comments

Comments
 (0)