dremio daemon 的admin 管理cli 简单说明

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/

posted on 2023-01-20 17:33  荣锋亮  阅读(51)  评论(0编辑  收藏  举报

导航