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    SNAPSHOT,
084    PREPARE_INCREMENTAL,
085    SNAPSHOTCOPY,
086    INCREMENTAL_COPY,
087    STORE_MANIFEST
088  }
089
090  /**
091   * Backup id
092   */
093  private String backupId;
094
095  /**
096   * Backup type, full or incremental
097   */
098  private BackupType type;
099
100  /**
101   * Target root directory for storing the backup files
102   */
103  private String backupRootDir;
104
105  /**
106   * Backup state
107   */
108  private BackupState state;
109
110  /**
111   * Backup phase
112   */
113  private BackupPhase phase = BackupPhase.REQUEST;
114
115  /**
116   * Backup failure message
117   */
118  private String failedMsg;
119
120  /**
121   * Backup status map for all tables
122   */
123  private Map<TableName, BackupTableInfo> backupTableInfoMap;
124
125  /**
126   * Actual start timestamp of a backup process
127   */
128  private long startTs;
129
130  /**
131   * Actual end timestamp of the backup process
132   */
133  private long completeTs;
134
135  /**
136   * Total bytes of incremental logs copied
137   */
138  private long totalBytesCopied;
139
140  /**
141   * For incremental backup, a location of a backed-up hlogs
142   */
143  private String hlogTargetDir = null;
144
145  /**
146   * Incremental backup file list
147   */
148  private List<String> incrBackupFileList;
149
150  /**
151   * New region server log timestamps for table set after distributed log roll. The keys consist of
152   * all tables that are part of the backup chain of the backup root (not just the tables that were
153   * specified when creating the backup, which could be a subset). The value is a map of
154   * RegionServer hostname to the last log-roll timestamp, i.e. the point up to which logs are
155   * included in the backup.
156   */
157  private Map<TableName, Map<String, Long>> tableSetTimestampMap;
158
159  /**
160   * Previous Region server log timestamps for table set after distributed log roll key - table
161   * name, value - map of RegionServer hostname -> last log rolled timestamp
162   */
163  private Map<TableName, Map<String, Long>> incrTimestampMap;
164
165  /**
166   * Backup progress in %% (0-100)
167   */
168  private int progress;
169
170  /**
171   * Number of parallel workers. -1 - system defined
172   */
173  private int workers = -1;
174
175  /**
176   * Bandwidth per worker in MB per sec. -1 - unlimited
177   */
178  private long bandwidth = -1;
179
180  /**
181   * Do not verify checksum between source snapshot and exported snapshot
182   */
183  private boolean noChecksumVerify;
184
185  public BackupInfo() {
186    backupTableInfoMap = new HashMap<>();
187  }
188
189  public BackupInfo(String backupId, BackupType type, TableName[] tables, String targetRootDir) {
190    this();
191    this.backupId = backupId;
192    this.type = type;
193    this.backupRootDir = targetRootDir;
194    this.addTables(tables);
195    if (type == BackupType.INCREMENTAL) {
196      setHLogTargetDir(BackupUtils.getLogBackupDir(targetRootDir, backupId));
197    }
198    this.startTs = 0;
199    this.completeTs = 0;
200  }
201
202  public int getWorkers() {
203    return workers;
204  }
205
206  public void setWorkers(int workers) {
207    this.workers = workers;
208  }
209
210  public long getBandwidth() {
211    return bandwidth;
212  }
213
214  public void setBandwidth(long bandwidth) {
215    this.bandwidth = bandwidth;
216  }
217
218  public void setNoChecksumVerify(boolean noChecksumVerify) {
219    this.noChecksumVerify = noChecksumVerify;
220  }
221
222  public boolean getNoChecksumVerify() {
223    return noChecksumVerify;
224  }
225
226  public void setBackupTableInfoMap(Map<TableName, BackupTableInfo> backupTableInfoMap) {
227    this.backupTableInfoMap = backupTableInfoMap;
228  }
229
230  public Map<TableName, Map<String, Long>> getTableSetTimestampMap() {
231    return tableSetTimestampMap;
232  }
233
234  public void setTableSetTimestampMap(Map<TableName, Map<String, Long>> tableSetTimestampMap) {
235    this.tableSetTimestampMap = tableSetTimestampMap;
236  }
237
238  public void setType(BackupType type) {
239    this.type = type;
240  }
241
242  public void setBackupRootDir(String targetRootDir) {
243    this.backupRootDir = targetRootDir;
244  }
245
246  public void setTotalBytesCopied(long totalBytesCopied) {
247    this.totalBytesCopied = totalBytesCopied;
248  }
249
250  /**
251   * Set progress (0-100%)
252   * @param p progress value
253   */
254  public void setProgress(int p) {
255    this.progress = p;
256  }
257
258  /**
259   * Get current progress
260   */
261  public int getProgress() {
262    return progress;
263  }
264
265  public String getBackupId() {
266    return backupId;
267  }
268
269  public void setBackupId(String backupId) {
270    this.backupId = backupId;
271  }
272
273  public BackupTableInfo getBackupTableInfo(TableName table) {
274    return this.backupTableInfoMap.get(table);
275  }
276
277  public String getFailedMsg() {
278    return failedMsg;
279  }
280
281  public void setFailedMsg(String failedMsg) {
282    if (failedMsg != null && failedMsg.length() > MAX_FAILED_MESSAGE_LENGTH) {
283      failedMsg = failedMsg.substring(0, MAX_FAILED_MESSAGE_LENGTH);
284    }
285    this.failedMsg = failedMsg;
286  }
287
288  public long getStartTs() {
289    return startTs;
290  }
291
292  public void setStartTs(long startTs) {
293    this.startTs = startTs;
294  }
295
296  public long getCompleteTs() {
297    return completeTs;
298  }
299
300  public void setCompleteTs(long endTs) {
301    this.completeTs = endTs;
302  }
303
304  public long getTotalBytesCopied() {
305    return totalBytesCopied;
306  }
307
308  public BackupState getState() {
309    return state;
310  }
311
312  public void setState(BackupState flag) {
313    this.state = flag;
314  }
315
316  public BackupPhase getPhase() {
317    return phase;
318  }
319
320  public void setPhase(BackupPhase phase) {
321    this.phase = phase;
322  }
323
324  public BackupType getType() {
325    return type;
326  }
327
328  public void setSnapshotName(TableName table, String snapshotName) {
329    this.backupTableInfoMap.get(table).setSnapshotName(snapshotName);
330  }
331
332  public String getSnapshotName(TableName table) {
333    return this.backupTableInfoMap.get(table).getSnapshotName();
334  }
335
336  public List<String> getSnapshotNames() {
337    List<String> snapshotNames = new ArrayList<>();
338    for (BackupTableInfo backupStatus : this.backupTableInfoMap.values()) {
339      snapshotNames.add(backupStatus.getSnapshotName());
340    }
341    return snapshotNames;
342  }
343
344  public Set<TableName> getTables() {
345    return this.backupTableInfoMap.keySet();
346  }
347
348  public List<TableName> getTableNames() {
349    return new ArrayList<>(backupTableInfoMap.keySet());
350  }
351
352  public void addTables(TableName[] tables) {
353    for (TableName table : tables) {
354      BackupTableInfo backupStatus = new BackupTableInfo(table, this.backupRootDir, this.backupId);
355      this.backupTableInfoMap.put(table, backupStatus);
356    }
357  }
358
359  public void setTables(List<TableName> tables) {
360    this.backupTableInfoMap.clear();
361    for (TableName table : tables) {
362      BackupTableInfo backupStatus = new BackupTableInfo(table, this.backupRootDir, this.backupId);
363      this.backupTableInfoMap.put(table, backupStatus);
364    }
365  }
366
367  public String getBackupRootDir() {
368    return backupRootDir;
369  }
370
371  public String getTableBackupDir(TableName tableName) {
372    return BackupUtils.getTableBackupDir(backupRootDir, backupId, tableName);
373  }
374
375  public void setHLogTargetDir(String hlogTagetDir) {
376    this.hlogTargetDir = hlogTagetDir;
377  }
378
379  public String getHLogTargetDir() {
380    return hlogTargetDir;
381  }
382
383  public List<String> getIncrBackupFileList() {
384    return incrBackupFileList;
385  }
386
387  public void setIncrBackupFileList(List<String> incrBackupFileList) {
388    this.incrBackupFileList = incrBackupFileList;
389  }
390
391  /**
392   * Set the new region server log timestamps after distributed log roll
393   * @param prevTableSetTimestampMap table timestamp map
394   */
395  public void setIncrTimestampMap(Map<TableName, Map<String, Long>> prevTableSetTimestampMap) {
396    this.incrTimestampMap = prevTableSetTimestampMap;
397  }
398
399  /**
400   * Get new region server log timestamps after distributed log roll
401   * @return new region server log timestamps
402   */
403  public Map<TableName, Map<String, Long>> getIncrTimestampMap() {
404    return this.incrTimestampMap;
405  }
406
407  public TableName getTableBySnapshot(String snapshotName) {
408    for (Entry<TableName, BackupTableInfo> entry : this.backupTableInfoMap.entrySet()) {
409      if (snapshotName.equals(entry.getValue().getSnapshotName())) {
410        return entry.getKey();
411      }
412    }
413    return null;
414  }
415
416  public BackupProtos.BackupInfo toProtosBackupInfo() {
417    BackupProtos.BackupInfo.Builder builder = BackupProtos.BackupInfo.newBuilder();
418    builder.setBackupId(getBackupId());
419    setBackupTableInfoMap(builder);
420    setTableSetTimestampMap(builder);
421    builder.setCompleteTs(getCompleteTs());
422    if (getFailedMsg() != null) {
423      builder.setFailedMessage(getFailedMsg());
424    }
425    if (getState() != null) {
426      builder.setBackupState(BackupProtos.BackupInfo.BackupState.valueOf(getState().name()));
427    }
428    if (getPhase() != null) {
429      builder.setBackupPhase(BackupProtos.BackupInfo.BackupPhase.valueOf(getPhase().name()));
430    }
431
432    builder.setProgress(getProgress());
433    builder.setStartTs(getStartTs());
434    builder.setBackupRootDir(getBackupRootDir());
435    builder.setBackupType(BackupProtos.BackupType.valueOf(getType().name()));
436    builder.setWorkersNumber(workers);
437    builder.setBandwidth(bandwidth);
438    builder.setTotalBytesCopied(totalBytesCopied);
439    builder.setNoChecksumVerify(noChecksumVerify);
440    if (incrBackupFileList != null) {
441      builder.addAllIncrBackupFileList(incrBackupFileList);
442    }
443    if (incrTimestampMap != null) {
444      for (Entry<TableName, Map<String, Long>> entry : incrTimestampMap.entrySet()) {
445        builder.putIncrTimestampMap(entry.getKey().getNameAsString(),
446          BackupProtos.BackupInfo.RSTimestampMap.newBuilder().putAllRsTimestamp(entry.getValue())
447            .build());
448      }
449    }
450    return builder.build();
451  }
452
453  @Override
454  public int hashCode() {
455    int hash = 33 * type.hashCode() + backupId != null ? backupId.hashCode() : 0;
456    if (backupRootDir != null) {
457      hash = 33 * hash + backupRootDir.hashCode();
458    }
459    hash = 33 * hash + state.hashCode();
460    hash = 33 * hash + phase.hashCode();
461    hash = 33 * hash + (int) (startTs ^ (startTs >>> 32));
462    hash = 33 * hash + (int) (completeTs ^ (completeTs >>> 32));
463    hash = 33 * hash + (int) (totalBytesCopied ^ (totalBytesCopied >>> 32));
464    if (hlogTargetDir != null) {
465      hash = 33 * hash + hlogTargetDir.hashCode();
466    }
467    return hash;
468  }
469
470  @Override
471  public boolean equals(Object obj) {
472    if (obj instanceof BackupInfo) {
473      BackupInfo other = (BackupInfo) obj;
474      try {
475        return Bytes.equals(toByteArray(), other.toByteArray());
476      } catch (IOException e) {
477        LOG.error(e.toString(), e);
478        return false;
479      }
480    } else {
481      return false;
482    }
483  }
484
485  @Override
486  public String toString() {
487    return backupId;
488  }
489
490  public byte[] toByteArray() throws IOException {
491    return toProtosBackupInfo().toByteArray();
492  }
493
494  private void setBackupTableInfoMap(BackupProtos.BackupInfo.Builder builder) {
495    for (Entry<TableName, BackupTableInfo> entry : backupTableInfoMap.entrySet()) {
496      builder.addBackupTableInfo(entry.getValue().toProto());
497    }
498  }
499
500  private void setTableSetTimestampMap(BackupProtos.BackupInfo.Builder builder) {
501    if (this.getTableSetTimestampMap() != null) {
502      for (Entry<TableName, Map<String, Long>> entry : this.getTableSetTimestampMap().entrySet()) {
503        builder.putTableSetTimestamp(entry.getKey().getNameAsString(),
504          BackupProtos.BackupInfo.RSTimestampMap.newBuilder().putAllRsTimestamp(entry.getValue())
505            .build());
506      }
507    }
508  }
509
510  public static BackupInfo fromByteArray(byte[] data) throws IOException {
511    return fromProto(BackupProtos.BackupInfo.parseFrom(data));
512  }
513
514  public static BackupInfo fromStream(final InputStream stream) throws IOException {
515    return fromProto(BackupProtos.BackupInfo.parseDelimitedFrom(stream));
516  }
517
518  public static BackupInfo fromProto(BackupProtos.BackupInfo proto) {
519    BackupInfo context = new BackupInfo();
520    context.setBackupId(proto.getBackupId());
521    context.setBackupTableInfoMap(toMap(proto.getBackupTableInfoList()));
522    context.setTableSetTimestampMap(getTableSetTimestampMap(proto.getTableSetTimestampMap()));
523    context.setCompleteTs(proto.getCompleteTs());
524    if (proto.hasFailedMessage()) {
525      context.setFailedMsg(proto.getFailedMessage());
526    }
527    if (proto.hasBackupState()) {
528      context.setState(BackupInfo.BackupState.valueOf(proto.getBackupState().name()));
529    }
530
531    context
532      .setHLogTargetDir(BackupUtils.getLogBackupDir(proto.getBackupRootDir(), proto.getBackupId()));
533
534    if (proto.hasBackupPhase()) {
535      context.setPhase(BackupPhase.valueOf(proto.getBackupPhase().name()));
536    }
537    if (proto.hasProgress()) {
538      context.setProgress(proto.getProgress());
539    }
540    context.setStartTs(proto.getStartTs());
541    context.setBackupRootDir(proto.getBackupRootDir());
542    context.setType(BackupType.valueOf(proto.getBackupType().name()));
543    context.setWorkers(proto.getWorkersNumber());
544    context.setBandwidth(proto.getBandwidth());
545    context.setTotalBytesCopied(proto.getTotalBytesCopied());
546    context.setNoChecksumVerify(proto.getNoChecksumVerify());
547    if (proto.getIncrBackupFileListCount() > 0) {
548      context.setIncrBackupFileList(new ArrayList<>(proto.getIncrBackupFileListList()));
549    }
550    if (proto.getIncrTimestampMapCount() > 0) {
551      context.setIncrTimestampMap(getTableSetTimestampMap(proto.getIncrTimestampMapMap()));
552    }
553    return context;
554  }
555
556  private static Map<TableName, BackupTableInfo> toMap(List<BackupProtos.BackupTableInfo> list) {
557    HashMap<TableName, BackupTableInfo> map = new HashMap<>();
558    for (BackupProtos.BackupTableInfo tbs : list) {
559      map.put(ProtobufUtil.toTableName(tbs.getTableName()), BackupTableInfo.convert(tbs));
560    }
561    return map;
562  }
563
564  private static Map<TableName, Map<String, Long>>
565    getTableSetTimestampMap(Map<String, BackupProtos.BackupInfo.RSTimestampMap> map) {
566    Map<TableName, Map<String, Long>> tableSetTimestampMap = new HashMap<>();
567    for (Entry<String, BackupProtos.BackupInfo.RSTimestampMap> entry : map.entrySet()) {
568      tableSetTimestampMap.put(TableName.valueOf(entry.getKey()),
569        entry.getValue().getRsTimestampMap());
570    }
571
572    return tableSetTimestampMap;
573  }
574
575  public String getShortDescription() {
576    StringBuilder sb = new StringBuilder();
577    sb.append("{");
578    sb.append("ID=" + backupId).append(",");
579    sb.append("Type=" + getType()).append(",");
580    sb.append("Tables=" + getTableListAsString()).append(",");
581    sb.append("State=" + getState()).append(",");
582    Calendar cal = Calendar.getInstance();
583    cal.setTimeInMillis(getStartTs());
584    Date date = cal.getTime();
585    sb.append("Start time=" + date).append(",");
586    if (state == BackupState.FAILED) {
587      sb.append("Failed message=" + getFailedMsg()).append(",");
588    } else if (state == BackupState.RUNNING) {
589      sb.append("Phase=" + getPhase()).append(",");
590    } else if (state == BackupState.COMPLETE) {
591      cal = Calendar.getInstance();
592      cal.setTimeInMillis(getCompleteTs());
593      date = cal.getTime();
594      sb.append("End time=" + date).append(",");
595    }
596    sb.append("Progress=" + getProgress() + "%");
597    sb.append("}");
598
599    return sb.toString();
600  }
601
602  public String getStatusAndProgressAsString() {
603    StringBuilder sb = new StringBuilder();
604    sb.append("id: ").append(getBackupId()).append(" state: ").append(getState())
605      .append(" progress: ").append(getProgress());
606    return sb.toString();
607  }
608
609  public String getTableListAsString() {
610    StringBuilder sb = new StringBuilder();
611    sb.append("{");
612    sb.append(StringUtils.join(backupTableInfoMap.keySet(), ","));
613    sb.append("}");
614    return sb.toString();
615  }
616
617  /**
618   * We use only time stamps to compare objects during sort operation
619   */
620  @Override
621  public int compareTo(BackupInfo o) {
622    Long thisTS =
623      Long.valueOf(this.getBackupId().substring(this.getBackupId().lastIndexOf("_") + 1));
624    Long otherTS = Long.valueOf(o.getBackupId().substring(o.getBackupId().lastIndexOf("_") + 1));
625    return thisTS.compareTo(otherTS);
626  }
627}