admin cli 提供了对于dremio 维护的能力,包含了备份,清理元数据,导出profile,nessie 维护,恢复,更新kv 存储、重置密码。。。
修复acl (企业版特性)
参考代码处理
基于了注解以及类扫描机制
- 代码
public static void main(String[] args) throws Exception {
final SabotConfig sabotConfig = DACConfig.newConfig().getConfig().getSabotConfig();
final ScanResult classpathScan = ClassPathScanner.fromPrescan(sabotConfig);
// 基于类扫描查找相关admin command,需要包含一个AdminCommand 的注解
final List<Class<?>> adminCommands = classpathScan.getAnnotatedClasses(AdminCommand.class);
if (args.length < 1) {
printUsage("Missing action argument", adminCommands, System.err);
System.exit(1);
}
final String commandName = args[0];
if ("-h".equals(commandName) || "--help".equals(commandName)) {
printUsage("", adminCommands, System.out);
System.exit(0);
}
Class<?> command = null;
for (Class<?> clazz : adminCommands) {
final AdminCommand adminCommand = clazz.getAnnotation(AdminCommand.class);
if (adminCommand.value().equals(commandName)) {
command = clazz;
break;
}
}
if (command == null) {
printUsage(String.format("Unknown action: '%s'", commandName), adminCommands, System.err);
System.exit(2);
}
try {
// 命令运行
runCommand(commandName, command, Arrays.copyOfRange(args, 1, args.length));
} catch (Exception e) {
AdminLogger.log(String.format("Failed to run '%s' command: %s", commandName, e.getMessage()));
throw e;
}
}
- 命令运行
runCommand,实际上就是通过反射处理的
public static void runCommand(String commandName, Class<?> command, String[] commandArgs) throws Exception {
// 类需要包含一个main 方法,同时也会将上边传递的参数,传递给main 方法
final Method mainMethod = command.getMethod("main", String[].class);
Preconditions.checkState(Modifier.isStatic(mainMethod.getModifiers())
&& Modifier.isPublic(mainMethod.getModifiers()), "#main(String[]) must have public and static modifiers");
final Object[] objects = new Object[1];
objects[0] = commandArgs;
try {
//noinspection JavaReflectionInvocation
mainMethod.invoke(null, objects);
} catch (final ReflectiveOperationException e) {
final Throwable cause = e.getCause() != null ? e.getCause() : e;
Throwables.throwIfUnchecked(cause);
throw (Exception)cause;
}
}
参考command 创建(比如Backup)
同时结合了jcommander 进行cli 开发,可以看到部分cli 的开发是直接基于底层处理的(admin cli 只能在master 运行)
// 包含AdminCommand 注解的就是admin cli 提供的帮助能力
@AdminCommand(value = "backup", description = "Backs up Dremio metadata and user-uploaded files")
public class Backup {
private static final Logger LOGGER = LoggerFactory.getLogger(Backup.class);
/**
* Command line options for backup
*/
@Parameters(separators = "=")
static final class BackupManagerOptions {
@Parameter(names = { "-h", "--help" }, description = "show usage", help = true)
private boolean help = false;
@Parameter(names = { "-d", "--backupdir" }, description = "backup directory path. for example, " +
"/mnt/dremio/backups or hdfs://$namenode:8020/dremio/backups", required = true)
private String backupDir = null;
@Parameter(names = { "-l", "--local-attach" }, description = "Attach locally to Dremio JVM to authenticate user. " +
"Not compatible with user/password options")
private boolean localAttach = false;
@Parameter(names = { "-u", "--user" }, description = "username (admin)")
private String userName = null;
@Parameter(names = { "-p", "--password" }, description = "password", password = true)
private String password = null;
@Parameter(names = { "-a", "--accept-all" }, description = "accept all ssl certificates")
private boolean acceptAll = false;
@Parameter(names = { "-j", "--json" }, description = "do json backup (defaults to binary)")
private boolean json = false;
@Parameter(names = { "-i", "--include-profiles" }, description = "include profiles in backup")
private boolean profiles = false;
@Parameter(names = { "-s", "--same-process" }, description = "execute backup using the same process as " +
"dremio-admin and not Dremio Server process. This option should only be used with user/password options",
hidden = true)
private boolean sameProcess = false;
}
public static BackupStats createBackup(
DACConfig dacConfig,
Provider<CredentialsService> credentialsServiceProvider,
String userName,
String password,
boolean checkSSLCertificates,
URI uri,
boolean binary,
boolean includeProfiles
) throws IOException, GeneralSecurityException {
final WebClient client = new WebClient(dacConfig, credentialsServiceProvider, userName, password,
checkSSLCertificates);
BackupOptions options = new BackupOptions(uri.toString(), binary, includeProfiles);
return client.buildPost(BackupStats.class, "/backup", options);
}
static CheckpointInfo createCheckpoint(
DACConfig dacConfig,
Provider<CredentialsService> credentialsServiceProvider,
String userName,
String password,
boolean checkSSLCertificates,
URI uri,
boolean binary,
boolean includeProfiles
) throws IOException, GeneralSecurityException {
final WebClient client = new WebClient(dacConfig, credentialsServiceProvider, userName, password,
checkSSLCertificates);
BackupOptions options = new BackupOptions(uri.toString(), binary, includeProfiles);
return client.buildPost(CheckpointInfo.class, "/backup/checkpoint", options);
}
static BackupStats uploadsBackup(
DACConfig dacConfig,
Provider<CredentialsService> credentialsServiceProvider,
String userName,
String password,
boolean checkSSLCertificates,
URI uri,
boolean includeProfiles
) throws IOException, GeneralSecurityException {
final WebClient client = new WebClient(dacConfig, credentialsServiceProvider, userName, password,
checkSSLCertificates);
BackupResource.UploadsBackupOptions options = new ImmutableUploadsBackupOptions.Builder()
.setBackupDestinationDirectory(uri.toString())
.setIsIncludeProfiles(includeProfiles)
.build();
return client.buildPost(BackupStats.class, "/backup/uploads", options);
}
private static boolean validateOnlineOption(BackupManagerOptions options) {
return (options.userName != null) && (options.password != null);
}
// 需要包含一个main 函数,实际会执行的入口,daemon admin 入口会通过反射处理调用
public static void main(String[] args) {
try {
final DACConfig dacConfig = DACConfig.newConfig();
final BackupResult backupResult = doMain(args, dacConfig);
int returnCode = backupResult.getExitStatus();
if (returnCode != 0) {
System.exit(returnCode);
}
} catch (Exception e) {
AdminLogger.log("Failed to create backup", e);
System.exit(1);
}
}
public static BackupResult doMain(String[] args, DACConfig dacConfig) {
final BackupManagerOptions options = new BackupManagerOptions();
JCommander jc = JCommander.newBuilder().addObject(options).build();
jc.setProgramName("dremio-admin backup");
final ImmutableBackupResult.Builder result = new ImmutableBackupResult.Builder();
try {
jc.parse(args);
} catch (ParameterException p) {
AdminLogger.log(p.getMessage());
jc.usage();
return result.setExitStatus(1).build();
}
if(options.help) {
jc.usage();
return result.setExitStatus(0).build();
}
if (options.localAttach && (options.userName != null || options.password != null)) {
AdminLogger.log("Do not pass username or password when running in local-attach mode");
jc.usage();
return result.setExitStatus(1).build();
}
final SabotConfig sabotConfig = dacConfig.getConfig().getSabotConfig();
final ScanResult scanResult = ClassPathScanner.fromPrescan(sabotConfig);
try (CredentialsService credentialsService = CredentialsService.newInstance(dacConfig.getConfig(), scanResult)) {
if (!dacConfig.isMaster) {
throw new UnsupportedOperationException("Backup should be ran on master node. ");
}
// Make sure that unqualified paths are resolved locally first, and default filesystem
// is pointing to file
Path backupDir = Path.of(options.backupDir);
final String scheme = backupDir.toURI().getScheme();
if (scheme == null || "file".equals(scheme)) {
backupDir = HadoopFileSystem.getLocal(new Configuration()).makeQualified(backupDir);
}
URI target = backupDir.toURI();
if (options.localAttach) {
LOGGER.info("Running backup attaching to existing Dremio Server processes");
String[] backupArgs = {"backup",options.backupDir, Boolean.toString(!options.json), Boolean.toString(options.profiles)};
try {
DremioAttach.main(backupArgs);
} catch (NoClassDefFoundError error) {
AdminLogger.log(
"A JDK is required to use local-attach mode. Please make sure JAVA_HOME is correctly configured");
}
} else {
if (options.userName == null) {
options.userName = System.console().readLine("username: ");
}
if (options.password == null) {
char[] pwd = System.console().readPassword("password: ");
options.password = new String(pwd);
}
if (!validateOnlineOption(options)) {
throw new ParameterException("User credential is required.");
}
final CredentialsService credService = options.acceptAll ? null : credentialsService;
final boolean checkSSLCertificates = options.acceptAll;
if (!options.sameProcess) {
LOGGER.info("Running backup using REST API");
BackupStats backupStats = createBackup(dacConfig, () -> credService, options.userName, options.password,
checkSSLCertificates, target, !options.json, options.profiles);
AdminLogger.log("Backup created at {}, dremio tables {}, uploaded files {}",
backupStats.getBackupPath(), backupStats.getTables(), backupStats.getFiles());
result.setBackupStats(backupStats);
} else {
LOGGER.info("Running backup using Admin CLI process");
result.setBackupStats(backupUsingCliProcess(dacConfig, options, credentialsService, target,
checkSSLCertificates));
}
}
return result.setExitStatus(0).build();
} catch (Exception e) {
AdminLogger.log("Failed to create backup at {}:", options.backupDir, e);
return result.setExitStatus(1).build();
}
}
private static BackupStats backupUsingCliProcess(DACConfig dacConfig, BackupManagerOptions options,
CredentialsService credentialsService, URI target, boolean checkSSLCertificates) throws Exception {
CheckpointInfo checkpoint = null;
try {
//backup using same process is a 3 steps process: create DB checkpoint, backup uploads and backup DB
checkpoint = createCheckpoint(dacConfig, () -> credentialsService, options.userName,
options.password,
checkSSLCertificates, target, !options.json, !options.profiles);
AdminLogger.log("Checkpoint created");
final Path backupDestinationDirPath = Path.of(checkpoint.getBackupDestinationDir());
final FileSystem fs = HadoopFileSystem.get(backupDestinationDirPath,
new Configuration());
final BackupOptions backupOptions = new BackupOptions(checkpoint.getBackupDestinationDir(), !options.json,
options.profiles);
final Optional<LocalKVStoreProvider> optionalKvStoreProvider =
CmdUtils.getReadOnlyKVStoreProvider(dacConfig.getConfig().withValue(DremioConfig.DB_PATH_STRING,
checkpoint.getCheckpointPath()));
if (!optionalKvStoreProvider.isPresent()) {
throw new IllegalStateException("No KVStore detected");
}
try (final LocalKVStoreProvider localKVStoreProvider = optionalKvStoreProvider.get()) {
localKVStoreProvider.start();
final BackupStats tablesBackupStats = BackupRestoreUtil.createBackup(fs, backupOptions,
localKVStoreProvider, null, checkpoint);
final BackupStats uploadsBackupStats = uploadsBackup(dacConfig, () -> credentialsService, options.userName,
options.password, checkSSLCertificates, backupDestinationDirPath.toURI(), options.profiles);
final BackupStats backupStats = merge(uploadsBackupStats, tablesBackupStats);
AdminLogger.log("Backup created at {}, dremio tables {}, uploaded files {}",
backupStats.getBackupPath(), backupStats.getTables(), backupStats.getFiles());
return backupStats;
}
} finally {
if (checkpoint != null && StringUtils.isNotEmpty(checkpoint.getCheckpointPath())) {
FileUtils.deleteDirectory(new File(checkpoint.getCheckpointPath()));
}
}
}
private static BackupStats merge(BackupStats uploadStats, BackupStats tablesStats) {
return new BackupStats(uploadStats.getBackupPath(), tablesStats.getTables(), uploadStats.getFiles());
}
@Value.Immutable
interface BackupResult {
int getExitStatus();
Optional<BackupStats> getBackupStats();
}
}
说明
dremio admin cli 不少功能是直接使用了dremio 底层模块的能力(直接实例化对象处理的)并不是直接通过rest api 维护的,同时admin cli 也可以了解一些内部的运行机制
以上只是一个简单的说明,其他的可以参考此模式学习
参考资料
distribution/resources/src/main/resources/bin/dremio-admin
dac/daemon/src/main/java/com/dremio/dac/cmd/AdminCommandRunner.java
https://docs.dremio.com/software/advanced-administration/dremio-admin-cli/