Skip to content

Commit 56cd973

Browse files
committed
Add restore command to ZK CLI
1 parent a2d36ba commit 56cd973

File tree

9 files changed

+458
-36
lines changed

9 files changed

+458
-36
lines changed

zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeperMain.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.apache.zookeeper.cli.MalformedCommandException;
5858
import org.apache.zookeeper.cli.ReconfigCommand;
5959
import org.apache.zookeeper.cli.RemoveWatchesCommand;
60+
import org.apache.zookeeper.cli.RestoreCommand;
6061
import org.apache.zookeeper.cli.SetAclCommand;
6162
import org.apache.zookeeper.cli.SetCommand;
6263
import org.apache.zookeeper.cli.SetQuotaCommand;
@@ -123,6 +124,7 @@ public boolean getPrintWatches() {
123124
new GetAllChildrenNumberCommand().addToMap(commandMapCli);
124125
new VersionCommand().addToMap(commandMapCli);
125126
new AddWatchCommand().addToMap(commandMapCli);
127+
new RestoreCommand().addToMap(commandMapCli);
126128

127129
// add all to commandMap
128130
for (Entry<String, CliCommand> entry : commandMapCli.entrySet()) {

zookeeper-server/src/main/java/org/apache/zookeeper/cli/CliParseException.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,7 @@ public CliParseException(String message) {
3131
super(message);
3232
}
3333

34+
public CliParseException(String message, Throwable cause) {
35+
super(message, cause);
36+
}
3437
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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+
19+
20+
package org.apache.zookeeper.cli;
21+
22+
import org.apache.commons.cli.CommandLine;
23+
import org.apache.commons.cli.Option;
24+
import org.apache.commons.cli.Options;
25+
import org.apache.commons.cli.ParseException;
26+
import org.apache.commons.cli.Parser;
27+
import org.apache.commons.cli.PosixParser;
28+
import org.apache.zookeeper.server.backup.RestoreFromBackupTool;
29+
30+
/**
31+
* Restore command for ZkCli.
32+
*/
33+
public class RestoreCommand extends CliCommand {
34+
35+
private RestoreFromBackupTool tool;
36+
private CommandLine cl;
37+
private static Options options = new Options();
38+
private static final String RESTORE_ZXID_STR = "restore_zxid";
39+
private static final String RESTORE_TIMESTAMP_STR = "restore_timestamp";
40+
private static final String BACKUP_STORE_STR = "backup_store";
41+
private static final String SNAP_DESTINATION_STR = "snap_destination";
42+
private static final String LOG_DESTINATION_STR = "log_destination";
43+
private static final String TIMETABLE_STORAGE_PATH_STR = "timetable_storage_path";
44+
private static final String LOCAL_RESTORE_TEMP_DIR_PATH_STR = "local_restore_temp_dir_path";
45+
private static final String DRY_RUN_STR = "dry_run";
46+
public static final String RESTORE_ZXID_OPTION = "z";
47+
public static final String RESTORE_TIMESTAMP_OPTION = "t";
48+
public static final String BACKUP_STORE_OPTION = "b";
49+
public static final String SNAP_DESTINATION_OPTION = "s";
50+
public static final String LOG_DESTINATION_OPTION = "l";
51+
public static final String TIMETABLE_STORAGE_PATH_OPTION = "m";
52+
public static final String LOCAL_RESTORE_TEMP_DIR_PATH_OPTION = "r";
53+
public static final String DRY_RUN_OPTION = "n";
54+
private static final String RESTORE_ZXID_CMD = "-" + RESTORE_ZXID_OPTION + " " + RESTORE_ZXID_STR;
55+
private static final String RESTORE_TIMESTAMP_CMD =
56+
"-" + RESTORE_TIMESTAMP_OPTION + " " + RESTORE_TIMESTAMP_STR;
57+
private static final String BACKUP_STORE_CMD = "-" + BACKUP_STORE_OPTION + " " + BACKUP_STORE_STR;
58+
private static final String SNAP_DESTINATION_CMD =
59+
"-" + SNAP_DESTINATION_OPTION + " " + SNAP_DESTINATION_STR;
60+
private static final String LOG_DESTINATION_CMD =
61+
"-" + LOG_DESTINATION_OPTION + " " + LOG_DESTINATION_STR;
62+
private static final String TIMETABLE_STORAGE_PATH_CMD =
63+
"-" + TIMETABLE_STORAGE_PATH_OPTION + " " + TIMETABLE_STORAGE_PATH_STR;
64+
private static final String LOCAL_RESTORE_TEMP_DIR_PATH_CMD =
65+
"-" + LOCAL_RESTORE_TEMP_DIR_PATH_OPTION + " " + LOCAL_RESTORE_TEMP_DIR_PATH_STR;
66+
private static final String DRY_RUN_CMD = "-" + DRY_RUN_OPTION;
67+
private static final String RESTORE_CMD_STR = "restore";
68+
private static final String OPTION_STR =
69+
"[" + RESTORE_ZXID_CMD + "]/[" + RESTORE_TIMESTAMP_CMD + "] [" + BACKUP_STORE_CMD + "] ["
70+
+ SNAP_DESTINATION_CMD + "] [" + LOG_DESTINATION_CMD + "] [" + TIMETABLE_STORAGE_PATH_CMD
71+
+ "](needed if restore to a timestamp) [" + LOCAL_RESTORE_TEMP_DIR_PATH_CMD
72+
+ "](optional) [" + DRY_RUN_CMD + "](optional)";
73+
74+
static {
75+
options.addOption(new Option(RESTORE_ZXID_OPTION, true, RESTORE_ZXID_STR));
76+
options.addOption(new Option(RESTORE_TIMESTAMP_OPTION, true, RESTORE_TIMESTAMP_STR));
77+
options.addOption(new Option(BACKUP_STORE_OPTION, true, BACKUP_STORE_STR));
78+
options.addOption(new Option(SNAP_DESTINATION_OPTION, true, SNAP_DESTINATION_STR));
79+
options.addOption(new Option(LOG_DESTINATION_OPTION, true, LOG_DESTINATION_STR));
80+
options.addOption(new Option(TIMETABLE_STORAGE_PATH_OPTION, true, TIMETABLE_STORAGE_PATH_STR));
81+
options.addOption(
82+
new Option(LOCAL_RESTORE_TEMP_DIR_PATH_OPTION, true, LOCAL_RESTORE_TEMP_DIR_PATH_STR));
83+
options.addOption(new Option(DRY_RUN_OPTION, false, DRY_RUN_STR));
84+
}
85+
86+
public RestoreCommand() {
87+
super(RESTORE_CMD_STR, OPTION_STR);
88+
tool = new RestoreFromBackupTool();
89+
}
90+
91+
@Override
92+
public String getUsageStr() {
93+
return "Usage: RestoreFromBackupTool " + RESTORE_CMD_STR + " " + OPTION_STR + "\n "
94+
+ RESTORE_ZXID_CMD
95+
+ ": the point to restore to, either the string 'latest' or a zxid in hex format. Choose one between this option or "
96+
+ RESTORE_TIMESTAMP_CMD + ", if both are specified, this option will be prioritized\n "
97+
+ RESTORE_TIMESTAMP_CMD
98+
+ ": the point to restore to, a timestamp in long format. Choose one between this option or "
99+
+ RESTORE_ZXID_CMD + ".\n " + BACKUP_STORE_CMD
100+
+ ": the connection information for the backup store\n For GPFS the format is: gpfs:<config_path>:<backup_path>:<namespace>\n "
101+
+ SNAP_DESTINATION_CMD + ": local destination path for restored snapshots\n "
102+
+ LOG_DESTINATION_CMD + ": local destination path for restored txlogs\n "
103+
+ TIMETABLE_STORAGE_PATH_CMD
104+
+ ": Needed if restore to a timestamp. Backup storage path for timetable files, for GPFS the format is: gpfs:<config_path>:<backup_path>:<namespace>, if not set, default to be same as backup storage path\n "
105+
+ LOCAL_RESTORE_TEMP_DIR_PATH_CMD
106+
+ ": Optional, local path for creating a temporary intermediate directory for restoration, the directory will be deleted after restoration is done\n "
107+
+ DRY_RUN_CMD + " " + DRY_RUN_STR
108+
+ ": Optional, no files will be actually copied in a dry run";
109+
}
110+
111+
@Override
112+
public CliCommand parse(String[] cmdArgs) throws CliParseException {
113+
Parser parser = new PosixParser();
114+
try {
115+
cl = parser.parse(options, cmdArgs);
116+
} catch (ParseException ex) {
117+
throw new CliParseException(getUsageStr(), ex);
118+
}
119+
if ((!cl.hasOption(RESTORE_ZXID_OPTION) && !cl.hasOption(RESTORE_TIMESTAMP_OPTION)) || !cl
120+
.hasOption(BACKUP_STORE_OPTION) || !cl.hasOption(SNAP_DESTINATION_OPTION) || !cl
121+
.hasOption(LOG_DESTINATION_OPTION)) {
122+
throw new CliParseException("Missing required argument(s).\n" + getUsageStr());
123+
}
124+
return this;
125+
}
126+
127+
@Override
128+
public boolean exec() throws CliException {
129+
return tool.runWithRetries(cl);
130+
}
131+
}

zookeeper-server/src/main/java/org/apache/zookeeper/server/backup/BackupConfig.java

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ public class BackupConfig {
6868
private final String backupStoragePath;
6969
/*
7070
* The custom namespace string under which the backup files will be stored in.
71-
* E.g.) <backupStoragePath>/<namespace>/snapshot/snapshot-123456
72-
* <backupStoragePath>/<namespace>/translog/translog-123456
71+
* E.g.) <backupStoragePath>/<namespace>/snapshot-123456
72+
* <backupStoragePath>/<namespace>/translog-123456
7373
*/
7474
private final String namespace;
7575

@@ -82,8 +82,8 @@ public class BackupConfig {
8282

8383
public BackupConfig(Builder builder) {
8484
this.builder = builder;
85-
this.statusDir = builder.statusDir.get();
86-
this.tmpDir = builder.tmpDir.get();
85+
this.statusDir = builder.statusDir.orElse(null);
86+
this.tmpDir = builder.tmpDir.orElse(null);
8787
this.backupIntervalInMinutes = builder.backupIntervalInMinutes.orElse(
8888
DEFAULT_BACKUP_INTERVAL_MINUTES);
8989
this.retentionPeriodInDays = builder.retentionPeriodInDays.orElse(DEFAULT_RETENTION_DAYS);
@@ -93,7 +93,7 @@ public BackupConfig(Builder builder) {
9393
this.storageProviderClassName = builder.storageProviderClassName.get();
9494
this.storageConfig = builder.storageConfig.orElse(null);
9595
this.backupStoragePath = builder.backupStoragePath.orElse("");
96-
this.namespace = builder.namespace.orElse("");
96+
this.namespace = builder.namespace.orElse("UNKNOWN");
9797
this.timetableEnabled = builder.timetableEnabled.orElse(false);
9898
// If timetable storage path is not given, use the backup storage path
9999
this.timetableStoragePath = builder.timetableStoragePath.orElse(backupStoragePath);
@@ -166,10 +166,10 @@ public static class Builder {
166166
private Optional<Integer> retentionPeriodInDays = Optional.of(DEFAULT_RETENTION_DAYS);
167167
private Optional<Integer> retentionMaintenanceIntervalInHours =
168168
Optional.of(DEFAULT_RETENTION_MAINTENANCE_INTERVAL_HOURS);
169-
private Optional<String> storageProviderClassName = Optional.empty();
170-
private Optional<File> storageConfig = Optional.empty();
171-
private Optional<String> backupStoragePath = Optional.empty();
172-
private Optional<String> namespace = Optional.empty();
169+
protected Optional<String> storageProviderClassName = Optional.empty();
170+
protected Optional<File> storageConfig = Optional.empty();
171+
protected Optional<String> backupStoragePath = Optional.empty();
172+
protected Optional<String> namespace = Optional.empty();
173173
private Optional<Boolean> timetableEnabled = Optional.empty();
174174
private Optional<String> timetableStoragePath = Optional.empty();
175175
private Optional<Long> timetableBackupIntervalInMs = Optional.empty();
@@ -405,4 +405,41 @@ private void validate() throws ConfigException {
405405
}
406406
}
407407
}
408+
409+
public static class RestorationConfigBuilder extends Builder {
410+
public RestorationConfigBuilder setStorageProviderClassName(
411+
String storageProviderClassName) {
412+
this.storageProviderClassName = Optional.of(storageProviderClassName);
413+
return this;
414+
}
415+
416+
public RestorationConfigBuilder setStorageConfig(File storageConfig) {
417+
this.storageConfig = Optional.of(storageConfig);
418+
return this;
419+
}
420+
421+
public RestorationConfigBuilder setBackupStoragePath(String backupStoragePath) {
422+
this.backupStoragePath = Optional.of(backupStoragePath);
423+
return this;
424+
}
425+
426+
public RestorationConfigBuilder setNamespace(String namespace) {
427+
this.namespace = Optional.of(namespace);
428+
return this;
429+
}
430+
431+
@Override
432+
public Optional<BackupConfig> build() throws ConfigException {
433+
if (!storageProviderClassName.isPresent() || storageProviderClassName.get().isEmpty()) {
434+
throw new ConfigException("Please specify a valid storage provider class name.");
435+
}
436+
if (!backupStoragePath.isPresent()) {
437+
throw new ConfigException("Please specify a valid backup storage path..");
438+
}
439+
if (!namespace.isPresent()) {
440+
throw new ConfigException("Please specify a valid namespace.");
441+
}
442+
return Optional.of(new BackupConfig(this));
443+
}
444+
}
408445
}

zookeeper-server/src/main/java/org/apache/zookeeper/server/backup/BackupManager.java

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ public BackupManager(File snapDir, File dataLogDir, long serverId, BackupConfig
503503
this.serverId = serverId;
504504
this.namespace = backupConfig.getNamespace() == null ? "UNKNOWN" : backupConfig.getNamespace();
505505
try {
506-
backupStorage = createStorageProviderImpl(backupConfig);
506+
backupStorage = BackupUtil.createStorageProviderImpl(backupConfig);
507507
} catch (ReflectiveOperationException e) {
508508
throw new BackupException(e.getMessage(), e);
509509
}
@@ -597,7 +597,7 @@ public synchronized void initialize() throws IOException {
597597
// This is because we want the timetable backup to be stored in a different storage path
598598
BackupStorageProvider timetableBackupStorage;
599599
try {
600-
timetableBackupStorage = createStorageProviderImpl(
600+
timetableBackupStorage = BackupUtil.createStorageProviderImpl(
601601
backupConfig.getBuilder().setBackupStoragePath(backupConfig.getTimetableStoragePath())
602602
.build().get());
603603
LOG.info(
@@ -612,25 +612,4 @@ public synchronized void initialize() throws IOException {
612612
timetableBackup.initialize();
613613
}
614614
}
615-
616-
/**
617-
* Instantiates the storage provider implementation by reflection. This allows the user to
618-
* choose which BackupStorageProvider implementation to use by specifying the fully-qualified
619-
* class name in BackupConfig (read from Properties).
620-
* @param backupConfig
621-
* @return
622-
* @throws ClassNotFoundException
623-
* @throws NoSuchMethodException
624-
* @throws IllegalAccessException
625-
* @throws InvocationTargetException
626-
* @throws InstantiationException
627-
*/
628-
private BackupStorageProvider createStorageProviderImpl(BackupConfig backupConfig)
629-
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
630-
InvocationTargetException, InstantiationException {
631-
Class<?> clazz = Class.forName(backupConfig.getStorageProviderClassName());
632-
Constructor<?> constructor = clazz.getConstructor(BackupConfig.class);
633-
Object storageProvider = constructor.newInstance(backupConfig);
634-
return (BackupStorageProvider) storageProvider;
635-
}
636615
}

zookeeper-server/src/main/java/org/apache/zookeeper/server/backup/BackupUtil.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.io.File;
2222
import java.io.IOException;
2323
import java.io.Serializable;
24+
import java.lang.reflect.Constructor;
25+
import java.lang.reflect.InvocationTargetException;
2426
import java.util.*;
2527

2628
import com.google.common.base.Function;
@@ -41,6 +43,7 @@ public class BackupUtil {
4143
// invalid timestamp is -1 by convention
4244
public static final long INVALID_TIMESTAMP = -1L;
4345
public static final String LOST_LOG_PREFIX = "lostLogs";
46+
public static final String LATEST = "latest";
4447

4548
/**
4649
* Identifiers for the two zxids in a backedup file name
@@ -356,6 +359,27 @@ public static List<Range<Long>> getRanges(
356359
zxidRangeExtractor));
357360
}
358361

362+
/**
363+
* Instantiates the storage provider implementation by reflection. This allows the user to
364+
* choose which BackupStorageProvider implementation to use by specifying the fully-qualified
365+
* class name in BackupConfig (read from Properties).
366+
* @param backupConfig
367+
* @return
368+
* @throws ClassNotFoundException
369+
* @throws NoSuchMethodException
370+
* @throws IllegalAccessException
371+
* @throws InvocationTargetException
372+
* @throws InstantiationException
373+
*/
374+
public static BackupStorageProvider createStorageProviderImpl(BackupConfig backupConfig)
375+
throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
376+
InvocationTargetException, InstantiationException {
377+
Class<?> clazz = Class.forName(backupConfig.getStorageProviderClassName());
378+
Constructor<?> constructor = clazz.getConstructor(BackupConfig.class);
379+
Object storageProvider = constructor.newInstance(backupConfig);
380+
return (BackupStorageProvider) storageProvider;
381+
}
382+
359383
// Utility class
360384
private BackupUtil() {}
361385
}

0 commit comments

Comments
 (0)