content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.bookkeeper.tools.cli.commands.bookie;
  
  import com.beust.jcommander.Parameter;
  import com.google.common.util.concurrent.UncheckedExecutionException;
  import java.io.IOException;
  import java.util.concurrent.TimeUnit;
  import lombok.Setter;
  import lombok.experimental.Accessors;
  import org.apache.bookkeeper.bookie.BookieImpl;
  import org.apache.bookkeeper.bookie.BookieShell;
  import org.apache.bookkeeper.client.BKException;
  import org.apache.bookkeeper.client.BookKeeper;
  import org.apache.bookkeeper.client.BookKeeperAdmin;
  import org.apache.bookkeeper.client.UpdateLedgerOp;
  import org.apache.bookkeeper.common.util.MathUtils;
  import org.apache.bookkeeper.conf.ClientConfiguration;
  import org.apache.bookkeeper.conf.ServerConfiguration;
  import org.apache.bookkeeper.net.BookieId;
  import org.apache.bookkeeper.tools.cli.helpers.BookieCommand;
  import org.apache.bookkeeper.tools.framework.CliFlags;
  import org.apache.bookkeeper.tools.framework.CliSpec;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * Command to update ledger command.
   */
  public class FlipBookieIdCommand extends BookieCommand<FlipBookieIdCommand.FlipBookieIdFlags> {
  
      static final Logger LOG = LoggerFactory.getLogger(FlipBookieIdCommand.class);
  
      private static final String NAME = "flip-bookie-id";
      private static final String DESC = "Update bookie id in ledgers (this may take a long time).";
  
      public FlipBookieIdCommand() {
          this(new FlipBookieIdFlags());
      }
  
      private FlipBookieIdCommand(FlipBookieIdFlags flags) {
          super(CliSpec.<FlipBookieIdFlags>newBuilder()
                    .withName(NAME)
                    .withDescription(DESC)
                    .withFlags(flags)
                    .build());
      }
  
      /**
       * Flags for update ledger command.
       */
      @Accessors(fluent = true)
      @Setter
      public static class FlipBookieIdFlags extends CliFlags {
  
          @Parameter(names = { "-host", "--hostname" },
              description = "Expects configuration useHostNameAsBookieID=true as the option value (default: ip address)")
          private boolean hostname;
  
          @Parameter(names = { "-s", "--updatepersec" },
              description = "Number of ledgers updating per second (default: 5 per sec)")
          private int updatePerSec = 5;
  
          @Parameter(names = { "-r",
                  "--maxOutstandingReads" }, description = "Max outstanding reads (default: 5 * updatespersec)")
          private int maxOutstandingReads = updatePerSec * 5;
  
          @Parameter(names = {"-l", "--limit"},
              description = "Maximum number of ledgers of ledgers to update (default: no limit)")
          private int limit = Integer.MIN_VALUE;
  
          @Parameter(names = { "-v", "--verbose" }, description = "Print status of the ledger updation (default: false)")
          private boolean verbose;
  
          @Parameter(names = { "-p", "--printprogress" },
              description = "Print messages on every configured seconds if verbose turned on (default: 10 secs)")
          private long printProgress = 10;
      }
  
      @Override
      public boolean apply(ServerConfiguration conf, FlipBookieIdFlags cmdFlags) {
          try {
              return updateLedger(conf, cmdFlags);
          } catch (Exception e) {
              throw new UncheckedExecutionException(e.getMessage(), e);
          }
      }
  
      private boolean updateLedger(ServerConfiguration conf, FlipBookieIdFlags flags)
          throws InterruptedException, BKException, IOException {
  
          if (!conf.getUseHostNameAsBookieID() && flags.hostname) {
              LOG.error("Expects configuration useHostNameAsBookieID=true as the option value");
              return false;
          } else if (conf.getUseHostNameAsBookieID() && !flags.hostname) {
              LOG.error("Expects configuration useHostNameAsBookieID=false as the option value'");
              return false;
          }
  
          final int rate = flags.updatePerSec;
          if (rate <= 0) {
              LOG.error("Invalid updatespersec {}, should be > 0", rate);
              return false;
          }
  
          final int maxOutstandingReads = flags.maxOutstandingReads;
          if (maxOutstandingReads <= 0) {
              LOG.error("Invalid maxOutstandingReads {}, should be > 0", maxOutstandingReads);
              return false;
          }
  
          final int limit = flags.limit;
          if (limit <= 0 && limit != Integer.MIN_VALUE) {
              LOG.error("Invalid limit {}, should be > 0", limit);
              return false;
          }
  
          final long printProgress;
          if (flags.verbose) {
              printProgress = 10;
          } else {
              printProgress = flags.printProgress;
          }
  
          final ClientConfiguration clientConfiguration = new ClientConfiguration();
          clientConfiguration.addConfiguration(conf);
          final BookKeeper bk = new BookKeeper(clientConfiguration);
          final BookKeeperAdmin admin = new BookKeeperAdmin(bk);
          final UpdateLedgerOp updateLedgerOp = new UpdateLedgerOp(bk, admin);
          final ServerConfiguration serverConfiguration = new ServerConfiguration(conf);
          final BookieId newBookieId = BookieImpl.getBookieId(serverConfiguration);
          serverConfiguration.setUseHostNameAsBookieID(!flags.hostname);
          final BookieId oldBookieId = BookieImpl.getBookieId(serverConfiguration);
  
          BookieShell.UpdateLedgerNotifier progressable = new BookieShell.UpdateLedgerNotifier() {
              long lastReport = System.nanoTime();
  
              @Override
              public void progress(long updated, long issued) {
                  if (printProgress <= 0) {
                      return; // disabled
                  }
                  if (TimeUnit.MILLISECONDS.toSeconds(MathUtils.elapsedMSec(lastReport)) >= printProgress) {
                      LOG.info("Number of ledgers issued={}, updated={}", issued, updated);
                      lastReport = MathUtils.nowInNano();
                  }
              }
          };
  
          try {
              updateLedgerOp.updateBookieIdInLedgers(oldBookieId, newBookieId, rate, maxOutstandingReads, limit,
                      progressable);
          } catch (IOException e) {
              LOG.error("Failed to update ledger metadata", e);
              return false;
          }
  
          return true;
      }
  }
  