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    return builder.build();
439  }
440
441  @Override
442  public int hashCode() {
443    int hash = 33 * type.hashCode() + backupId != null ? backupId.hashCode() : 0;
444    if (backupRootDir != null) {
445      hash = 33 * hash + backupRootDir.hashCode();
446    }
447    hash = 33 * hash + state.hashCode();
448    hash = 33 * hash + phase.hashCode();
449    hash = 33 * hash + (int) (startTs ^ (startTs >>> 32));
450    hash = 33 * hash + (int) (completeTs ^ (completeTs >>> 32));
451    hash = 33 * hash + (int) (totalBytesCopied ^ (totalBytesCopied >>> 32));
452    if (hlogTargetDir != null) {
453      hash = 33 * hash + hlogTargetDir.hashCode();
454    }
455    return hash;
456  }
457
458  @Override
459  public boolean equals(Object obj) {
460    if (obj instanceof BackupInfo) {
461      BackupInfo other = (BackupInfo) obj;
462      try {
463        return Bytes.equals(toByteArray(), other.toByteArray());
464      } catch (IOException e) {
465        LOG.error(e.toString(), e);
466        return false;
467      }
468    } else {
469      return false;
470    }
471  }
472
473  @Override
474  public String toString() {
475    return backupId;
476  }
477
478  public byte[] toByteArray() throws IOException {
479    return toProtosBackupInfo().toByteArray();
480  }
481
482  private void setBackupTableInfoMap(BackupProtos.BackupInfo.Builder builder) {
483    for (Entry<TableName, BackupTableInfo> entry : backupTableInfoMap.entrySet()) {
484      builder.addBackupTableInfo(entry.getValue().toProto());
485    }
486  }
487
488  private void setTableSetTimestampMap(BackupProtos.BackupInfo.Builder builder) {
489    if (this.getTableSetTimestampMap() != null) {
490      for (Entry<TableName, Map<String, Long>> entry : this.getTableSetTimestampMap().entrySet()) {
491        builder.putTableSetTimestamp(entry.getKey().getNameAsString(),
492          BackupProtos.BackupInfo.RSTimestampMap.newBuilder().putAllRsTimestamp(entry.getValue())
493            .build());
494      }
495    }
496  }
497
498  public static BackupInfo fromByteArray(byte[] data) throws IOException {
499    return fromProto(BackupProtos.BackupInfo.parseFrom(data));
500  }
501
502  public static BackupInfo fromStream(final InputStream stream) throws IOException {
503    return fromProto(BackupProtos.BackupInfo.parseDelimitedFrom(stream));
504  }
505
506  public static BackupInfo fromProto(BackupProtos.BackupInfo proto) {
507    BackupInfo context = new BackupInfo();
508    context.setBackupId(proto.getBackupId());
509    context.setBackupTableInfoMap(toMap(proto.getBackupTableInfoList()));
510    context.setTableSetTimestampMap(getTableSetTimestampMap(proto.getTableSetTimestampMap()));
511    context.setCompleteTs(proto.getCompleteTs());
512    if (proto.hasFailedMessage()) {
513      context.setFailedMsg(proto.getFailedMessage());
514    }
515    if (proto.hasBackupState()) {
516      context.setState(BackupInfo.BackupState.valueOf(proto.getBackupState().name()));
517    }
518
519    context
520      .setHLogTargetDir(BackupUtils.getLogBackupDir(proto.getBackupRootDir(), proto.getBackupId()));
521
522    if (proto.hasBackupPhase()) {
523      context.setPhase(BackupPhase.valueOf(proto.getBackupPhase().name()));
524    }
525    if (proto.hasProgress()) {
526      context.setProgress(proto.getProgress());
527    }
528    context.setStartTs(proto.getStartTs());
529    context.setBackupRootDir(proto.getBackupRootDir());
530    context.setType(BackupType.valueOf(proto.getBackupType().name()));
531    context.setWorkers(proto.getWorkersNumber());
532    context.setBandwidth(proto.getBandwidth());
533    return context;
534  }
535
536  private static Map<TableName, BackupTableInfo> toMap(List<BackupProtos.BackupTableInfo> list) {
537    HashMap<TableName, BackupTableInfo> map = new HashMap<>();
538    for (BackupProtos.BackupTableInfo tbs : list) {
539      map.put(ProtobufUtil.toTableName(tbs.getTableName()), BackupTableInfo.convert(tbs));
540    }
541    return map;
542  }
543
544  private static Map<TableName, Map<String, Long>>
545    getTableSetTimestampMap(Map<String, BackupProtos.BackupInfo.RSTimestampMap> map) {
546    Map<TableName, Map<String, Long>> tableSetTimestampMap = new HashMap<>();
547    for (Entry<String, BackupProtos.BackupInfo.RSTimestampMap> entry : map.entrySet()) {
548      tableSetTimestampMap.put(TableName.valueOf(entry.getKey()),
549        entry.getValue().getRsTimestampMap());
550    }
551
552    return tableSetTimestampMap;
553  }
554
555  public String getShortDescription() {
556    StringBuilder sb = new StringBuilder();
557    sb.append("{");
558    sb.append("ID=" + backupId).append(",");
559    sb.append("Type=" + getType()).append(",");
560    sb.append("Tables=" + getTableListAsString()).append(",");
561    sb.append("State=" + getState()).append(",");
562    Calendar cal = Calendar.getInstance();
563    cal.setTimeInMillis(getStartTs());
564    Date date = cal.getTime();
565    sb.append("Start time=" + date).append(",");
566    if (state == BackupState.FAILED) {
567      sb.append("Failed message=" + getFailedMsg()).append(",");
568    } else if (state == BackupState.RUNNING) {
569      sb.append("Phase=" + getPhase()).append(",");
570    } else if (state == BackupState.COMPLETE) {
571      cal = Calendar.getInstance();
572      cal.setTimeInMillis(getCompleteTs());
573      date = cal.getTime();
574      sb.append("End time=" + date).append(",");
575    }
576    sb.append("Progress=" + getProgress() + "%");
577    sb.append("}");
578
579    return sb.toString();
580  }
581
582  public String getStatusAndProgressAsString() {
583    StringBuilder sb = new StringBuilder();
584    sb.append("id: ").append(getBackupId()).append(" state: ").append(getState())
585      .append(" progress: ").append(getProgress());
586    return sb.toString();
587  }
588
589  public String getTableListAsString() {
590    StringBuilder sb = new StringBuilder();
591    sb.append("{");
592    sb.append(StringUtils.join(backupTableInfoMap.keySet(), ","));
593    sb.append("}");
594    return sb.toString();
595  }
596
597  /**
598   * We use only time stamps to compare objects during sort operation
599   */
600  @Override
601  public int compareTo(BackupInfo o) {
602    Long thisTS =
603      Long.valueOf(this.getBackupId().substring(this.getBackupId().lastIndexOf("_") + 1));
604    Long otherTS = Long.valueOf(o.getBackupId().substring(o.getBackupId().lastIndexOf("_") + 1));
605    return thisTS.compareTo(otherTS);
606  }
607}