View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.client.replication;
20  
21  import com.google.common.collect.Lists;
22  import java.io.Closeable;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Map.Entry;
32  import java.util.Set;
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.hbase.*;
38  import org.apache.hadoop.hbase.classification.InterfaceAudience;
39  import org.apache.hadoop.hbase.classification.InterfaceStability;
40  import org.apache.hadoop.hbase.client.Admin;
41  import org.apache.hadoop.hbase.client.Connection;
42  import org.apache.hadoop.hbase.client.ConnectionFactory;
43  import org.apache.hadoop.hbase.client.RegionLocator;
44  import org.apache.hadoop.hbase.replication.ReplicationException;
45  import org.apache.hadoop.hbase.replication.ReplicationFactory;
46  import org.apache.hadoop.hbase.replication.ReplicationPeer;
47  import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
48  import org.apache.hadoop.hbase.replication.ReplicationPeerZKImpl;
49  import org.apache.hadoop.hbase.replication.ReplicationPeers;
50  import org.apache.hadoop.hbase.replication.ReplicationQueuesClient;
51  import org.apache.hadoop.hbase.replication.ReplicationSerDeHelper;
52  import org.apache.hadoop.hbase.util.Pair;
53  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
54  
55  /**
56   * <p>
57   * This class provides the administrative interface to HBase cluster
58   * replication. In order to use it, the cluster and the client using
59   * ReplicationAdmin must be configured with <code>hbase.replication</code>
60   * set to true.
61   * </p>
62   * <p>
63   * Adding a new peer results in creating new outbound connections from every
64   * region server to a subset of region servers on the slave cluster. Each
65   * new stream of replication will start replicating from the beginning of the
66   * current WAL, meaning that edits from that past will be replicated.
67   * </p>
68   * <p>
69   * Removing a peer is a destructive and irreversible operation that stops
70   * all the replication streams for the given cluster and deletes the metadata
71   * used to keep track of the replication state.
72   * </p>
73   * <p>
74   * To see which commands are available in the shell, type
75   * <code>replication</code>.
76   * </p>
77   */
78  @InterfaceAudience.Public
79  @InterfaceStability.Evolving
80  public class ReplicationAdmin implements Closeable {
81    private static final Log LOG = LogFactory.getLog(ReplicationAdmin.class);
82  
83    public static final String TNAME = "tableName";
84    public static final String CFNAME = "columnFamlyName";
85    public static final String REPLICATION_WALENTRYFILTER_CONFIG_KEY
86        = "hbase.replication.source.custom.walentryfilters";
87    // only Global for now, can add other type
88    // such as, 1) no global replication, or 2) the table is replicated to this cluster, etc.
89    public static final String REPLICATIONTYPE = "replicationType";
90    public static final String REPLICATIONGLOBAL = Integer
91        .toString(HConstants.REPLICATION_SCOPE_GLOBAL);
92  
93    private final Connection connection;
94    // TODO: replication should be managed by master. All the classes except ReplicationAdmin should
95    // be moved to hbase-server. Resolve it in HBASE-11392.
96    private final ReplicationQueuesClient replicationQueuesClient;
97    private final ReplicationPeers replicationPeers;
98    /**
99     * A watcher used by replicationPeers and replicationQueuesClient. Keep reference so can dispose
100    * on {@link #close()}.
101    */
102   private final ZooKeeperWatcher zkw;
103 
104   /**
105    * Constructor that creates a connection to the local ZooKeeper ensemble.
106    * @param conf Configuration to use
107    * @throws IOException if an internal replication error occurs
108    * @throws RuntimeException if replication isn't enabled.
109    */
110   public ReplicationAdmin(Configuration conf) throws IOException {
111     if (!conf.getBoolean(HConstants.REPLICATION_ENABLE_KEY,
112         HConstants.REPLICATION_ENABLE_DEFAULT)) {
113       throw new RuntimeException("hbase.replication isn't true, please " +
114           "enable it in order to use replication");
115     }
116     this.connection = ConnectionFactory.createConnection(conf);
117     try {
118       zkw = createZooKeeperWatcher();
119       try {
120         this.replicationQueuesClient =
121             ReplicationFactory.getReplicationQueuesClient(zkw, conf, this.connection);
122         this.replicationQueuesClient.init();
123         this.replicationPeers = ReplicationFactory.getReplicationPeers(zkw, conf,
124           this.replicationQueuesClient, this.connection);
125         this.replicationPeers.init();
126       } catch (Exception exception) {
127         if (zkw != null) {
128           zkw.close();
129         }
130         throw exception;
131       }
132     } catch (Exception exception) {
133       if (connection != null) {
134         connection.close();
135       }
136       if (exception instanceof IOException) {
137         throw (IOException) exception;
138       } else if (exception instanceof RuntimeException) {
139         throw (RuntimeException) exception;
140       } else {
141         throw new IOException("Error initializing the replication admin client.", exception);
142       }
143     }
144   }
145 
146   private ZooKeeperWatcher createZooKeeperWatcher() throws IOException {
147     // This Abortable doesn't 'abort'... it just logs.
148     return new ZooKeeperWatcher(connection.getConfiguration(), "ReplicationAdmin", new Abortable() {
149       @Override
150       public void abort(String why, Throwable e) {
151         LOG.error(why, e);
152         // We used to call system.exit here but this script can be embedded by other programs that
153         // want to do replication stuff... so inappropriate calling System.exit. Just log for now.
154       }
155 
156       @Override
157       public boolean isAborted() {
158         return false;
159       }
160     });
161   }
162 
163   /**
164    * Add a new peer cluster to replicate to.
165    * @param id a short name that identifies the cluster
166    * @param clusterKey the concatenation of the slave cluster's
167    * <code>hbase.zookeeper.quorum:hbase.zookeeper.property.clientPort:zookeeper.znode.parent</code>
168    * @throws IllegalStateException if there's already one slave since
169    * multi-slave isn't supported yet.
170    * @deprecated Use addPeer(String, ReplicationPeerConfig, Map) instead.
171    */
172   @Deprecated
173   public void addPeer(String id, String clusterKey) throws ReplicationException {
174     this.addPeer(id, new ReplicationPeerConfig().setClusterKey(clusterKey), null);
175   }
176 
177   @Deprecated
178   public void addPeer(String id, String clusterKey, String tableCFs)
179     throws ReplicationException {
180     this.addPeer(id, new ReplicationPeerConfig().setClusterKey(clusterKey),
181       parseTableCFsFromConfig(tableCFs));
182   }
183 
184   /**
185    * Add a new remote slave cluster for replication.
186    * @param id a short name that identifies the cluster
187    * @param peerConfig configuration for the replication slave cluster
188    * @param tableCfs the table and column-family list which will be replicated for this peer.
189    * A map from tableName to column family names. An empty collection can be passed
190    * to indicate replicating all column families. Pass null for replicating all table and column
191    * families
192    */
193   public void addPeer(String id, ReplicationPeerConfig peerConfig,
194       Map<TableName, ? extends Collection<String>> tableCfs) throws ReplicationException {
195     if (tableCfs != null) {
196       peerConfig.setTableCFsMap(tableCfs);
197     }
198     checkConfiguredWALEntryFilters(peerConfig);
199     this.replicationPeers.addPeer(id, peerConfig);
200   }
201 
202   /**
203    * Add a new remote slave cluster for replication.
204    * @param id a short name that identifies the cluster
205    * @param peerConfig configuration for the replication slave cluster
206    */
207   public void addPeer(String id, ReplicationPeerConfig peerConfig) throws ReplicationException {
208     checkConfiguredWALEntryFilters(peerConfig);
209     this.replicationPeers.addPeer(id, peerConfig);
210   }
211 
212   public void updatePeerConfig(String id, ReplicationPeerConfig peerConfig)
213     throws ReplicationException {
214     checkConfiguredWALEntryFilters(peerConfig);
215     this.replicationPeers.updatePeerConfig(id, peerConfig);
216   }
217 
218   public static Map<TableName, List<String>> parseTableCFsFromConfig(String tableCFsConfig) {
219     return ReplicationSerDeHelper.parseTableCFsFromConfig(tableCFsConfig);
220   }
221 
222   @InterfaceAudience.Private
223   static String getTableCfsStr(Map<TableName, ? extends Collection<String>> tableCfs) {
224     String tableCfsStr = null;
225     if (tableCfs != null) {
226       // Format: table1:cf1,cf2;table2:cfA,cfB;table3
227       StringBuilder builder = new StringBuilder();
228       for (Entry<TableName, ? extends Collection<String>> entry : tableCfs.entrySet()) {
229         if (builder.length() > 0) {
230           builder.append(";");
231         }
232         builder.append(entry.getKey());
233         if (entry.getValue() != null && !entry.getValue().isEmpty()) {
234           builder.append(":");
235           builder.append(StringUtils.join(entry.getValue(), ","));
236         }
237       }
238       tableCfsStr = builder.toString();
239     }
240     return tableCfsStr;
241   }
242 
243   /**
244    * Removes a peer cluster and stops the replication to it.
245    * @param id a short name that identifies the cluster
246    */
247   public void removePeer(String id) throws ReplicationException {
248     this.replicationPeers.removePeer(id);
249   }
250 
251   /**
252    * Restart the replication stream to the specified peer.
253    * @param id a short name that identifies the cluster
254    */
255   public void enablePeer(String id) throws ReplicationException {
256     this.replicationPeers.enablePeer(id);
257   }
258 
259   /**
260    * Stop the replication stream to the specified peer.
261    * @param id a short name that identifies the cluster
262    */
263   public void disablePeer(String id) throws ReplicationException {
264     this.replicationPeers.disablePeer(id);
265   }
266 
267   /**
268    * Get the number of slave clusters the local cluster has.
269    * @return number of slave clusters
270    */
271   public int getPeersCount() {
272     return this.replicationPeers.getAllPeerIds().size();
273   }
274 
275   /**
276    * Map of this cluster's peers for display.
277    * @return A map of peer ids to peer cluster keys
278    * @deprecated use {@link #listPeerConfigs()}
279    */
280   @Deprecated
281   public Map<String, String> listPeers() {
282     Map<String, ReplicationPeerConfig> peers = this.listPeerConfigs();
283     Map<String, String> ret = new HashMap<String, String>(peers.size());
284 
285     for (Map.Entry<String, ReplicationPeerConfig> entry : peers.entrySet()) {
286       ret.put(entry.getKey(), entry.getValue().getClusterKey());
287     }
288     return ret;
289   }
290 
291   public Map<String, ReplicationPeerConfig> listPeerConfigs() {
292     return this.replicationPeers.getAllPeerConfigs();
293   }
294 
295   public ReplicationPeerConfig getPeerConfig(String id) throws ReplicationException {
296     return this.replicationPeers.getReplicationPeerConfig(id);
297   }
298 
299   /**
300    * Get the replicable table-cf config of the specified peer.
301    * @param id a short name that identifies the cluster
302    */
303   public String getPeerTableCFs(String id) throws ReplicationException {
304     return ReplicationSerDeHelper.convertToString(this.replicationPeers.getPeerTableCFsConfig(id));
305   }
306 
307   /**
308    * Set the replicable table-cf config of the specified peer
309    * @param id a short name that identifies the cluster
310    * @deprecated use {@link #setPeerTableCFs(String, Map)}
311    */
312   @Deprecated
313   public void setPeerTableCFs(String id, String tableCFs) throws ReplicationException {
314     this.setPeerTableCFs(id, parseTableCFsFromConfig(tableCFs));
315   }
316 
317   /**
318    * Append the replicable table-cf config of the specified peer
319    * @param id a short that identifies the cluster
320    * @param tableCfs table-cfs config str
321    */
322   public void appendPeerTableCFs(String id, String tableCfs) throws ReplicationException {
323     appendPeerTableCFs(id, ReplicationSerDeHelper.parseTableCFsFromConfig(tableCfs));
324   }
325 
326   /**
327    * Append the replicable table-cf config of the specified peer
328    * @param id a short that identifies the cluster
329    * @param tableCfs A map from tableName to column family names
330    */
331   public void appendPeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs)
332       throws ReplicationException {
333     if (tableCfs == null) {
334       throw new ReplicationException("tableCfs is null");
335     }
336     Map<TableName, List<String>> preTableCfs = this.replicationPeers.getPeerTableCFsConfig(id);
337     if (preTableCfs == null) {
338       setPeerTableCFs(id, tableCfs);
339       return;
340     }
341 
342     for (Map.Entry<TableName, ? extends Collection<String>> entry : tableCfs.entrySet()) {
343       TableName table = entry.getKey();
344       Collection<String> appendCfs = entry.getValue();
345       if (preTableCfs.containsKey(table)) {
346         List<String> cfs = preTableCfs.get(table);
347         if (cfs == null || appendCfs == null) {
348           preTableCfs.put(table, null);
349         } else {
350           Set<String> cfSet = new HashSet<String>(cfs);
351           cfSet.addAll(appendCfs);
352           preTableCfs.put(table, Lists.newArrayList(cfSet));
353         }
354       } else {
355         if (appendCfs == null || appendCfs.isEmpty()) {
356           preTableCfs.put(table, null);
357         } else {
358           preTableCfs.put(table, Lists.newArrayList(appendCfs));
359         }
360       }
361     }
362     setPeerTableCFs(id, preTableCfs);
363   }
364 
365   /**
366    * Remove some table-cfs from table-cfs config of the specified peer
367    * @param id a short name that identifies the cluster
368    * @param tableCf table-cfs config str
369    * @throws ReplicationException
370    */
371   public void removePeerTableCFs(String id, String tableCf) throws ReplicationException {
372     removePeerTableCFs(id, ReplicationSerDeHelper.parseTableCFsFromConfig(tableCf));
373   }
374 
375   /**
376    * Remove some table-cfs from config of the specified peer
377    * @param id a short name that identifies the cluster
378    * @param tableCfs A map from tableName to column family names
379    * @throws ReplicationException
380    */
381   public void removePeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs)
382       throws ReplicationException {
383     if (tableCfs == null) {
384       throw new ReplicationException("tableCfs is null");
385     }
386 
387     Map<TableName, List<String>> preTableCfs = this.replicationPeers.getPeerTableCFsConfig(id);
388     if (preTableCfs == null) {
389       throw new ReplicationException("Table-Cfs for peer" + id + " is null");
390     }
391     for (Map.Entry<TableName, ? extends Collection<String>> entry: tableCfs.entrySet()) {
392       TableName table = entry.getKey();
393       Collection<String> removeCfs = entry.getValue();
394       if (preTableCfs.containsKey(table)) {
395         List<String> cfs = preTableCfs.get(table);
396         if (cfs == null && removeCfs == null) {
397           preTableCfs.remove(table);
398         } else if (cfs != null && removeCfs != null) {
399           Set<String> cfSet = new HashSet<String>(cfs);
400           cfSet.removeAll(removeCfs);
401           if (cfSet.isEmpty()) {
402             preTableCfs.remove(table);
403           } else {
404             preTableCfs.put(table, Lists.newArrayList(cfSet));
405           }
406         } else if (cfs == null && removeCfs != null) {
407           throw new ReplicationException("Cannot remove cf of table: " + table
408               + " which doesn't specify cfs from table-cfs config in peer: " + id);
409         } else if (cfs != null && removeCfs == null) {
410           throw new ReplicationException("Cannot remove table: " + table
411               + " which has specified cfs from table-cfs config in peer: " + id);
412         }
413       } else {
414         throw new ReplicationException("No table: " + table + " in table-cfs config of peer: " + id);
415       }
416     }
417     setPeerTableCFs(id, preTableCfs);
418   }
419 
420   /**
421    * Set the replicable table-cf config of the specified peer
422    * @param id a short name that identifies the cluster
423    * @param tableCfs the table and column-family list which will be replicated for this peer.
424    * A map from tableName to column family names. An empty collection can be passed
425    * to indicate replicating all column families. Pass null for replicating all table and column
426    * families
427    */
428   public void setPeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs)
429       throws ReplicationException {
430     this.replicationPeers.setPeerTableCFsConfig(id, tableCfs);
431   }
432 
433   /**
434    * Get the state of the specified peer cluster
435    * @param id String format of the Short name that identifies the peer,
436    * an IllegalArgumentException is thrown if it doesn't exist
437    * @return true if replication is enabled to that peer, false if it isn't
438    */
439   public boolean getPeerState(String id) throws ReplicationException {
440     return this.replicationPeers.getStatusOfPeerFromBackingStore(id);
441   }
442 
443   @Override
444   public void close() throws IOException {
445     if (this.zkw != null) {
446       this.zkw.close();
447     }
448     if (this.connection != null) {
449       this.connection.close();
450     }
451   }
452 
453 
454   /**
455    * Find all column families that are replicated from this cluster
456    * @return the full list of the replicated column families of this cluster as:
457    *        tableName, family name, replicationType
458    *
459    * Currently replicationType is Global. In the future, more replication
460    * types may be extended here. For example
461    *  1) the replication may only apply to selected peers instead of all peers
462    *  2) the replicationType may indicate the host Cluster servers as Slave
463    *     for the table:columnFam.
464    */
465   public List<HashMap<String, String>> listReplicated() throws IOException {
466     List<HashMap<String, String>> replicationColFams = new ArrayList<HashMap<String, String>>();
467 
468     Admin admin = connection.getAdmin();
469     HTableDescriptor[] tables;
470     try {
471       tables = admin.listTables();
472     } finally {
473       if (admin!= null) admin.close();
474     }
475 
476     for (HTableDescriptor table : tables) {
477       HColumnDescriptor[] columns = table.getColumnFamilies();
478       String tableName = table.getNameAsString();
479       for (HColumnDescriptor column : columns) {
480         if (column.getScope() != HConstants.REPLICATION_SCOPE_LOCAL) {
481           // At this moment, the columfam is replicated to all peers
482           HashMap<String, String> replicationEntry = new HashMap<String, String>();
483           replicationEntry.put(TNAME, tableName);
484           replicationEntry.put(CFNAME, column.getNameAsString());
485           replicationEntry.put(REPLICATIONTYPE, REPLICATIONGLOBAL);
486           replicationColFams.add(replicationEntry);
487         }
488       }
489     }
490 
491     return replicationColFams;
492   }
493 
494   /**
495    * Enable a table's replication switch.
496    * @param tableName name of the table
497    * @throws IOException if a remote or network exception occurs
498    */
499   public void enableTableRep(final TableName tableName) throws IOException {
500     if (tableName == null) {
501       throw new IllegalArgumentException("Table name cannot be null");
502     }
503     try (Admin admin = this.connection.getAdmin()) {
504       if (!admin.tableExists(tableName)) {
505         throw new TableNotFoundException("Table '" + tableName.getNameAsString()
506             + "' does not exists.");
507       }
508     }
509     byte[][] splits = getTableSplitRowKeys(tableName);
510     checkAndSyncTableDescToPeers(tableName, splits);
511     setTableRep(tableName, true);
512   }
513 
514   /**
515    * Disable a table's replication switch.
516    * @param tableName name of the table
517    * @throws IOException if a remote or network exception occurs
518    */
519   public void disableTableRep(final TableName tableName) throws IOException {
520     if (tableName == null) {
521       throw new IllegalArgumentException("Table name is null");
522     }
523     try (Admin admin = this.connection.getAdmin()) {
524       if (!admin.tableExists(tableName)) {
525         throw new TableNotFoundException("Table '" + tableName.getNamespaceAsString()
526             + "' does not exists.");
527       }
528     }
529     setTableRep(tableName, false);
530   }
531 
532   /**
533    * Get the split row keys of table
534    * @param tableName table name
535    * @return array of split row keys
536    * @throws IOException
537    */
538   private byte[][] getTableSplitRowKeys(TableName tableName) throws IOException {
539     try (RegionLocator locator = connection.getRegionLocator(tableName);) {
540       byte[][] startKeys = locator.getStartKeys();
541       if (startKeys.length == 1) {
542         return null;
543       }
544       byte[][] splits = new byte[startKeys.length - 1][];
545       for (int i = 1; i < startKeys.length; i++) {
546         splits[i - 1] = startKeys[i];
547       }
548       return splits;
549     }
550   }
551 
552   /**
553    * Connect to peer and check the table descriptor on peer:
554    * <ol>
555    * <li>Create the same table on peer when not exist.</li>
556    * <li>Throw an exception if the table already has replication enabled on any of the column
557    * families.</li>
558    * <li>Throw an exception if the table exists on peer cluster but descriptors are not same.</li>
559    * </ol>
560    * @param tableName name of the table to sync to the peer
561    * @param splits table split keys
562    * @throws IOException
563    */
564   private void checkAndSyncTableDescToPeers(final TableName tableName, final byte[][] splits)
565       throws IOException {
566     List<ReplicationPeer> repPeers = listReplicationPeers();
567     if (repPeers == null || repPeers.size() <= 0) {
568       throw new IllegalArgumentException("Found no peer cluster for replication.");
569     }
570     
571     final TableName onlyTableNameQualifier = TableName.valueOf(tableName.getQualifierAsString());
572     
573     for (ReplicationPeer repPeer : repPeers) {
574       Map<TableName, List<String>> tableCFMap = repPeer.getTableCFs();
575       // TODO Currently peer TableCFs will not include namespace so we need to check only for table
576       // name without namespace in it. Need to correct this logic once we fix HBASE-11386.
577       if (tableCFMap != null && !tableCFMap.containsKey(onlyTableNameQualifier)) {
578         continue;
579       }
580 
581       Configuration peerConf = repPeer.getConfiguration();
582       HTableDescriptor localHtd = null;
583       try (Connection conn = ConnectionFactory.createConnection(peerConf);
584           Admin admin = this.connection.getAdmin();
585           Admin repHBaseAdmin = conn.getAdmin()) {
586         localHtd = admin.getTableDescriptor(tableName);
587         HTableDescriptor peerHtd = null;
588         if (!repHBaseAdmin.tableExists(tableName)) {
589           repHBaseAdmin.createTable(localHtd, splits);
590         } else {
591           peerHtd = repHBaseAdmin.getTableDescriptor(tableName);
592           if (peerHtd == null) {
593             throw new IllegalArgumentException("Failed to get table descriptor for table "
594                 + tableName.getNameAsString() + " from peer cluster " + repPeer.getId());
595           }
596           if (!compareForReplication(peerHtd, localHtd)) {
597             throw new IllegalArgumentException("Table " + tableName.getNameAsString()
598                 + " exists in peer cluster " + repPeer.getId()
599                 + ", but the table descriptors are not same when compared with source cluster."
600                 + " Thus can not enable the table's replication switch.");
601           }
602         }
603       }
604     }
605   }
606 
607   @InterfaceAudience.Private
608   public void peerAdded(String id) throws ReplicationException {
609     this.replicationPeers.peerAdded(id);
610   }
611 
612   @InterfaceAudience.Private
613   List<ReplicationPeer> listReplicationPeers() {
614     Map<String, ReplicationPeerConfig> peers = listPeerConfigs();
615     if (peers == null || peers.size() <= 0) {
616       return null;
617     }
618     List<ReplicationPeer> listOfPeers = new ArrayList<ReplicationPeer>(peers.size());
619     for (Entry<String, ReplicationPeerConfig> peerEntry : peers.entrySet()) {
620       String peerId = peerEntry.getKey();
621       try {
622         Pair<ReplicationPeerConfig, Configuration> pair = this.replicationPeers.getPeerConf(peerId);
623         Configuration peerConf = pair.getSecond();
624         ReplicationPeer peer = new ReplicationPeerZKImpl(zkw, pair.getSecond(),
625           peerId, pair.getFirst(), this.connection);
626         listOfPeers.add(peer);
627       } catch (ReplicationException e) {
628         LOG.warn("Failed to get valid replication peers. "
629             + "Error connecting to peer cluster with peerId=" + peerId + ". Error message="
630             + e.getMessage());
631         LOG.debug("Failure details to get valid replication peers.", e);
632         continue;
633       }
634     }
635     return listOfPeers;
636   }
637 
638   /**
639    * Set the table's replication switch if the table's replication switch is already not set.
640    * @param tableName name of the table
641    * @param enableRep is replication switch enable or disable
642    * @throws IOException if a remote or network exception occurs
643    */
644   private void setTableRep(final TableName tableName, boolean enableRep) throws IOException {
645     Admin admin = null;
646     try {
647       admin = this.connection.getAdmin();
648       HTableDescriptor htd = admin.getTableDescriptor(tableName);
649       ReplicationState currentReplicationState = getTableReplicationState(htd);
650       if ((enableRep && currentReplicationState != ReplicationState.ENABLED)
651           || (!enableRep && currentReplicationState != ReplicationState.DISABLED)) {
652         boolean isOnlineSchemaUpdateEnabled =
653             this.connection.getConfiguration()
654                 .getBoolean("hbase.online.schema.update.enable", true);
655         if (!isOnlineSchemaUpdateEnabled) {
656           admin.disableTable(tableName);
657         }
658         for (HColumnDescriptor hcd : htd.getFamilies()) {
659           hcd.setScope(enableRep ? HConstants.REPLICATION_SCOPE_GLOBAL
660               : HConstants.REPLICATION_SCOPE_LOCAL);
661         }
662         admin.modifyTable(tableName, htd);
663         if (!isOnlineSchemaUpdateEnabled) {
664           admin.enableTable(tableName);
665         }
666       }
667     } finally {
668       if (admin != null) {
669         try {
670           admin.close();
671         } catch (IOException e) {
672           LOG.warn("Failed to close admin connection.");
673           LOG.debug("Details on failure to close admin connection.", e);
674         }
675       }
676     }
677   }
678 
679   /**
680    * This enum indicates the current state of the replication for a given table.
681    */
682   private enum ReplicationState {
683     ENABLED, // all column families enabled
684     MIXED, // some column families enabled, some disabled
685     DISABLED // all column families disabled
686   }
687 
688   /**
689    * @param htd table descriptor details for the table to check
690    * @return ReplicationState the current state of the table.
691    */
692   private ReplicationState getTableReplicationState(HTableDescriptor htd) {
693     boolean hasEnabled = false;
694     boolean hasDisabled = false;
695 
696     for (HColumnDescriptor hcd : htd.getFamilies()) {
697       if (hcd.getScope() != HConstants.REPLICATION_SCOPE_GLOBAL) {
698         hasDisabled = true;
699       } else {
700         hasEnabled = true;
701       }
702     }
703 
704     if (hasEnabled && hasDisabled) return ReplicationState.MIXED;
705     if (hasEnabled) return ReplicationState.ENABLED;
706     return ReplicationState.DISABLED;
707   }
708 
709   @SuppressWarnings("unchecked")
710   private void checkConfiguredWALEntryFilters(ReplicationPeerConfig peerConfig)
711     throws ReplicationException {
712     String filterCSV = peerConfig.getConfiguration().
713         get(REPLICATION_WALENTRYFILTER_CONFIG_KEY);
714     if (filterCSV != null && !filterCSV.isEmpty()) {
715       String[] filters = filterCSV.split(",");
716       for (String filter : filters) {
717         try {
718           Class<?> clazz = Class.forName(filter);
719           Object o = clazz.getDeclaredConstructor().newInstance();
720         } catch (Exception e) {
721           throw new ReplicationException("Configured WALEntryFilter " + filter +
722               " could not be created. Failing add/update " + "peer operation.", e);
723         }
724       }
725     }
726   }
727 
728   /**
729    * Copies the REPLICATION_SCOPE of table descriptor passed as an argument. Before copy, the method
730    * ensures that the name of table and column-families should match.
731    * @param peerHtd descriptor on peer cluster
732    * @param localHtd - The HTableDescriptor of table from source cluster.
733    * @return true If the name of table and column families match and REPLICATION_SCOPE copied
734    *         successfully. false If there is any mismatch in the names.
735    */
736   private boolean copyReplicationScope(final HTableDescriptor peerHtd,
737       final HTableDescriptor localHtd) {
738     // Copy the REPLICATION_SCOPE only when table names and the names of
739     // Column-Families are same.
740     int result = peerHtd.getTableName().compareTo(localHtd.getTableName());
741 
742     if (result == 0) {
743       Iterator<HColumnDescriptor> remoteHCDIter = peerHtd.getFamilies().iterator();
744       Iterator<HColumnDescriptor> localHCDIter = localHtd.getFamilies().iterator();
745 
746       while (remoteHCDIter.hasNext() && localHCDIter.hasNext()) {
747         HColumnDescriptor remoteHCD = remoteHCDIter.next();
748         HColumnDescriptor localHCD = localHCDIter.next();
749 
750         String remoteHCDName = remoteHCD.getNameAsString();
751         String localHCDName = localHCD.getNameAsString();
752 
753         if (remoteHCDName.equals(localHCDName)) {
754           remoteHCD.setScope(localHCD.getScope());
755         } else {
756           result = -1;
757           break;
758         }
759       }
760       if (remoteHCDIter.hasNext() || localHCDIter.hasNext()) {
761         return false;
762       }
763     }
764 
765     return result == 0;
766   }
767 
768   /**
769    * Compare the contents of the descriptor with another one passed as a parameter for replication
770    * purpose. The REPLICATION_SCOPE field is ignored during comparison.
771    * @param peerHtd descriptor on peer cluster
772    * @param localHtd descriptor on source cluster which needs to be replicated.
773    * @return true if the contents of the two descriptors match (ignoring just REPLICATION_SCOPE).
774    * @see java.lang.Object#equals(java.lang.Object)
775    */
776   private boolean compareForReplication(HTableDescriptor peerHtd, HTableDescriptor localHtd) {
777     if (peerHtd == null) {
778       return false;
779     }
780     if (peerHtd.equals(localHtd)) {
781       return true;
782     }
783     boolean result = false;
784 
785     // Create a copy of peer HTD as we need to change its replication
786     // scope to match with the local HTD.
787     HTableDescriptor peerHtdCopy = new HTableDescriptor(peerHtd);
788 
789     result = copyReplicationScope(peerHtdCopy, localHtd);
790 
791     // If copy was successful, compare the two tables now.
792     if (result) {
793       result = (peerHtdCopy.compareTo(localHtd) == 0);
794     }
795 
796     return result;
797   }
798 }