View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.backup.impl;
19  
20  import java.io.Closeable;
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Map.Entry;
28  import java.util.Set;
29  import java.util.TreeSet;
30  
31  import org.apache.commons.lang.StringUtils;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.hbase.Cell;
36  import org.apache.hadoop.hbase.CellUtil;
37  import org.apache.hadoop.hbase.HBaseConfiguration;
38  import org.apache.hadoop.hbase.HColumnDescriptor;
39  import org.apache.hadoop.hbase.HConstants;
40  import org.apache.hadoop.hbase.HTableDescriptor;
41  import org.apache.hadoop.hbase.TableName;
42  import org.apache.hadoop.hbase.backup.BackupInfo;
43  import org.apache.hadoop.hbase.backup.BackupInfo.BackupState;
44  import org.apache.hadoop.hbase.backup.util.BackupClientUtil;
45  import org.apache.hadoop.hbase.classification.InterfaceAudience;
46  import org.apache.hadoop.hbase.classification.InterfaceStability;
47  import org.apache.hadoop.hbase.client.Connection;
48  import org.apache.hadoop.hbase.client.Delete;
49  import org.apache.hadoop.hbase.client.Get;
50  import org.apache.hadoop.hbase.client.Put;
51  import org.apache.hadoop.hbase.client.Result;
52  import org.apache.hadoop.hbase.client.ResultScanner;
53  import org.apache.hadoop.hbase.client.Scan;
54  import org.apache.hadoop.hbase.client.Table;
55  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
56  import org.apache.hadoop.hbase.protobuf.generated.BackupProtos;
57  
58  /**
59   * This class provides 'hbase:backup' table API
60   */
61  @InterfaceAudience.Private
62  @InterfaceStability.Evolving
63  public final class BackupSystemTable implements Closeable {
64    
65    static class WALItem {
66      String backupId;
67      String walFile;
68      String backupRoot;
69      
70      WALItem(String backupId, String walFile, String backupRoot)
71      {
72        this.backupId = backupId;
73        this.walFile = walFile;
74        this.backupRoot = backupRoot;
75      }
76  
77      public String getBackupId() {
78        return backupId;
79      }
80  
81      public String getWalFile() {
82        return walFile;
83      }
84  
85      public String getBackupRoot() {
86        return backupRoot;
87      }
88      
89      public String toString() {
90        return "/"+ backupRoot + "/"+backupId + "/" + walFile;
91      }
92      
93    }
94    
95    private static final Log LOG = LogFactory.getLog(BackupSystemTable.class);
96    private final static TableName tableName = TableName.BACKUP_TABLE_NAME;  
97    // Stores backup sessions (contexts)
98    final static byte[] SESSIONS_FAMILY = "session".getBytes();
99    // Stores other meta 
100   final static byte[] META_FAMILY = "meta".getBytes();
101   // Connection to HBase cluster, shared
102   // among all instances
103   private final Connection connection;
104     
105   public BackupSystemTable(Connection conn) throws IOException {
106     this.connection = conn;
107   }
108 
109  
110   public void close() {
111      // do nothing 
112   }
113 
114   /**
115    * Updates status (state) of a backup session in hbase:backup table
116    * @param context context
117    * @throws IOException exception
118    */
119   public void updateBackupInfo(BackupInfo context) throws IOException {
120 
121     if (LOG.isDebugEnabled()) {
122       LOG.debug("update backup status in hbase:backup for: " + context.getBackupId()
123         + " set status=" + context.getState());
124     }
125     try (Table table = connection.getTable(tableName)) {
126       Put put = BackupSystemTableHelper.createPutForBackupContext(context);
127       table.put(put);
128     }
129   }
130 
131   /**
132    * Deletes backup status from hbase:backup table
133    * @param backupId backup id
134    * @return true, if operation succeeded, false - otherwise 
135    * @throws IOException exception
136    */
137 
138   public void deleteBackupInfo(String backupId) throws IOException {
139 
140     if (LOG.isDebugEnabled()) {
141       LOG.debug("delete backup status in hbase:backup for " + backupId);
142     }
143     try (Table table = connection.getTable(tableName)) {
144       Delete del = BackupSystemTableHelper.createDeleteForBackupInfo(backupId);
145       table.delete(del);
146     }
147   }
148 
149   /**
150    * Reads backup status object (instance of BackupContext) from hbase:backup table
151    * @param backupId - backupId
152    * @return Current status of backup session or null
153    */
154 
155   public BackupInfo readBackupInfo(String backupId) throws IOException {
156     if (LOG.isDebugEnabled()) {
157       LOG.debug("read backup status from hbase:backup for: " + backupId);
158     }
159 
160     try (Table table = connection.getTable(tableName)) {
161       Get get = BackupSystemTableHelper.createGetForBackupContext(backupId);
162       Result res = table.get(get);
163       if(res.isEmpty()){
164         return null;
165       }
166       return BackupSystemTableHelper.resultToBackupInfo(res);
167     }
168   }
169 
170   /**
171    * Read the last backup start code (timestamp) of last successful backup. Will return null if
172    * there is no start code stored on hbase or the value is of length 0. These two cases indicate
173    * there is no successful backup completed so far.
174    * @param backupRoot root directory path to backup 
175    * @return the timestamp of last successful backup
176    * @throws IOException exception
177    */
178   public String readBackupStartCode(String backupRoot) throws IOException {
179     if (LOG.isDebugEnabled()) {
180       LOG.debug("read backup start code from hbase:backup");
181     }
182     try (Table table = connection.getTable(tableName)) {
183       Get get = BackupSystemTableHelper.createGetForStartCode(backupRoot);
184       Result res = table.get(get);
185       if (res.isEmpty()) {
186         return null;
187       }
188       Cell cell = res.listCells().get(0);
189       byte[] val = CellUtil.cloneValue(cell);
190       if (val.length == 0){
191         return null;
192       }
193       return new String(val);
194     }
195   }
196 
197   /**
198    * Write the start code (timestamp) to hbase:backup. If passed in null, then write 0 byte.
199    * @param startCode start code
200    * @param backupRoot root directory path to backup 
201    * @throws IOException exception
202    */
203   public void writeBackupStartCode(Long startCode, String backupRoot) throws IOException {
204     if (LOG.isDebugEnabled()) {
205       LOG.debug("write backup start code to hbase:backup " + startCode);
206     }
207     try (Table table = connection.getTable(tableName)) {
208       Put put = BackupSystemTableHelper.createPutForStartCode(startCode.toString(), backupRoot);
209       table.put(put);
210     }
211   }
212 
213   /**
214    * Get the Region Servers log information after the last log roll from hbase:backup.
215    * @param backupRoot root directory path to backup 
216    * @return RS log info
217    * @throws IOException exception
218    */
219   public HashMap<String, Long> readRegionServerLastLogRollResult(String backupRoot)
220       throws IOException {
221     if (LOG.isDebugEnabled()) {
222       LOG.debug("read region server last roll log result to hbase:backup");
223     }
224 
225     Scan scan = BackupSystemTableHelper.createScanForReadRegionServerLastLogRollResult(backupRoot);
226 
227     try (Table table = connection.getTable(tableName);
228         ResultScanner scanner = table.getScanner(scan)) {
229       Result res = null;
230       HashMap<String, Long> rsTimestampMap = new HashMap<String, Long>();
231       while ((res = scanner.next()) != null) {
232         res.advance();
233         Cell cell = res.current();
234         byte[] row = CellUtil.cloneRow(cell);
235         String server =
236             BackupSystemTableHelper.getServerNameForReadRegionServerLastLogRollResult(row);
237         byte[] data = CellUtil.cloneValue(cell);
238         rsTimestampMap.put(server, Long.parseLong(new String(data)));
239       }
240       return rsTimestampMap;
241     }
242   }
243 
244   /**
245    * Writes Region Server last roll log result (timestamp) to hbase:backup table
246    * @param server - Region Server name
247    * @param timestamp - last log timestamp
248    * @param backupRoot root directory path to backup 
249    * @throws IOException exception
250    */
251   public void writeRegionServerLastLogRollResult(String server, Long ts, String backupRoot)
252       throws IOException {
253     if (LOG.isDebugEnabled()) {
254       LOG.debug("write region server last roll log result to hbase:backup");
255     }
256     try (Table table = connection.getTable(tableName)) {
257       Put put =
258           BackupSystemTableHelper.createPutForRegionServerLastLogRollResult(server,ts,backupRoot);
259       table.put(put);
260     }
261   }
262 
263   /**
264    * Get all completed backup information (in desc order by time)
265    * @param onlyCompeleted, true, if only successfully completed sessions
266    * @return history info of BackupCompleteData
267    * @throws IOException exception
268    */
269   public ArrayList<BackupInfo> getBackupHistory(boolean onlyCompleted) throws IOException {
270     if (LOG.isDebugEnabled()) {
271       LOG.debug("get backup history from hbase:backup");
272     }
273     ArrayList<BackupInfo> list ;
274     BackupState state = onlyCompleted? BackupState.COMPLETE: BackupState.ANY;
275     list = getBackupContexts(state);
276     return BackupClientUtil.sortHistoryListDesc(list);    
277   }
278 
279   public ArrayList<BackupInfo> getBackupHistory() throws IOException {
280     return getBackupHistory(false);
281   }
282   
283   /**
284    * Get all backup session with a given status (in desc order by time)
285    * @param status status
286    * @return history info of backup contexts
287    * @throws IOException exception
288    */
289   public ArrayList<BackupInfo> getBackupContexts(BackupState status) throws IOException {
290     if (LOG.isDebugEnabled()) {
291       LOG.debug("get backup contexts from hbase:backup");
292     }
293 
294     Scan scan = BackupSystemTableHelper.createScanForBackupHistory();
295     ArrayList<BackupInfo> list = new ArrayList<BackupInfo>();
296 
297     try (Table table = connection.getTable(tableName);
298         ResultScanner scanner = table.getScanner(scan)) {
299       Result res = null;
300       while ((res = scanner.next()) != null) {
301         res.advance();
302         BackupInfo context = BackupSystemTableHelper.cellToBackupInfo(res.current());
303         if (status != BackupState.ANY && context.getState() != status){
304           continue;
305         }
306         list.add(context);
307       }
308       return list;
309     }
310   }
311 
312   /**
313    * Write the current timestamps for each regionserver to hbase:backup 
314    * after a successful full or incremental backup. The saved timestamp is of the last
315    *  log file that was backed up already.
316    * @param tables tables
317    * @param newTimestamps timestamps
318    * @param backupRoot root directory path to backup 
319    * @throws IOException exception
320    */
321   public void writeRegionServerLogTimestamp(Set<TableName> tables,
322       HashMap<String, Long> newTimestamps, String backupRoot) throws IOException {
323     if (LOG.isDebugEnabled()) {
324       LOG.debug("write RS log time stamps to hbase:backup for tables ["+ 
325           StringUtils.join(tables, ",")+"]");
326     }
327     List<Put> puts = new ArrayList<Put>();
328     for (TableName table : tables) {
329       byte[] smapData = toTableServerTimestampProto(table, newTimestamps).toByteArray();
330       Put put = 
331           BackupSystemTableHelper.createPutForWriteRegionServerLogTimestamp(table, 
332             smapData, backupRoot);
333       puts.add(put);
334     }
335     try (Table table = connection.getTable(tableName)) {
336       table.put(puts);
337     }
338   }
339 
340   /**
341    * Read the timestamp for each region server log after the last successful backup. Each table has
342    * its own set of the timestamps. The info is stored for each table as a concatenated string of
343    * rs->timestapmp
344    * @param backupRoot root directory path to backup 
345    * @return the timestamp for each region server. key: tableName value:
346    *         RegionServer,PreviousTimeStamp
347    * @throws IOException exception
348    */
349   public HashMap<TableName, HashMap<String, Long>> readLogTimestampMap(String backupRoot)
350       throws IOException {
351     if (LOG.isDebugEnabled()) {
352       LOG.debug("read RS log ts from hbase:backup for root="+ backupRoot);
353     }
354 
355     HashMap<TableName, HashMap<String, Long>> tableTimestampMap =
356         new HashMap<TableName, HashMap<String, Long>>();
357 
358     Scan scan = BackupSystemTableHelper.createScanForReadLogTimestampMap(backupRoot);
359     try (Table table = connection.getTable(tableName);
360         ResultScanner scanner = table.getScanner(scan)) {
361       Result res = null;
362       while ((res = scanner.next()) != null) {
363         res.advance();
364         Cell cell = res.current();
365         byte[] row = CellUtil.cloneRow(cell);
366         String tabName = BackupSystemTableHelper.getTableNameForReadLogTimestampMap(row);
367         TableName tn = TableName.valueOf(tabName);
368         byte[] data = CellUtil.cloneValue(cell);
369         if (data == null) {
370           throw new IOException("Data of last backup data from hbase:backup "
371               + "is empty. Create a backup first.");
372         }
373         if (data != null && data.length > 0) {
374           HashMap<String, Long> lastBackup =
375               fromTableServerTimestampProto(BackupProtos.TableServerTimestamp.parseFrom(data));
376           tableTimestampMap.put(tn, lastBackup);
377         }
378       }
379       return tableTimestampMap;
380     }
381   }
382 
383   private BackupProtos.TableServerTimestamp toTableServerTimestampProto(TableName table,
384       Map<String, Long> map) {
385     BackupProtos.TableServerTimestamp.Builder tstBuilder =
386         BackupProtos.TableServerTimestamp.newBuilder();
387     tstBuilder.setTable(ProtobufUtil.toProtoTableName(table));
388 
389     for(Entry<String, Long> entry: map.entrySet()) {
390       BackupProtos.ServerTimestamp.Builder builder = BackupProtos.ServerTimestamp.newBuilder();
391       builder.setServer(entry.getKey());
392       builder.setTimestamp(entry.getValue());
393       tstBuilder.addServerTimestamp(builder.build());
394     }
395 
396     return tstBuilder.build();
397   }
398 
399   private HashMap<String, Long> fromTableServerTimestampProto(
400       BackupProtos.TableServerTimestamp proto) {
401     HashMap<String, Long> map = new HashMap<String, Long> ();
402     List<BackupProtos.ServerTimestamp> list = proto.getServerTimestampList();
403     for(BackupProtos.ServerTimestamp st: list) {
404       map.put(st.getServer(), st.getTimestamp());
405     }
406     return map;
407   }
408 
409   /**
410    * Return the current tables covered by incremental backup.
411    * @param backupRoot root directory path to backup 
412    * @return set of tableNames
413    * @throws IOException exception
414    */
415   public  Set<TableName> getIncrementalBackupTableSet(String backupRoot)
416       throws IOException {
417     if (LOG.isDebugEnabled()) {
418       LOG.debug("get incr backup table set from hbase:backup");
419     }
420     TreeSet<TableName> set = new TreeSet<>();
421 
422     try (Table table = connection.getTable(tableName)) {
423       Get get = BackupSystemTableHelper.createGetForIncrBackupTableSet(backupRoot);
424       Result res = table.get(get);
425       if (res.isEmpty()) {
426         return set;
427       }
428       List<Cell> cells = res.listCells();
429       for (Cell cell : cells) {
430         // qualifier = table name - we use table names as qualifiers
431         set.add(TableName.valueOf(CellUtil.cloneQualifier(cell)));
432       }
433       return set;
434     }
435   }
436 
437   /**
438    * Add tables to global incremental backup set
439    * @param tables - set of tables
440    * @param backupRoot root directory path to backup 
441    * @throws IOException exception
442    */
443   public void addIncrementalBackupTableSet(Set<TableName> tables, String backupRoot) throws IOException {
444     if (LOG.isDebugEnabled()) {
445       LOG.debug("Add incremental backup table set to hbase:backup. ROOT="+backupRoot +
446         " tables ["+ StringUtils.join(tables, " ")+"]");
447       for (TableName table : tables) {
448         LOG.debug(table);
449       }
450     }
451     try (Table table = connection.getTable(tableName)) {
452       Put put = BackupSystemTableHelper.createPutForIncrBackupTableSet(tables, backupRoot);
453       table.put(put);
454     }
455   }
456 
457   /**
458    * Register WAL files as eligible for deletion
459    * @param files files
460    * @param backupId backup id
461    * @param backupRoot root directory path to backup 
462    * @throws IOException exception
463    */
464   public void addWALFiles(List<String> files, String backupId, 
465       String backupRoot) throws IOException {
466     if (LOG.isDebugEnabled()) {
467       LOG.debug("add WAL files to hbase:backup: "+backupId +" "+backupRoot+" files ["+
468      StringUtils.join(files, ",")+"]");
469       for(String f: files){
470         LOG.debug("add :"+f);
471       }
472     }
473     try (Table table = connection.getTable(tableName)) {
474       List<Put> puts = 
475           BackupSystemTableHelper.createPutsForAddWALFiles(files, backupId, backupRoot);
476       table.put(puts);
477     }
478   }
479 
480   /**
481    * Register WAL files as eligible for deletion
482    * @param backupRoot root directory path to backup 
483    * @throws IOException exception
484    */
485   public Iterator<WALItem> getWALFilesIterator(String backupRoot) throws IOException {
486     if (LOG.isDebugEnabled()) {
487       LOG.debug("get WAL files from hbase:backup");
488     }
489     final Table table = connection.getTable(tableName);
490     Scan scan = BackupSystemTableHelper.createScanForGetWALs(backupRoot);
491     final ResultScanner scanner = table.getScanner(scan);
492     final Iterator<Result> it = scanner.iterator();
493     return new Iterator<WALItem>() {
494 
495       @Override
496       public boolean hasNext() {
497         boolean next = it.hasNext();
498         if (!next) {
499           // close all
500           try {
501             scanner.close();
502             table.close();
503           } catch (IOException e) {
504             LOG.error("Close WAL Iterator", e);
505           }
506         }
507         return next;
508       }
509 
510       @Override
511       public WALItem next() {
512         Result next = it.next();
513         List<Cell> cells = next.listCells();
514         byte[] buf = cells.get(0).getValueArray();
515         int len = cells.get(0).getValueLength();
516         int offset = cells.get(0).getValueOffset();
517         String backupId = new String(buf, offset, len);
518         buf = cells.get(1).getValueArray();
519         len = cells.get(1).getValueLength();
520         offset = cells.get(1).getValueOffset();
521         String walFile = new String(buf, offset, len);
522         buf = cells.get(2).getValueArray();
523         len = cells.get(2).getValueLength();
524         offset = cells.get(2).getValueOffset();
525         String backupRoot = new String(buf, offset, len);    
526         return new WALItem(backupId, walFile, backupRoot);
527       }
528 
529       @Override
530       public void remove() {
531         // not implemented
532         throw new RuntimeException("remove is not supported");
533       }
534     };
535 
536   }
537 
538   /**
539    * Check if WAL file is eligible for deletion
540    * Future: to support all backup destinations
541    * @param file file
542    * @return true, if - yes.
543    * @throws IOException exception
544    */
545   public boolean isWALFileDeletable(String file) throws IOException {
546     if (LOG.isDebugEnabled()) {
547       LOG.debug("Check if WAL file has been already backed up in hbase:backup "+ file);
548     }
549     try (Table table = connection.getTable(tableName)) {
550       Get get = BackupSystemTableHelper.createGetForCheckWALFile(file);
551       Result res = table.get(get);
552       if (res.isEmpty()){
553         return false;
554       }
555       return true;
556     }
557   }
558 
559   /**
560    * Checks if we have at least one backup session in hbase:backup This API is used by
561    * BackupLogCleaner
562    * @return true, if - at least one session exists in hbase:backup table
563    * @throws IOException exception
564    */
565   public boolean hasBackupSessions() throws IOException {
566     if (LOG.isDebugEnabled()) {
567       LOG.debug("Has backup sessions from hbase:backup");
568     }
569     boolean result = false;
570     Scan scan = BackupSystemTableHelper.createScanForBackupHistory();
571     scan.setCaching(1);
572     try (Table table = connection.getTable(tableName);
573         ResultScanner scanner = table.getScanner(scan)) {
574       if (scanner.next() != null) {
575         result = true;
576       }
577       return result;
578     }
579   }
580   
581   /**
582    * BACKUP SETS
583    */
584   
585   /**
586    * Get backup set list
587    * @return backup set list
588    * @throws IOException
589    */
590   public List<String> listBackupSets() throws IOException {
591     if (LOG.isDebugEnabled()) {
592       LOG.debug(" Backup set list");
593     }
594     List<String> list = new ArrayList<String>();
595     Table table = null;
596     ResultScanner scanner = null;
597     try {
598       table = connection.getTable(tableName);
599       Scan scan = BackupSystemTableHelper.createScanForBackupSetList();
600       scan.setMaxVersions(1);
601       scanner = table.getScanner(scan);
602       Result res = null;
603      while ((res = scanner.next()) != null) {
604        res.advance();
605        list.add(BackupSystemTableHelper.cellKeyToBackupSetName(res.current()));
606      }
607      return list;
608    } finally {
609      if(scanner != null) {
610        scanner.close();
611      }
612      if (table != null) {
613        table.close();
614      }
615    }
616  }
617  
618  /**
619   * Get backup set description (list of tables)
620   * @param name - set's name
621   * @return list of tables in a backup set 
622   * @throws IOException
623   */
624  public List<TableName> describeBackupSet(String name) throws IOException {
625    if (LOG.isDebugEnabled()) {
626      LOG.debug(" Backup set describe: "+name);
627    }
628    Table table = null;
629    try {
630      table = connection.getTable(tableName);
631      Get get = BackupSystemTableHelper.createGetForBackupSet(name);
632      Result res = table.get(get);
633      if(res.isEmpty()) return null;
634      res.advance();
635      String[] tables = 
636          BackupSystemTableHelper.cellValueToBackupSet(res.current());
637      return toList(tables);
638    } finally {
639      if (table != null) {
640        table.close();
641      }
642    }
643  }
644  
645  private List<TableName> toList(String[] tables)
646  {
647    List<TableName> list = new ArrayList<TableName>(tables.length);
648    for(String name: tables) {
649      list.add(TableName.valueOf(name));
650    }
651    return list;
652  }
653  
654  /**
655   * Add backup set (list of tables)
656   * @param name - set name
657   * @param tables - list of tables, comma-separated
658   * @throws IOException
659   */
660  public void addToBackupSet(String name, String[] newTables) throws IOException {
661    if (LOG.isDebugEnabled()) {
662      LOG.debug("Backup set add: "+name+" tables ["+ StringUtils.join(newTables, " ")+"]");
663    }
664    Table table = null;
665    String[] union = null;
666    try {
667      table = connection.getTable(tableName);
668      Get get = BackupSystemTableHelper.createGetForBackupSet(name);
669      Result res = table.get(get);
670      if(res.isEmpty()) {
671        union = newTables;
672      } else {
673        res.advance();
674        String[] tables = 
675          BackupSystemTableHelper.cellValueToBackupSet(res.current());
676        union = merge(tables, newTables);  
677      }
678      Put put = BackupSystemTableHelper.createPutForBackupSet(name, union);
679      table.put(put);
680    } finally {
681      if (table != null) {
682        table.close();
683      }
684    }
685  }
686  
687  private String[] merge(String[] tables, String[] newTables) {
688    List<String> list = new ArrayList<String>();
689    // Add all from tables
690    for(String t: tables){
691      list.add(t);
692    }
693    for(String nt: newTables){
694      if(list.contains(nt)) continue;
695      list.add(nt);
696    }
697    String[] arr = new String[list.size()];
698    list.toArray(arr);
699    return arr;
700  }
701 
702  /**
703   * Remove tables from backup set (list of tables)
704   * @param name - set name
705   * @param tables - list of tables, comma-separated
706   * @throws IOException
707   */
708   public void removeFromBackupSet(String name, String[] toRemove) throws IOException {
709     if (LOG.isDebugEnabled()) {
710       LOG.debug(" Backup set remove from : " + name+" tables ["+
711      StringUtils.join(toRemove, " ")+"]");
712     }
713     Table table = null;
714     String[] disjoint = null;
715     try {
716       table = connection.getTable(tableName);
717       Get get = BackupSystemTableHelper.createGetForBackupSet(name);
718       Result res = table.get(get);
719       if (res.isEmpty()) {
720         LOG.warn("Backup set '"+ name+"' not found.");
721         return;
722       } else {
723         res.advance();
724         String[] tables = BackupSystemTableHelper.cellValueToBackupSet(res.current());
725         disjoint = disjoin(tables, toRemove);
726       }
727       if (disjoint.length > 0) {
728         Put put = BackupSystemTableHelper.createPutForBackupSet(name, disjoint);
729         table.put(put);
730       } else {
731         // Delete
732         //describeBackupSet(name);
733         LOG.warn("Backup set '"+ name+"' does not contain tables ["+
734         StringUtils.join(toRemove, " ")+"]");
735       }
736     } finally {
737       if (table != null) {
738         table.close();
739       }
740     }
741   }
742 
743   private String[] disjoin(String[] tables, String[] toRemove) {
744     List<String> list = new ArrayList<String>();
745     // Add all from tables
746     for (String t : tables) {
747       list.add(t);
748     }
749     for (String nt : toRemove) {
750       if (list.contains(nt)) {
751         list.remove(nt);
752       }
753     }
754     String[] arr = new String[list.size()];
755     list.toArray(arr);
756     return arr;
757   }
758 
759  /**
760   * Delete backup set 
761   * @param name set's name
762   * @throws IOException
763   */
764   public void deleteBackupSet(String name) throws IOException {
765     if (LOG.isDebugEnabled()) {
766       LOG.debug(" Backup set delete: " + name);
767     }
768     Table table = null;
769     try {
770       table = connection.getTable(tableName);
771       Delete del = BackupSystemTableHelper.createDeleteForBackupSet(name);
772       table.delete(del);
773     } finally {
774       if (table != null) {
775         table.close();
776       }
777     }
778   }
779 
780   /**
781    * Get backup system table descriptor
782    * @return descriptor
783    */
784   public static HTableDescriptor getSystemTableDescriptor() {
785     HTableDescriptor tableDesc = new HTableDescriptor(tableName);
786     HColumnDescriptor colSessionsDesc = new HColumnDescriptor(SESSIONS_FAMILY);
787     colSessionsDesc.setMaxVersions(1);
788     // Time to keep backup sessions (secs)
789     Configuration config = HBaseConfiguration.create();
790     int ttl =
791         config.getInt(HConstants.BACKUP_SYSTEM_TTL_KEY, HConstants.BACKUP_SYSTEM_TTL_DEFAULT);
792     colSessionsDesc.setTimeToLive(ttl);
793     tableDesc.addFamily(colSessionsDesc);
794     HColumnDescriptor colMetaDesc = new HColumnDescriptor(META_FAMILY);
795     //colDesc.setMaxVersions(1);
796     tableDesc.addFamily(colMetaDesc);
797     return tableDesc;
798   }
799 
800   public static String getTableNameAsString() {
801     return tableName.getNameAsString();
802   }
803   
804   public static TableName getTableName() {
805     return tableName;
806   }
807 }