Skip to content

Commit cd61c1d

Browse files
committed
Add restore command to ZK CLI
1 parent a254f39 commit cd61c1d

File tree

8 files changed

+407
-46
lines changed

8 files changed

+407
-46
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 DATA_DESTINATION_STR = "data_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 DATA_DESTINATION_OPTION = "d";
50+
public static final String LOG_DESTINATION_OPTION = "l";
51+
public static final String TIMETABLE_STORAGE_PATH_OPTION = "s";
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 DATA_DESTINATION_CMD =
59+
"-" + DATA_DESTINATION_OPTION + " " + DATA_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+
+ DATA_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(DATA_DESTINATION_OPTION, true, DATA_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+
+ DATA_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(DATA_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: 44 additions & 3 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

@@ -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);
@@ -405,4 +405,45 @@ private void validate() throws ConfigException {
405405
}
406406
}
407407
}
408+
409+
public static class RestorationBackupConfigBuilder extends Builder {
410+
private Optional<String> storageProviderClassName = Optional.empty();
411+
private Optional<File> storageConfig = Optional.empty();
412+
private Optional<String> backupStoragePath = Optional.empty();
413+
private Optional<String> namespace = Optional.empty();
414+
415+
public RestorationBackupConfigBuilder setStorageProviderClassName(
416+
String storageProviderClassName) {
417+
this.storageProviderClassName = Optional.of(storageProviderClassName);
418+
return this;
419+
}
420+
421+
public RestorationBackupConfigBuilder setStorageConfig(File storageConfig) {
422+
this.storageConfig = Optional.of(storageConfig);
423+
return this;
424+
}
425+
426+
public RestorationBackupConfigBuilder setBackupStoragePath(String backupStoragePath) {
427+
this.backupStoragePath = Optional.of(backupStoragePath);
428+
return this;
429+
}
430+
431+
public RestorationBackupConfigBuilder setNamespace(String namespace) {
432+
this.namespace = Optional.of(namespace);
433+
return this;
434+
}
435+
436+
public Optional<BackupConfig> build() throws ConfigException {
437+
if (!storageProviderClassName.isPresent() || storageProviderClassName.get().isEmpty()) {
438+
throw new ConfigException("Please specify a valid storage provider class name.");
439+
}
440+
if (!backupStoragePath.isPresent()) {
441+
throw new ConfigException("Please specify a valid backup storage path..");
442+
}
443+
if (!namespace.isPresent()) {
444+
throw new ConfigException("Please specify a valid namespace.");
445+
}
446+
return Optional.of(new BackupConfig(this));
447+
}
448+
}
408449
}

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: 23 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;
@@ -356,6 +358,27 @@ public static List<Range<Long>> getRanges(
356358
zxidRangeExtractor));
357359
}
358360

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

0 commit comments

Comments
 (0)