/** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.client.replication; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Abortable; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.ReplicationPeerNotFoundException; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.HBaseAdmin; import org.apache.hadoop.hbase.client.RegionLocator; import org.apache.hadoop.hbase.replication.ReplicationException; import org.apache.hadoop.hbase.replication.ReplicationFactory; import org.apache.hadoop.hbase.replication.ReplicationPeer; import org.apache.hadoop.hbase.replication.ReplicationPeerConfig; import org.apache.hadoop.hbase.replication.ReplicationPeerDescription; import org.apache.hadoop.hbase.replication.ReplicationPeerZKImpl; import org.apache.hadoop.hbase.replication.ReplicationPeers; import org.apache.hadoop.hbase.replication.ReplicationQueuesClient; import org.apache.hadoop.hbase.replication.ReplicationQueuesClientArguments; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; /** * <p> * This class provides the administrative interface to HBase cluster * replication. * </p> * <p> * Adding a new peer results in creating new outbound connections from every * region server to a subset of region servers on the slave cluster. Each * new stream of replication will start replicating from the beginning of the * current WAL, meaning that edits from that past will be replicated. * </p> * <p> * Removing a peer is a destructive and irreversible operation that stops * all the replication streams for the given cluster and deletes the metadata * used to keep track of the replication state. * </p> * <p> * To see which commands are available in the shell, type * <code>replication</code>. * </p> * * @deprecated use {@link org.apache.hadoop.hbase.client.Admin} instead. */ @InterfaceAudience.Public @Deprecated public class ReplicationAdmin implements Closeable { private static final Log LOG = LogFactory.getLog(ReplicationAdmin.class); public static final String TNAME = "tableName"; public static final String CFNAME = "columnFamilyName"; // only Global for now, can add other type // such as, 1) no global replication, or 2) the table is replicated to this cluster, etc. public static final String REPLICATIONTYPE = "replicationType"; public static final String REPLICATIONGLOBAL = Integer.toString(HConstants.REPLICATION_SCOPE_GLOBAL); public static final String REPLICATIONSERIAL = Integer.toString(HConstants.REPLICATION_SCOPE_SERIAL); private final Connection connection; // TODO: replication should be managed by master. All the classes except ReplicationAdmin should // be moved to hbase-server. Resolve it in HBASE-11392. private final ReplicationQueuesClient replicationQueuesClient; private final ReplicationPeers replicationPeers; /** * A watcher used by replicationPeers and replicationQueuesClient. Keep reference so can dispose * on {@link #close()}. */ private final ZooKeeperWatcher zkw; private Admin admin; /** * Constructor that creates a connection to the local ZooKeeper ensemble. * @param conf Configuration to use * @throws IOException if an internal replication error occurs * @throws RuntimeException if replication isn't enabled. */ public ReplicationAdmin(Configuration conf) throws IOException { this.connection = ConnectionFactory.createConnection(conf); admin = connection.getAdmin(); try { zkw = createZooKeeperWatcher(); try { this.replicationQueuesClient = ReplicationFactory.getReplicationQueuesClient(new ReplicationQueuesClientArguments(conf, this.connection, zkw)); this.replicationQueuesClient.init(); this.replicationPeers = ReplicationFactory.getReplicationPeers(zkw, conf, this.replicationQueuesClient, this.connection); this.replicationPeers.init(); } catch (Exception exception) { if (zkw != null) { zkw.close(); } throw exception; } } catch (Exception exception) { connection.close(); if (exception instanceof IOException) { throw (IOException) exception; } else if (exception instanceof RuntimeException) { throw (RuntimeException) exception; } else { throw new IOException("Error initializing the replication admin client.", exception); } } } private ZooKeeperWatcher createZooKeeperWatcher() throws IOException { // This Abortable doesn't 'abort'... it just logs. return new ZooKeeperWatcher(connection.getConfiguration(), "ReplicationAdmin", new Abortable() { @Override public void abort(String why, Throwable e) { LOG.error(why, e); // We used to call system.exit here but this script can be embedded by other programs that // want to do replication stuff... so inappropriate calling System.exit. Just log for now. } @Override public boolean isAborted() { return false; } }); } /** * Add a new remote slave cluster for replication. * @param id a short name that identifies the cluster * @param peerConfig configuration for the replication slave cluster * @param tableCfs the table and column-family list which will be replicated for this peer. * A map from tableName to column family names. An empty collection can be passed * to indicate replicating all column families. Pass null for replicating all table and column * families * @deprecated as release of 2.0.0, and it will be removed in 3.0.0, * use {@link #addPeer(String, ReplicationPeerConfig)} instead. */ @Deprecated public void addPeer(String id, ReplicationPeerConfig peerConfig, Map<TableName, ? extends Collection<String>> tableCfs) throws ReplicationException, IOException { if (tableCfs != null) { peerConfig.setTableCFsMap(tableCfs); } this.admin.addReplicationPeer(id, peerConfig); } /** * Add a new remote slave cluster for replication. * @param id a short name that identifies the cluster * @param peerConfig configuration for the replication slave cluster * @deprecated use * {@link org.apache.hadoop.hbase.client.Admin#addReplicationPeer(String, ReplicationPeerConfig)} * instead */ @Deprecated public void addPeer(String id, ReplicationPeerConfig peerConfig) throws ReplicationException, IOException { checkNamespacesAndTableCfsConfigConflict(peerConfig.getNamespaces(), peerConfig.getTableCFsMap()); this.admin.addReplicationPeer(id, peerConfig); } /** * @deprecated as release of 2.0.0, and it will be removed in 3.0.0 * */ @Deprecated public static Map<TableName, List<String>> parseTableCFsFromConfig(String tableCFsConfig) { return ReplicationSerDeHelper.parseTableCFsFromConfig(tableCFsConfig); } /** * @deprecated use * {@link org.apache.hadoop.hbase.client.Admin#updateReplicationPeerConfig(String, ReplicationPeerConfig)} * instead */ @Deprecated public void updatePeerConfig(String id, ReplicationPeerConfig peerConfig) throws IOException { this.admin.updateReplicationPeerConfig(id, peerConfig); } /** * Removes a peer cluster and stops the replication to it. * @param id a short name that identifies the cluster * @deprecated use {@link org.apache.hadoop.hbase.client.Admin#removeReplicationPeer(String)} instead */ @Deprecated public void removePeer(String id) throws IOException { this.admin.removeReplicationPeer(id); } /** * Restart the replication stream to the specified peer. * @param id a short name that identifies the cluster * @deprecated use {@link org.apache.hadoop.hbase.client.Admin#enableReplicationPeer(String)} * instead */ @Deprecated public void enablePeer(String id) throws IOException { this.admin.enableReplicationPeer(id); } /** * Stop the replication stream to the specified peer. * @param id a short name that identifies the cluster * @deprecated use {@link org.apache.hadoop.hbase.client.Admin#disableReplicationPeer(String)} * instead */ @Deprecated public void disablePeer(String id) throws IOException { this.admin.disableReplicationPeer(id); } /** * Get the number of slave clusters the local cluster has. * @return number of slave clusters * @throws IOException * @deprecated */ @Deprecated public int getPeersCount() throws IOException { return this.admin.listReplicationPeers().size(); } /** * @deprecated use {@link org.apache.hadoop.hbase.client.Admin#listReplicationPeers()} instead */ @Deprecated public Map<String, ReplicationPeerConfig> listPeerConfigs() throws IOException { List<ReplicationPeerDescription> peers = this.admin.listReplicationPeers(); Map<String, ReplicationPeerConfig> result = new TreeMap<>(); for (ReplicationPeerDescription peer : peers) { result.put(peer.getPeerId(), peer.getPeerConfig()); } return result; } /** * @deprecated use {@link org.apache.hadoop.hbase.client.Admin#getReplicationPeerConfig(String)} * instead */ @Deprecated public ReplicationPeerConfig getPeerConfig(String id) throws IOException { return admin.getReplicationPeerConfig(id); } /** * Get the replicable table-cf config of the specified peer. * @param id a short name that identifies the cluster * @deprecated as release of 2.0.0, and it will be removed in 3.0.0, * use {@link #getPeerConfig(String)} instead. * */ @Deprecated public String getPeerTableCFs(String id) throws IOException { ReplicationPeerConfig peerConfig = admin.getReplicationPeerConfig(id); return ReplicationSerDeHelper.convertToString(peerConfig.getTableCFsMap()); } /** * Append the replicable table-cf config of the specified peer * @param id a short that identifies the cluster * @param tableCfs table-cfs config str * @throws ReplicationException * @throws IOException * @deprecated as release of 2.0.0, and it will be removed in 3.0.0, * use {@link #appendPeerTableCFs(String, Map)} instead. */ @Deprecated public void appendPeerTableCFs(String id, String tableCfs) throws ReplicationException, IOException { appendPeerTableCFs(id, ReplicationSerDeHelper.parseTableCFsFromConfig(tableCfs)); } /** * Append the replicable table-cf config of the specified peer * @param id a short that identifies the cluster * @param tableCfs A map from tableName to column family names * @throws ReplicationException * @throws IOException */ @Deprecated public void appendPeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs) throws ReplicationException, IOException { if (tableCfs == null) { throw new ReplicationException("tableCfs is null"); } ReplicationPeerConfig peerConfig = admin.getReplicationPeerConfig(id); Map<TableName, List<String>> preTableCfs = peerConfig.getTableCFsMap(); if (preTableCfs == null) { setPeerTableCFs(id, tableCfs); return; } for (Map.Entry<TableName, ? extends Collection<String>> entry : tableCfs.entrySet()) { TableName table = entry.getKey(); Collection<String> appendCfs = entry.getValue(); if (preTableCfs.containsKey(table)) { List<String> cfs = preTableCfs.get(table); if (cfs == null || appendCfs == null || appendCfs.isEmpty()) { preTableCfs.put(table, null); } else { Set<String> cfSet = new HashSet<>(cfs); cfSet.addAll(appendCfs); preTableCfs.put(table, Lists.newArrayList(cfSet)); } } else { if (appendCfs == null || appendCfs.isEmpty()) { preTableCfs.put(table, null); } else { preTableCfs.put(table, Lists.newArrayList(appendCfs)); } } } updatePeerConfig(id, peerConfig); } /** * Remove some table-cfs from table-cfs config of the specified peer * @param id a short name that identifies the cluster * @param tableCf table-cfs config str * @throws ReplicationException * @throws IOException * @deprecated as release of 2.0.0, and it will be removed in 3.0.0, * use {@link #removePeerTableCFs(String, Map)} instead. */ @Deprecated public void removePeerTableCFs(String id, String tableCf) throws ReplicationException, IOException { removePeerTableCFs(id, ReplicationSerDeHelper.parseTableCFsFromConfig(tableCf)); } /** * Remove some table-cfs from config of the specified peer * @param id a short name that identifies the cluster * @param tableCfs A map from tableName to column family names * @throws ReplicationException * @throws IOException */ @Deprecated public void removePeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs) throws ReplicationException, IOException { if (tableCfs == null) { throw new ReplicationException("tableCfs is null"); } ReplicationPeerConfig peerConfig = admin.getReplicationPeerConfig(id); Map<TableName, List<String>> preTableCfs = peerConfig.getTableCFsMap(); if (preTableCfs == null) { throw new ReplicationException("Table-Cfs for peer" + id + " is null"); } for (Map.Entry<TableName, ? extends Collection<String>> entry: tableCfs.entrySet()) { TableName table = entry.getKey(); Collection<String> removeCfs = entry.getValue(); if (preTableCfs.containsKey(table)) { List<String> cfs = preTableCfs.get(table); if (cfs == null && (removeCfs == null || removeCfs.isEmpty())) { preTableCfs.remove(table); } else if (cfs != null && (removeCfs != null && !removeCfs.isEmpty())) { Set<String> cfSet = new HashSet<>(cfs); cfSet.removeAll(removeCfs); if (cfSet.isEmpty()) { preTableCfs.remove(table); } else { preTableCfs.put(table, Lists.newArrayList(cfSet)); } } else if (cfs == null && (removeCfs != null && !removeCfs.isEmpty())) { throw new ReplicationException("Cannot remove cf of table: " + table + " which doesn't specify cfs from table-cfs config in peer: " + id); } else if (cfs != null && (removeCfs == null || removeCfs.isEmpty())) { throw new ReplicationException("Cannot remove table: " + table + " which has specified cfs from table-cfs config in peer: " + id); } } else { throw new ReplicationException("No table: " + table + " in table-cfs config of peer: " + id); } } updatePeerConfig(id, peerConfig); } /** * Set the replicable table-cf config of the specified peer * @param id a short name that identifies the cluster * @param tableCfs the table and column-family list which will be replicated for this peer. * A map from tableName to column family names. An empty collection can be passed * to indicate replicating all column families. Pass null for replicating all table and column * families */ @Deprecated public void setPeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs) throws IOException { ReplicationPeerConfig peerConfig = getPeerConfig(id); peerConfig.setTableCFsMap(tableCfs); updatePeerConfig(id, peerConfig); } /** * Get the state of the specified peer cluster * @param id String format of the Short name that identifies the peer, * an IllegalArgumentException is thrown if it doesn't exist * @return true if replication is enabled to that peer, false if it isn't */ @Deprecated public boolean getPeerState(String id) throws ReplicationException, IOException { List<ReplicationPeerDescription> peers = admin.listReplicationPeers(id); if (peers.isEmpty() || !id.equals(peers.get(0).getPeerId())) { throw new ReplicationPeerNotFoundException(id); } return peers.get(0).isEnabled(); } @Override public void close() throws IOException { if (this.zkw != null) { this.zkw.close(); } if (this.connection != null) { this.connection.close(); } admin.close(); } /** * Find all column families that are replicated from this cluster * @return the full list of the replicated column families of this cluster as: * tableName, family name, replicationType * * Currently replicationType is Global. In the future, more replication * types may be extended here. For example * 1) the replication may only apply to selected peers instead of all peers * 2) the replicationType may indicate the host Cluster servers as Slave * for the table:columnFam. * @deprecated use {@link org.apache.hadoop.hbase.client.Admin#listReplicatedTableCFs()} instead */ @Deprecated public List<HashMap<String, String>> listReplicated() throws IOException { List<HashMap<String, String>> replicationColFams = new ArrayList<>(); admin.listReplicatedTableCFs().forEach( (tableCFs) -> { String table = tableCFs.getTable().getNameAsString(); tableCFs.getColumnFamilyMap() .forEach( (cf, scope) -> { HashMap<String, String> replicationEntry = new HashMap<>(); replicationEntry.put(TNAME, table); replicationEntry.put(CFNAME, cf); replicationEntry.put(REPLICATIONTYPE, scope == HConstants.REPLICATION_SCOPE_GLOBAL ? REPLICATIONGLOBAL : REPLICATIONSERIAL); replicationColFams.add(replicationEntry); }); }); return replicationColFams; } /** * Enable a table's replication switch. * @param tableName name of the table * @throws IOException if a remote or network exception occurs * @deprecated use {@link org.apache.hadoop.hbase.client.Admin#enableTableReplication(TableName)} * instead */ @Deprecated public void enableTableRep(final TableName tableName) throws IOException { admin.enableTableReplication(tableName); } /** * Disable a table's replication switch. * @param tableName name of the table * @throws IOException if a remote or network exception occurs * @deprecated use {@link org.apache.hadoop.hbase.client.Admin#disableTableReplication(TableName)} * instead */ @Deprecated public void disableTableRep(final TableName tableName) throws IOException { admin.disableTableReplication(tableName); } @VisibleForTesting @Deprecated public void peerAdded(String id) throws ReplicationException { this.replicationPeers.peerConnected(id); } /** * @deprecated use {@link org.apache.hadoop.hbase.client.Admin#listReplicationPeers()} instead */ @VisibleForTesting @Deprecated List<ReplicationPeer> listReplicationPeers() throws IOException { Map<String, ReplicationPeerConfig> peers = listPeerConfigs(); if (peers == null || peers.size() <= 0) { return null; } List<ReplicationPeer> listOfPeers = new ArrayList<>(peers.size()); for (Entry<String, ReplicationPeerConfig> peerEntry : peers.entrySet()) { String peerId = peerEntry.getKey(); try { Pair<ReplicationPeerConfig, Configuration> pair = this.replicationPeers.getPeerConf(peerId); Configuration peerConf = pair.getSecond(); ReplicationPeer peer = new ReplicationPeerZKImpl(zkw, pair.getSecond(), peerId, pair.getFirst(), this.connection); listOfPeers.add(peer); } catch (ReplicationException e) { LOG.warn("Failed to get valid replication peers. " + "Error connecting to peer cluster with peerId=" + peerId + ". Error message=" + e.getMessage()); LOG.debug("Failure details to get valid replication peers.", e); continue; } } return listOfPeers; } /** * Set a namespace in the peer config means that all tables in this namespace * will be replicated to the peer cluster. * * 1. If you already have set a namespace in the peer config, then you can't set any table * of this namespace to the peer config. * 2. If you already have set a table in the peer config, then you can't set this table's * namespace to the peer config. * * @param namespaces * @param tableCfs * @throws ReplicationException */ private void checkNamespacesAndTableCfsConfigConflict(Set<String> namespaces, Map<TableName, ? extends Collection<String>> tableCfs) throws ReplicationException { if (namespaces == null || namespaces.isEmpty()) { return; } if (tableCfs == null || tableCfs.isEmpty()) { return; } for (Map.Entry<TableName, ? extends Collection<String>> entry : tableCfs.entrySet()) { TableName table = entry.getKey(); if (namespaces.contains(table.getNamespaceAsString())) { throw new ReplicationException( "Table-cfs config conflict with namespaces config in peer"); } } } }