001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.backup;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.ArrayList;
023import java.util.Calendar;
024import java.util.Date;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
029import java.util.Set;
030import java.util.function.Predicate;
031import org.apache.commons.lang3.StringUtils;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.backup.util.BackupUtils;
034import org.apache.hadoop.hbase.util.Bytes;
035import org.apache.yetus.audience.InterfaceAudience;
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
040import org.apache.hadoop.hbase.shaded.protobuf.generated.BackupProtos;
041
042/**
043 * An object to encapsulate the information for each backup session
044 */
045@InterfaceAudience.Private
046public class BackupInfo implements Comparable<BackupInfo> {
047  private static final Logger LOG = LoggerFactory.getLogger(BackupInfo.class);
048  private static final int MAX_FAILED_MESSAGE_LENGTH = 1024;
049
050  public interface Filter extends Predicate<BackupInfo> {
051    /** Returns true if the BackupInfo passes the filter, false otherwise */
052    @Override
053    boolean test(BackupInfo backupInfo);
054  }
055
056  public static Filter withRoot(String backupRoot) {
057    return info -> info.getBackupRootDir().equals(backupRoot);
058  }
059
060  public static Filter withType(BackupType type) {
061    return info -> info.getType() == type;
062  }
063
064  public static Filter withState(BackupState state) {
065    return info -> info.getState() == state;
066  }
067
068  /**
069   * Backup session states
070   */
071  public enum BackupState {
072    RUNNING,
073    COMPLETE,
074    FAILED
075  }
076
077  /**
078   * BackupPhase - phases of an ACTIVE backup session (running), when state of a backup session is
079   * BackupState.RUNNING
080   */
081  public enum BackupPhase {
082    REQUEST,
083    SETUP_WAL_REPLICATION,
084    SNAPSHOT,
085    PREPARE_INCREMENTAL,
086    SNAPSHOTCOPY,
087    INCREMENTAL_COPY,
088    STORE_MANIFEST
089  }
090
091  /**
092   * Backup id
093   */
094  private String backupId;
095
096  /**
097   * Backup type, full or incremental
098   */
099  private BackupType type;
100
101  /**
102   * Target root directory for storing the backup files
103   */
104  private String backupRootDir;
105
106  /**
107   * Backup state
108   */
109  private BackupState state;
110
111  /**
112   * Backup phase
113   */
114  private BackupPhase phase = BackupPhase.REQUEST;
115
116  /**
117   * Backup failure message
118   */
119  private String failedMsg;
120
121  /**
122   * Backup status map for all tables
123   */
124  private Map<TableName, BackupTableInfo> backupTableInfoMap;
125
126  /**
127   * Actual start timestamp of a backup process
128   */
129  private long startTs;
130
131  /**
132   * Actual end timestamp of the backup process
133   */
134  private long completeTs;
135
136  /**
137   * Committed WAL timestamp for incremental backup
138   */
139  private long incrCommittedWalTs;
140
141  /**
142   * Total bytes of incremental logs copied
143   */
144  private long totalBytesCopied;
145
146  /**
147   * For incremental backup, a location of a backed-up hlogs
148   */
149  private String hlogTargetDir = null;
150
151  /**
152   * Incremental backup file list
153   */
154  private List<String> incrBackupFileList;
155
156  /**
157   * New region server log timestamps for table set after distributed log roll. The keys consist of
158   * all tables that are part of the backup chain of the backup root (not just the tables that were
159   * specified when creating the backup, which could be a subset). The value is a map of
160   * RegionServer hostname to the last log-roll timestamp, i.e. the point up to which logs are
161   * included in the backup.
162   */
163  private Map<TableName, Map<String, Long>> tableSetTimestampMap;
164
165  /**
166   * Previous Region server log timestamps for table set after distributed log roll key - table
167   * name, value - map of RegionServer hostname -> last log rolled timestamp
168   */
169  private Map<TableName, Map<String, Long>> incrTimestampMap;
170
171  /**
172   * Backup progress in %% (0-100)
173   */
174  private int progress;
175
176  /**
177   * Number of parallel workers. -1 - system defined
178   */
179  private int workers = -1;
180
181  /**
182   * Bandwidth per worker in MB per sec. -1 - unlimited
183   */
184  private long bandwidth = -1;
185
186  /**
187   * Do not verify checksum between source snapshot and exported snapshot
188   */
189  private boolean noChecksumVerify;
190
191  private boolean continuousBackupEnabled;
192
193  public BackupInfo() {
194    backupTableInfoMap = new HashMap<>();
195  }
196
197  public BackupInfo(String backupId, BackupType type, TableName[] tables, String targetRootDir) {
198    this();
199    this.backupId = backupId;
200    this.type = type;
201    this.backupRootDir = targetRootDir;
202    this.addTables(tables);
203    if (type == BackupType.INCREMENTAL) {
204      setHLogTargetDir(BackupUtils.getLogBackupDir(targetRootDir, backupId));
205    }
206    this.startTs = 0;
207    this.completeTs = 0;
208    this.continuousBackupEnabled = false;
209  }
210
211  public int getWorkers() {
212    return workers;
213  }
214
215  public void setWorkers(int workers) {
216    this.workers = workers;
217  }
218
219  public long getBandwidth() {
220    return bandwidth;
221  }
222
223  public void setBandwidth(long bandwidth) {
224    this.bandwidth = bandwidth;
225  }
226
227  public void setNoChecksumVerify(boolean noChecksumVerify) {
228    this.noChecksumVerify = noChecksumVerify;
229  }
230
231  public boolean getNoChecksumVerify() {
232    return noChecksumVerify;
233  }
234
235  public void setBackupTableInfoMap(Map<TableName, BackupTableInfo> backupTableInfoMap) {
236    this.backupTableInfoMap = backupTableInfoMap;
237  }
238
239  public Map<TableName, Map<String, Long>> getTableSetTimestampMap() {
240    return tableSetTimestampMap;
241  }
242
243  public void setTableSetTimestampMap(Map<TableName, Map<String, Long>> tableSetTimestampMap) {
244    this.tableSetTimestampMap = tableSetTimestampMap;
245  }
246
247  public void setType(BackupType type) {
248    this.type = type;
249  }
250
251  public void setBackupRootDir(String targetRootDir) {
252    this.backupRootDir = targetRootDir;
253  }
254
255  public void setTotalBytesCopied(long totalBytesCopied) {
256    this.totalBytesCopied = totalBytesCopied;
257  }
258
259  /**
260   * Set progress (0-100%)
261   * @param p progress value
262   */
263  public void setProgress(int p) {
264    this.progress = p;
265  }
266
267  /**
268   * Get current progress
269   */
270  public int getProgress() {
271    return progress;
272  }
273
274  public String getBackupId() {
275    return backupId;
276  }
277
278  public void setBackupId(String backupId) {
279    this.backupId = backupId;
280  }
281
282  public BackupTableInfo getBackupTableInfo(TableName table) {
283    return this.backupTableInfoMap.get(table);
284  }
285
286  public String getFailedMsg() {
287    return failedMsg;
288  }
289
290  public void setFailedMsg(String failedMsg) {
291    if (failedMsg != null && failedMsg.length() > MAX_FAILED_MESSAGE_LENGTH) {
292      failedMsg = failedMsg.substring(0, MAX_FAILED_MESSAGE_LENGTH);
293    }
294    this.failedMsg = failedMsg;
295  }
296
297  public long getStartTs() {
298    return startTs;
299  }
300
301  public void setStartTs(long startTs) {
302    this.startTs = startTs;
303  }
304
305  public long getCompleteTs() {
306    return completeTs;
307  }
308
309  public void setCompleteTs(long endTs) {
310    this.completeTs = endTs;
311  }
312
313  public long getIncrCommittedWalTs() {
314    return incrCommittedWalTs;
315  }
316
317  public void setIncrCommittedWalTs(long timestamp) {
318    this.incrCommittedWalTs = timestamp;
319  }
320
321  public long getTotalBytesCopied() {
322    return totalBytesCopied;
323  }
324
325  public BackupState getState() {
326    return state;
327  }
328
329  public void setState(BackupState flag) {
330    this.state = flag;
331  }
332
333  public BackupPhase getPhase() {
334    return phase;
335  }
336
337  public void setPhase(BackupPhase phase) {
338    this.phase = phase;
339  }
340
341  public BackupType getType() {
342    return type;
343  }
344
345  public void setSnapshotName(TableName table, String snapshotName) {
346    this.backupTableInfoMap.get(table).setSnapshotName(snapshotName);
347  }
348
349  public String getSnapshotName(TableName table) {
350    return this.backupTableInfoMap.get(table).getSnapshotName();
351  }
352
353  public List<String> getSnapshotNames() {
354    List<String> snapshotNames = new ArrayList<>();
355    for (BackupTableInfo backupStatus : this.backupTableInfoMap.values()) {
356      snapshotNames.add(backupStatus.getSnapshotName());
357    }
358    return snapshotNames;
359  }
360
361  public Set<TableName> getTables() {
362    return this.backupTableInfoMap.keySet();
363  }
364
365  public List<TableName> getTableNames() {
366    return new ArrayList<>(backupTableInfoMap.keySet());
367  }
368
369  public void addTables(TableName[] tables) {
370    for (TableName table : tables) {
371      BackupTableInfo backupStatus = new BackupTableInfo(table, this.backupRootDir, this.backupId);
372      this.backupTableInfoMap.put(table, backupStatus);
373    }
374  }
375
376  public void setTables(List<TableName> tables) {
377    this.backupTableInfoMap.clear();
378    for (TableName table : tables) {
379      BackupTableInfo backupStatus = new BackupTableInfo(table, this.backupRootDir, this.backupId);
380      this.backupTableInfoMap.put(table, backupStatus);
381    }
382  }
383
384  public String getBackupRootDir() {
385    return backupRootDir;
386  }
387
388  public String getTableBackupDir(TableName tableName) {
389    return BackupUtils.getTableBackupDir(backupRootDir, backupId, tableName);
390  }
391
392  public void setHLogTargetDir(String hlogTagetDir) {
393    this.hlogTargetDir = hlogTagetDir;
394  }
395
396  public String getHLogTargetDir() {
397    return hlogTargetDir;
398  }
399
400  public List<String> getIncrBackupFileList() {
401    return incrBackupFileList;
402  }
403
404  public void setIncrBackupFileList(List<String> incrBackupFileList) {
405    this.incrBackupFileList = incrBackupFileList;
406  }
407
408  /**
409   * Set the new region server log timestamps after distributed log roll
410   * @param prevTableSetTimestampMap table timestamp map
411   */
412  public void setIncrTimestampMap(Map<TableName, Map<String, Long>> prevTableSetTimestampMap) {
413    this.incrTimestampMap = prevTableSetTimestampMap;
414  }
415
416  /**
417   * Get new region server log timestamps after distributed log roll
418   * @return new region server log timestamps
419   */
420  public Map<TableName, Map<String, Long>> getIncrTimestampMap() {
421    return this.incrTimestampMap;
422  }
423
424  public TableName getTableBySnapshot(String snapshotName) {
425    for (Entry<TableName, BackupTableInfo> entry : this.backupTableInfoMap.entrySet()) {
426      if (snapshotName.equals(entry.getValue().getSnapshotName())) {
427        return entry.getKey();
428      }
429    }
430    return null;
431  }
432
433  public BackupProtos.BackupInfo toProtosBackupInfo() {
434    BackupProtos.BackupInfo.Builder builder = BackupProtos.BackupInfo.newBuilder();
435    builder.setBackupId(getBackupId());
436    setBackupTableInfoMap(builder);
437    setTableSetTimestampMap(builder);
438    builder.setCompleteTs(getCompleteTs());
439    if (getFailedMsg() != null) {
440      builder.setFailedMessage(getFailedMsg());
441    }
442    if (getState() != null) {
443      builder.setBackupState(BackupProtos.BackupInfo.BackupState.valueOf(getState().name()));
444    }
445    if (getPhase() != null) {
446      builder.setBackupPhase(BackupProtos.BackupInfo.BackupPhase.valueOf(getPhase().name()));
447    }
448
449    builder.setProgress(getProgress());
450    builder.setStartTs(getStartTs());
451    builder.setBackupRootDir(getBackupRootDir());
452    builder.setBackupType(BackupProtos.BackupType.valueOf(getType().name()));
453    builder.setWorkersNumber(workers);
454    builder.setBandwidth(bandwidth);
455    builder.setTotalBytesCopied(totalBytesCopied);
456    builder.setNoChecksumVerify(noChecksumVerify);
457    if (incrBackupFileList != null) {
458      builder.addAllIncrBackupFileList(incrBackupFileList);
459    }
460    if (incrTimestampMap != null) {
461      for (Entry<TableName, Map<String, Long>> entry : incrTimestampMap.entrySet()) {
462        builder.putIncrTimestampMap(entry.getKey().getNameAsString(),
463          BackupProtos.BackupInfo.RSTimestampMap.newBuilder().putAllRsTimestamp(entry.getValue())
464            .build());
465      }
466    }
467    builder.setContinuousBackupEnabled(isContinuousBackupEnabled());
468    builder.setIncrCommittedWalTs(getIncrCommittedWalTs());
469    return builder.build();
470  }
471
472  @Override
473  public int hashCode() {
474    int hash = 33 * type.hashCode() + backupId != null ? backupId.hashCode() : 0;
475    if (backupRootDir != null) {
476      hash = 33 * hash + backupRootDir.hashCode();
477    }
478    hash = 33 * hash + state.hashCode();
479    hash = 33 * hash + phase.hashCode();
480    hash = 33 * hash + (int) (startTs ^ (startTs >>> 32));
481    hash = 33 * hash + (int) (completeTs ^ (completeTs >>> 32));
482    hash = 33 * hash + (int) (totalBytesCopied ^ (totalBytesCopied >>> 32));
483    if (hlogTargetDir != null) {
484      hash = 33 * hash + hlogTargetDir.hashCode();
485    }
486    return hash;
487  }
488
489  @Override
490  public boolean equals(Object obj) {
491    if (obj instanceof BackupInfo) {
492      BackupInfo other = (BackupInfo) obj;
493      try {
494        return Bytes.equals(toByteArray(), other.toByteArray());
495      } catch (IOException e) {
496        LOG.error(e.toString(), e);
497        return false;
498      }
499    } else {
500      return false;
501    }
502  }
503
504  @Override
505  public String toString() {
506    return backupId;
507  }
508
509  public byte[] toByteArray() throws IOException {
510    return toProtosBackupInfo().toByteArray();
511  }
512
513  private void setBackupTableInfoMap(BackupProtos.BackupInfo.Builder builder) {
514    for (Entry<TableName, BackupTableInfo> entry : backupTableInfoMap.entrySet()) {
515      builder.addBackupTableInfo(entry.getValue().toProto());
516    }
517  }
518
519  private void setTableSetTimestampMap(BackupProtos.BackupInfo.Builder builder) {
520    if (this.getTableSetTimestampMap() != null) {
521      for (Entry<TableName, Map<String, Long>> entry : this.getTableSetTimestampMap().entrySet()) {
522        builder.putTableSetTimestamp(entry.getKey().getNameAsString(),
523          BackupProtos.BackupInfo.RSTimestampMap.newBuilder().putAllRsTimestamp(entry.getValue())
524            .build());
525      }
526    }
527  }
528
529  public static BackupInfo fromByteArray(byte[] data) throws IOException {
530    return fromProto(BackupProtos.BackupInfo.parseFrom(data));
531  }
532
533  public static BackupInfo fromStream(final InputStream stream) throws IOException {
534    return fromProto(BackupProtos.BackupInfo.parseDelimitedFrom(stream));
535  }
536
537  public static BackupInfo fromProto(BackupProtos.BackupInfo proto) {
538    BackupInfo context = new BackupInfo();
539    context.setBackupId(proto.getBackupId());
540    context.setBackupTableInfoMap(toMap(proto.getBackupTableInfoList()));
541    context.setTableSetTimestampMap(getTableSetTimestampMap(proto.getTableSetTimestampMap()));
542    context.setCompleteTs(proto.getCompleteTs());
543    if (proto.hasFailedMessage()) {
544      context.setFailedMsg(proto.getFailedMessage());
545    }
546    if (proto.hasBackupState()) {
547      context.setState(BackupInfo.BackupState.valueOf(proto.getBackupState().name()));
548    }
549
550    context
551      .setHLogTargetDir(BackupUtils.getLogBackupDir(proto.getBackupRootDir(), proto.getBackupId()));
552
553    if (proto.hasBackupPhase()) {
554      context.setPhase(BackupPhase.valueOf(proto.getBackupPhase().name()));
555    }
556    if (proto.hasProgress()) {
557      context.setProgress(proto.getProgress());
558    }
559    context.setStartTs(proto.getStartTs());
560    context.setBackupRootDir(proto.getBackupRootDir());
561    context.setType(BackupType.valueOf(proto.getBackupType().name()));
562    context.setWorkers(proto.getWorkersNumber());
563    context.setBandwidth(proto.getBandwidth());
564    context.setTotalBytesCopied(proto.getTotalBytesCopied());
565    context.setNoChecksumVerify(proto.getNoChecksumVerify());
566    if (proto.getIncrBackupFileListCount() > 0) {
567      context.setIncrBackupFileList(new ArrayList<>(proto.getIncrBackupFileListList()));
568    }
569    if (proto.getIncrTimestampMapCount() > 0) {
570      context.setIncrTimestampMap(getTableSetTimestampMap(proto.getIncrTimestampMapMap()));
571    }
572    context.setContinuousBackupEnabled(proto.getContinuousBackupEnabled());
573    context.setIncrCommittedWalTs(proto.getIncrCommittedWalTs());
574    return context;
575  }
576
577  private static Map<TableName, BackupTableInfo> toMap(List<BackupProtos.BackupTableInfo> list) {
578    HashMap<TableName, BackupTableInfo> map = new HashMap<>();
579    for (BackupProtos.BackupTableInfo tbs : list) {
580      map.put(ProtobufUtil.toTableName(tbs.getTableName()), BackupTableInfo.convert(tbs));
581    }
582    return map;
583  }
584
585  private static Map<TableName, Map<String, Long>>
586    getTableSetTimestampMap(Map<String, BackupProtos.BackupInfo.RSTimestampMap> map) {
587    Map<TableName, Map<String, Long>> tableSetTimestampMap = new HashMap<>();
588    for (Entry<String, BackupProtos.BackupInfo.RSTimestampMap> entry : map.entrySet()) {
589      tableSetTimestampMap.put(TableName.valueOf(entry.getKey()),
590        entry.getValue().getRsTimestampMap());
591    }
592
593    return tableSetTimestampMap;
594  }
595
596  public String getShortDescription() {
597    StringBuilder sb = new StringBuilder();
598    sb.append("{");
599    sb.append("ID=" + backupId).append(",");
600    sb.append("Type=" + getType()).append(",");
601    sb.append("IsContinuous=" + isContinuousBackupEnabled()).append(",");
602    sb.append("Tables=" + getTableListAsString()).append(",");
603    sb.append("State=" + getState()).append(",");
604    Calendar cal = Calendar.getInstance();
605    cal.setTimeInMillis(getStartTs());
606    Date date = cal.getTime();
607    sb.append("Start time=" + date).append(",");
608    if (state == BackupState.FAILED) {
609      sb.append("Failed message=" + getFailedMsg()).append(",");
610    } else if (state == BackupState.RUNNING) {
611      sb.append("Phase=" + getPhase()).append(",");
612    } else if (state == BackupState.COMPLETE) {
613      cal = Calendar.getInstance();
614      cal.setTimeInMillis(getCompleteTs());
615      date = cal.getTime();
616      sb.append("End time=" + date).append(",");
617      if (getType() == BackupType.INCREMENTAL) {
618        cal = Calendar.getInstance();
619        cal.setTimeInMillis(getIncrCommittedWalTs());
620        date = cal.getTime();
621        sb.append("Committed WAL time for incremental backup=" + date).append(",");
622      }
623    }
624    sb.append("Progress=" + getProgress() + "%");
625    sb.append("}");
626
627    return sb.toString();
628  }
629
630  public String getStatusAndProgressAsString() {
631    StringBuilder sb = new StringBuilder();
632    sb.append("id: ").append(getBackupId()).append(" state: ").append(getState())
633      .append(" progress: ").append(getProgress());
634    return sb.toString();
635  }
636
637  public String getTableListAsString() {
638    StringBuilder sb = new StringBuilder();
639    sb.append("{");
640    sb.append(StringUtils.join(backupTableInfoMap.keySet(), ","));
641    sb.append("}");
642    return sb.toString();
643  }
644
645  /**
646   * We use only time stamps to compare objects during sort operation
647   */
648  @Override
649  public int compareTo(BackupInfo o) {
650    Long thisTS =
651      Long.valueOf(this.getBackupId().substring(this.getBackupId().lastIndexOf("_") + 1));
652    Long otherTS = Long.valueOf(o.getBackupId().substring(o.getBackupId().lastIndexOf("_") + 1));
653    return thisTS.compareTo(otherTS);
654  }
655
656  public void setContinuousBackupEnabled(boolean continuousBackupEnabled) {
657    this.continuousBackupEnabled = continuousBackupEnabled;
658  }
659
660  public boolean isContinuousBackupEnabled() {
661    return this.continuousBackupEnabled;
662  }
663}