1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
88
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
95
96 private final ReplicationQueuesClient replicationQueuesClient;
97 private final ReplicationPeers replicationPeers;
98
99
100
101
102 private final ZooKeeperWatcher zkw;
103
104
105
106
107
108
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
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
153
154 }
155
156 @Override
157 public boolean isAborted() {
158 return false;
159 }
160 });
161 }
162
163
164
165
166
167
168
169
170
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
186
187
188
189
190
191
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
204
205
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
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
245
246
247 public void removePeer(String id) throws ReplicationException {
248 this.replicationPeers.removePeer(id);
249 }
250
251
252
253
254
255 public void enablePeer(String id) throws ReplicationException {
256 this.replicationPeers.enablePeer(id);
257 }
258
259
260
261
262
263 public void disablePeer(String id) throws ReplicationException {
264 this.replicationPeers.disablePeer(id);
265 }
266
267
268
269
270
271 public int getPeersCount() {
272 return this.replicationPeers.getAllPeerIds().size();
273 }
274
275
276
277
278
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
301
302
303 public String getPeerTableCFs(String id) throws ReplicationException {
304 return ReplicationSerDeHelper.convertToString(this.replicationPeers.getPeerTableCFsConfig(id));
305 }
306
307
308
309
310
311
312 @Deprecated
313 public void setPeerTableCFs(String id, String tableCFs) throws ReplicationException {
314 this.setPeerTableCFs(id, parseTableCFsFromConfig(tableCFs));
315 }
316
317
318
319
320
321
322 public void appendPeerTableCFs(String id, String tableCfs) throws ReplicationException {
323 appendPeerTableCFs(id, ReplicationSerDeHelper.parseTableCFsFromConfig(tableCfs));
324 }
325
326
327
328
329
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
367
368
369
370
371 public void removePeerTableCFs(String id, String tableCf) throws ReplicationException {
372 removePeerTableCFs(id, ReplicationSerDeHelper.parseTableCFsFromConfig(tableCf));
373 }
374
375
376
377
378
379
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
422
423
424
425
426
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
435
436
437
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
456
457
458
459
460
461
462
463
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
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
496
497
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
516
517
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
534
535
536
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
554
555
556
557
558
559
560
561
562
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
576
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
640
641
642
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
681
682 private enum ReplicationState {
683 ENABLED,
684 MIXED,
685 DISABLED
686 }
687
688
689
690
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
730
731
732
733
734
735
736 private boolean copyReplicationScope(final HTableDescriptor peerHtd,
737 final HTableDescriptor localHtd) {
738
739
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
770
771
772
773
774
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
786
787 HTableDescriptor peerHtdCopy = new HTableDescriptor(peerHtd);
788
789 result = copyReplicationScope(peerHtdCopy, localHtd);
790
791
792 if (result) {
793 result = (peerHtdCopy.compareTo(localHtd) == 0);
794 }
795
796 return result;
797 }
798 }