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