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 java.io.Closeable;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Set;
31
32 import org.apache.commons.lang.StringUtils;
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.hadoop.hbase.classification.InterfaceAudience;
36 import org.apache.hadoop.hbase.classification.InterfaceStability;
37 import org.apache.hadoop.conf.Configuration;
38 import org.apache.hadoop.hbase.Abortable;
39 import org.apache.hadoop.hbase.HColumnDescriptor;
40 import org.apache.hadoop.hbase.HConstants;
41 import org.apache.hadoop.hbase.HTableDescriptor;
42 import org.apache.hadoop.hbase.TableName;
43 import org.apache.hadoop.hbase.TableNotFoundException;
44 import org.apache.hadoop.hbase.classification.InterfaceAudience;
45 import org.apache.hadoop.hbase.classification.InterfaceStability;
46 import org.apache.hadoop.hbase.client.Admin;
47 import org.apache.hadoop.hbase.client.HBaseAdmin;
48 import org.apache.hadoop.hbase.client.Connection;
49 import org.apache.hadoop.hbase.client.ConnectionFactory;
50 import org.apache.hadoop.hbase.client.RegionLocator;
51 import org.apache.hadoop.hbase.replication.ReplicationException;
52 import org.apache.hadoop.hbase.replication.ReplicationFactory;
53 import org.apache.hadoop.hbase.replication.ReplicationPeer;
54 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
55 import org.apache.hadoop.hbase.replication.ReplicationPeerZKImpl;
56 import org.apache.hadoop.hbase.replication.ReplicationPeers;
57 import org.apache.hadoop.hbase.replication.ReplicationQueuesClient;
58 import org.apache.hadoop.hbase.util.Pair;
59 import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
60 import org.apache.zookeeper.KeeperException;
61 import org.apache.zookeeper.data.Stat;
62
63 import com.google.common.annotations.VisibleForTesting;
64 import com.google.common.collect.Lists;
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 @InterfaceAudience.Public
90 @InterfaceStability.Evolving
91 public class ReplicationAdmin implements Closeable {
92 private static final Log LOG = LogFactory.getLog(ReplicationAdmin.class);
93
94 public static final String TNAME = "tableName";
95 public static final String CFNAME = "columnFamlyName";
96
97
98
99 public static final String REPLICATIONTYPE = "replicationType";
100 public static final String REPLICATIONGLOBAL = Integer
101 .toString(HConstants.REPLICATION_SCOPE_GLOBAL);
102
103 private final Connection connection;
104
105
106 private final ReplicationQueuesClient replicationQueuesClient;
107 private final ReplicationPeers replicationPeers;
108
109
110
111
112 private final ZooKeeperWatcher zkw;
113
114
115
116
117
118
119
120 public ReplicationAdmin(Configuration conf) throws IOException {
121 if (!conf.getBoolean(HConstants.REPLICATION_ENABLE_KEY,
122 HConstants.REPLICATION_ENABLE_DEFAULT)) {
123 throw new RuntimeException("hbase.replication isn't true, please " +
124 "enable it in order to use replication");
125 }
126 this.connection = ConnectionFactory.createConnection(conf);
127 try {
128 zkw = createZooKeeperWatcher();
129 try {
130 this.replicationQueuesClient =
131 ReplicationFactory.getReplicationQueuesClient(zkw, conf, this.connection);
132 this.replicationQueuesClient.init();
133 this.replicationPeers = ReplicationFactory.getReplicationPeers(zkw, conf,
134 this.replicationQueuesClient, this.connection);
135 this.replicationPeers.init();
136 } catch (Exception exception) {
137 if (zkw != null) {
138 zkw.close();
139 }
140 throw exception;
141 }
142 } catch (Exception exception) {
143 if (connection != null) {
144 connection.close();
145 }
146 if (exception instanceof IOException) {
147 throw (IOException) exception;
148 } else if (exception instanceof RuntimeException) {
149 throw (RuntimeException) exception;
150 } else {
151 throw new IOException("Error initializing the replication admin client.", exception);
152 }
153 }
154 }
155
156 private ZooKeeperWatcher createZooKeeperWatcher() throws IOException {
157
158 return new ZooKeeperWatcher(connection.getConfiguration(), "ReplicationAdmin", new Abortable() {
159 @Override
160 public void abort(String why, Throwable e) {
161 LOG.error(why, e);
162
163
164 }
165
166 @Override
167 public boolean isAborted() {
168 return false;
169 }
170 });
171 }
172
173
174
175
176
177
178
179
180
181
182 @Deprecated
183 public void addPeer(String id, String clusterKey) throws ReplicationException {
184 this.addPeer(id, new ReplicationPeerConfig().setClusterKey(clusterKey), null);
185 }
186
187 @Deprecated
188 public void addPeer(String id, String clusterKey, String tableCFs)
189 throws ReplicationException {
190 this.replicationPeers.addPeer(id,
191 new ReplicationPeerConfig().setClusterKey(clusterKey), tableCFs);
192 }
193
194
195
196
197
198
199
200
201
202
203 public void addPeer(String id, ReplicationPeerConfig peerConfig,
204 Map<TableName, ? extends Collection<String>> tableCfs) throws ReplicationException {
205 this.replicationPeers.addPeer(id, peerConfig, getTableCfsStr(tableCfs));
206 }
207
208 public static Map<TableName, List<String>> parseTableCFsFromConfig(String tableCFsConfig) {
209 if (tableCFsConfig == null || tableCFsConfig.trim().length() == 0) {
210 return null;
211 }
212
213 Map<TableName, List<String>> tableCFsMap = null;
214
215
216
217 String[] tables = tableCFsConfig.split(";");
218 for (String tab : tables) {
219
220 tab = tab.trim();
221 if (tab.length() == 0) {
222 continue;
223 }
224
225
226 String[] pair = tab.split(":");
227 String tabName = pair[0].trim();
228 if (pair.length > 2 || tabName.length() == 0) {
229 LOG.error("ignore invalid tableCFs setting: " + tab);
230 continue;
231 }
232
233
234 List<String> cfs = null;
235 if (pair.length == 2) {
236 String[] cfsList = pair[1].split(",");
237 for (String cf : cfsList) {
238 String cfName = cf.trim();
239 if (cfName.length() > 0) {
240 if (cfs == null) {
241 cfs = new ArrayList<String>();
242 }
243 cfs.add(cfName);
244 }
245 }
246 }
247
248
249 if (tableCFsMap == null) {
250 tableCFsMap = new HashMap<TableName, List<String>>();
251 }
252 tableCFsMap.put(TableName.valueOf(tabName), cfs);
253 }
254 return tableCFsMap;
255 }
256
257 @VisibleForTesting
258 static String getTableCfsStr(Map<TableName, ? extends Collection<String>> tableCfs) {
259 String tableCfsStr = null;
260 if (tableCfs != null) {
261
262 StringBuilder builder = new StringBuilder();
263 for (Entry<TableName, ? extends Collection<String>> entry : tableCfs.entrySet()) {
264 if (builder.length() > 0) {
265 builder.append(";");
266 }
267 builder.append(entry.getKey());
268 if (entry.getValue() != null && !entry.getValue().isEmpty()) {
269 builder.append(":");
270 builder.append(StringUtils.join(entry.getValue(), ","));
271 }
272 }
273 tableCfsStr = builder.toString();
274 }
275 return tableCfsStr;
276 }
277
278
279
280
281
282 public void removePeer(String id) throws ReplicationException {
283 this.replicationPeers.removePeer(id);
284 }
285
286
287
288
289
290 public void enablePeer(String id) throws ReplicationException {
291 this.replicationPeers.enablePeer(id);
292 }
293
294
295
296
297
298 public void disablePeer(String id) throws ReplicationException {
299 this.replicationPeers.disablePeer(id);
300 }
301
302
303
304
305
306 public int getPeersCount() {
307 return this.replicationPeers.getAllPeerIds().size();
308 }
309
310
311
312
313
314
315 @Deprecated
316 public Map<String, String> listPeers() {
317 Map<String, ReplicationPeerConfig> peers = this.listPeerConfigs();
318 Map<String, String> ret = new HashMap<String, String>(peers.size());
319
320 for (Map.Entry<String, ReplicationPeerConfig> entry : peers.entrySet()) {
321 ret.put(entry.getKey(), entry.getValue().getClusterKey());
322 }
323 return ret;
324 }
325
326 public Map<String, ReplicationPeerConfig> listPeerConfigs() {
327 return this.replicationPeers.getAllPeerConfigs();
328 }
329
330 public ReplicationPeerConfig getPeerConfig(String id) throws ReplicationException {
331 return this.replicationPeers.getReplicationPeerConfig(id);
332 }
333
334
335
336
337
338 public String getPeerTableCFs(String id) throws ReplicationException {
339 return this.replicationPeers.getPeerTableCFsConfig(id);
340 }
341
342
343
344
345
346
347 @Deprecated
348 public void setPeerTableCFs(String id, String tableCFs) throws ReplicationException {
349 this.replicationPeers.setPeerTableCFsConfig(id, tableCFs);
350 }
351
352
353
354
355
356
357 public void appendPeerTableCFs(String id, String tableCfs) throws ReplicationException {
358 appendPeerTableCFs(id, parseTableCFsFromConfig(tableCfs));
359 }
360
361
362
363
364
365
366 public void appendPeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs)
367 throws ReplicationException {
368 if (tableCfs == null) {
369 throw new ReplicationException("tableCfs is null");
370 }
371 Map<TableName, List<String>> preTableCfs = parseTableCFsFromConfig(getPeerTableCFs(id));
372 if (preTableCfs == null) {
373 setPeerTableCFs(id, tableCfs);
374 return;
375 }
376
377 for (Map.Entry<TableName, ? extends Collection<String>> entry : tableCfs.entrySet()) {
378 TableName table = entry.getKey();
379 Collection<String> appendCfs = entry.getValue();
380 if (preTableCfs.containsKey(table)) {
381 List<String> cfs = preTableCfs.get(table);
382 if (cfs == null || appendCfs == null) {
383 preTableCfs.put(table, null);
384 } else {
385 Set<String> cfSet = new HashSet<String>(cfs);
386 cfSet.addAll(appendCfs);
387 preTableCfs.put(table, Lists.newArrayList(cfSet));
388 }
389 } else {
390 if (appendCfs == null || appendCfs.isEmpty()) {
391 preTableCfs.put(table, null);
392 } else {
393 preTableCfs.put(table, Lists.newArrayList(appendCfs));
394 }
395 }
396 }
397 setPeerTableCFs(id, preTableCfs);
398 }
399
400
401
402
403
404
405
406 public void removePeerTableCFs(String id, String tableCf) throws ReplicationException {
407 removePeerTableCFs(id, parseTableCFsFromConfig(tableCf));
408 }
409
410
411
412
413
414
415
416 public void removePeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs)
417 throws ReplicationException {
418 if (tableCfs == null) {
419 throw new ReplicationException("tableCfs is null");
420 }
421
422 Map<TableName, List<String>> preTableCfs = parseTableCFsFromConfig(getPeerTableCFs(id));
423 if (preTableCfs == null) {
424 throw new ReplicationException("Table-Cfs for peer" + id + " is null");
425 }
426 for (Map.Entry<TableName, ? extends Collection<String>> entry: tableCfs.entrySet()) {
427 TableName table = entry.getKey();
428 Collection<String> removeCfs = entry.getValue();
429 if (preTableCfs.containsKey(table)) {
430 List<String> cfs = preTableCfs.get(table);
431 if (cfs == null && removeCfs == null) {
432 preTableCfs.remove(table);
433 } else if (cfs != null && removeCfs != null) {
434 Set<String> cfSet = new HashSet<String>(cfs);
435 cfSet.removeAll(removeCfs);
436 if (cfSet.isEmpty()) {
437 preTableCfs.remove(table);
438 } else {
439 preTableCfs.put(table, Lists.newArrayList(cfSet));
440 }
441 } else if (cfs == null && removeCfs != null) {
442 throw new ReplicationException("Cannot remove cf of table: " + table
443 + " which doesn't specify cfs from table-cfs config in peer: " + id);
444 } else if (cfs != null && removeCfs == null) {
445 throw new ReplicationException("Cannot remove table: " + table
446 + " which has specified cfs from table-cfs config in peer: " + id);
447 }
448 } else {
449 throw new ReplicationException("No table: " + table + " in table-cfs config of peer: " + id);
450 }
451 }
452 setPeerTableCFs(id, preTableCfs);
453 }
454
455
456
457
458
459
460
461
462
463 public void setPeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs)
464 throws ReplicationException {
465 this.replicationPeers.setPeerTableCFsConfig(id, getTableCfsStr(tableCfs));
466 }
467
468
469
470
471
472
473
474 public boolean getPeerState(String id) throws ReplicationException {
475 return this.replicationPeers.getStatusOfPeerFromBackingStore(id);
476 }
477
478 @Override
479 public void close() throws IOException {
480 if (this.zkw != null) {
481 this.zkw.close();
482 }
483 if (this.connection != null) {
484 this.connection.close();
485 }
486 }
487
488
489
490
491
492
493
494
495
496
497
498
499
500 public List<HashMap<String, String>> listReplicated() throws IOException {
501 List<HashMap<String, String>> replicationColFams = new ArrayList<HashMap<String, String>>();
502
503 Admin admin = connection.getAdmin();
504 HTableDescriptor[] tables;
505 try {
506 tables = admin.listTables();
507 } finally {
508 if (admin!= null) admin.close();
509 }
510
511 for (HTableDescriptor table : tables) {
512 HColumnDescriptor[] columns = table.getColumnFamilies();
513 String tableName = table.getNameAsString();
514 for (HColumnDescriptor column : columns) {
515 if (column.getScope() != HConstants.REPLICATION_SCOPE_LOCAL) {
516
517 HashMap<String, String> replicationEntry = new HashMap<String, String>();
518 replicationEntry.put(TNAME, tableName);
519 replicationEntry.put(CFNAME, column.getNameAsString());
520 replicationEntry.put(REPLICATIONTYPE, REPLICATIONGLOBAL);
521 replicationColFams.add(replicationEntry);
522 }
523 }
524 }
525
526 return replicationColFams;
527 }
528
529
530
531
532
533
534 public void enableTableRep(final TableName tableName) throws IOException {
535 if (tableName == null) {
536 throw new IllegalArgumentException("Table name cannot be null");
537 }
538 try (Admin admin = this.connection.getAdmin()) {
539 if (!admin.tableExists(tableName)) {
540 throw new TableNotFoundException("Table '" + tableName.getNameAsString()
541 + "' does not exists.");
542 }
543 }
544 byte[][] splits = getTableSplitRowKeys(tableName);
545 checkAndSyncTableDescToPeers(tableName, splits);
546 setTableRep(tableName, true);
547 }
548
549
550
551
552
553
554 public void disableTableRep(final TableName tableName) throws IOException {
555 if (tableName == null) {
556 throw new IllegalArgumentException("Table name is null");
557 }
558 try (Admin admin = this.connection.getAdmin()) {
559 if (!admin.tableExists(tableName)) {
560 throw new TableNotFoundException("Table '" + tableName.getNamespaceAsString()
561 + "' does not exists.");
562 }
563 }
564 setTableRep(tableName, false);
565 }
566
567
568
569
570
571
572
573 private byte[][] getTableSplitRowKeys(TableName tableName) throws IOException {
574 try (RegionLocator locator = connection.getRegionLocator(tableName);) {
575 byte[][] startKeys = locator.getStartKeys();
576 if (startKeys.length == 1) {
577 return null;
578 }
579 byte[][] splits = new byte[startKeys.length - 1][];
580 for (int i = 1; i < startKeys.length; i++) {
581 splits[i - 1] = startKeys[i];
582 }
583 return splits;
584 }
585 }
586
587
588
589
590
591
592
593
594
595
596
597 private void checkAndSyncTableDescToPeers(final TableName tableName, final byte[][] splits)
598 throws IOException {
599 List<ReplicationPeer> repPeers = listValidReplicationPeers();
600 if (repPeers == null || repPeers.size() <= 0) {
601 throw new IllegalArgumentException("Found no peer cluster for replication.");
602 }
603
604 final TableName onlyTableNameQualifier = TableName.valueOf(tableName.getQualifierAsString());
605
606 for (ReplicationPeer repPeer : repPeers) {
607 Map<TableName, List<String>> tableCFMap = repPeer.getTableCFs();
608
609
610 if (tableCFMap != null && !tableCFMap.containsKey(onlyTableNameQualifier)) {
611 continue;
612 }
613
614 Configuration peerConf = repPeer.getConfiguration();
615 HTableDescriptor htd = null;
616 try (Connection conn = ConnectionFactory.createConnection(peerConf);
617 Admin admin = this.connection.getAdmin();
618 Admin repHBaseAdmin = conn.getAdmin()) {
619 htd = admin.getTableDescriptor(tableName);
620 HTableDescriptor peerHtd = null;
621 if (!repHBaseAdmin.tableExists(tableName)) {
622 repHBaseAdmin.createTable(htd, splits);
623 } else {
624 peerHtd = repHBaseAdmin.getTableDescriptor(tableName);
625 if (peerHtd == null) {
626 throw new IllegalArgumentException("Failed to get table descriptor for table "
627 + tableName.getNameAsString() + " from peer cluster " + repPeer.getId());
628 } else if (!peerHtd.equals(htd)) {
629 throw new IllegalArgumentException("Table " + tableName.getNameAsString()
630 + " exists in peer cluster " + repPeer.getId()
631 + ", but the table descriptors are not same when comapred with source cluster."
632 + " Thus can not enable the table's replication switch.");
633 }
634 }
635 }
636 }
637 }
638
639 @VisibleForTesting
640 List<ReplicationPeer> listValidReplicationPeers() {
641 Map<String, ReplicationPeerConfig> peers = listPeerConfigs();
642 if (peers == null || peers.size() <= 0) {
643 return null;
644 }
645 List<ReplicationPeer> validPeers = new ArrayList<ReplicationPeer>(peers.size());
646 for (Entry<String, ReplicationPeerConfig> peerEntry : peers.entrySet()) {
647 String peerId = peerEntry.getKey();
648 Stat s = null;
649 try {
650 Pair<ReplicationPeerConfig, Configuration> pair = this.replicationPeers.getPeerConf(peerId);
651 Configuration peerConf = pair.getSecond();
652 ReplicationPeer peer = new ReplicationPeerZKImpl(peerConf, peerId, pair.getFirst(),
653 parseTableCFsFromConfig(this.getPeerTableCFs(peerId)));
654 s =
655 zkw.getRecoverableZooKeeper().exists(peerConf.get(HConstants.ZOOKEEPER_ZNODE_PARENT),
656 null);
657 if (null == s) {
658 LOG.info(peerId + ' ' + pair.getFirst().getClusterKey() + " is invalid now.");
659 continue;
660 }
661 validPeers.add(peer);
662 } catch (ReplicationException e) {
663 LOG.warn("Failed to get valid replication peers. "
664 + "Error connecting to peer cluster with peerId=" + peerId);
665 LOG.debug("Failure details to get valid replication peers.", e);
666 continue;
667 } catch (KeeperException e) {
668 LOG.warn("Failed to get valid replication peers. KeeperException code="
669 + e.code().intValue());
670 LOG.debug("Failure details to get valid replication peers.", e);
671 continue;
672 } catch (InterruptedException e) {
673 LOG.warn("Failed to get valid replication peers due to InterruptedException.");
674 LOG.debug("Failure details to get valid replication peers.", e);
675 continue;
676 }
677 }
678 return validPeers;
679 }
680
681
682
683
684
685
686
687 private void setTableRep(final TableName tableName, boolean isRepEnabled) throws IOException {
688 Admin admin = null;
689 try {
690 admin = this.connection.getAdmin();
691 HTableDescriptor htd = admin.getTableDescriptor(tableName);
692 if (isTableRepEnabled(htd) ^ isRepEnabled) {
693 boolean isOnlineSchemaUpdateEnabled =
694 this.connection.getConfiguration()
695 .getBoolean("hbase.online.schema.update.enable", true);
696 if (!isOnlineSchemaUpdateEnabled) {
697 admin.disableTable(tableName);
698 }
699 for (HColumnDescriptor hcd : htd.getFamilies()) {
700 hcd.setScope(isRepEnabled ? HConstants.REPLICATION_SCOPE_GLOBAL
701 : HConstants.REPLICATION_SCOPE_LOCAL);
702 }
703 admin.modifyTable(tableName, htd);
704 if (!isOnlineSchemaUpdateEnabled) {
705 admin.enableTable(tableName);
706 }
707 }
708 } finally {
709 if (admin != null) {
710 try {
711 admin.close();
712 } catch (IOException e) {
713 LOG.warn("Failed to close admin connection.");
714 LOG.debug("Details on failure to close admin connection.", e);
715 }
716 }
717 }
718 }
719
720
721
722
723
724 private boolean isTableRepEnabled(HTableDescriptor htd) {
725 for (HColumnDescriptor hcd : htd.getFamilies()) {
726 if (hcd.getScope() != HConstants.REPLICATION_SCOPE_GLOBAL) {
727 return false;
728 }
729 }
730 return true;
731 }
732 }