001/** 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019package org.apache.hadoop.hbase.zookeeper; 020 021import java.io.IOException; 022import java.util.Map.Entry; 023import java.util.Properties; 024 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.hbase.HConstants; 027import org.apache.yetus.audience.InterfaceAudience; 028 029import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 030 031/** 032 * Utility methods for reading, and building the ZooKeeper configuration. 033 * 034 * The order and priority for reading the config are as follows: 035 * (1). Property with "hbase.zookeeper.property." prefix from HBase XML 036 * (2). other zookeeper related properties in HBASE XML 037 */ 038@InterfaceAudience.Private 039public final class ZKConfig { 040 041 private static final String VARIABLE_START = "${"; 042 043 private ZKConfig() { 044 } 045 046 /** 047 * Make a Properties object holding ZooKeeper config. 048 * Parses the corresponding config options from the HBase XML configs 049 * and generates the appropriate ZooKeeper properties. 050 * @param conf Configuration to read from. 051 * @return Properties holding mappings representing ZooKeeper config file. 052 */ 053 public static Properties makeZKProps(Configuration conf) { 054 return makeZKPropsFromHbaseConfig(conf); 055 } 056 057 /** 058 * Make a Properties object holding ZooKeeper config. 059 * Parses the corresponding config options from the HBase XML configs 060 * and generates the appropriate ZooKeeper properties. 061 * 062 * @param conf Configuration to read from. 063 * @return Properties holding mappings representing ZooKeeper config file. 064 */ 065 private static Properties makeZKPropsFromHbaseConfig(Configuration conf) { 066 Properties zkProperties = new Properties(); 067 068 // Directly map all of the hbase.zookeeper.property.KEY properties. 069 // Synchronize on conf so no loading of configs while we iterate 070 synchronized (conf) { 071 for (Entry<String, String> entry : conf) { 072 String key = entry.getKey(); 073 if (key.startsWith(HConstants.ZK_CFG_PROPERTY_PREFIX)) { 074 String zkKey = key.substring(HConstants.ZK_CFG_PROPERTY_PREFIX_LEN); 075 String value = entry.getValue(); 076 // If the value has variables substitutions, need to do a get. 077 if (value.contains(VARIABLE_START)) { 078 value = conf.get(key); 079 } 080 zkProperties.setProperty(zkKey, value); 081 } 082 } 083 } 084 085 // If clientPort is not set, assign the default. 086 if (zkProperties.getProperty(HConstants.CLIENT_PORT_STR) == null) { 087 zkProperties.put(HConstants.CLIENT_PORT_STR, 088 HConstants.DEFAULT_ZOOKEPER_CLIENT_PORT); 089 } 090 091 // Create the server.X properties. 092 int peerPort = conf.getInt("hbase.zookeeper.peerport", 2888); 093 int leaderPort = conf.getInt("hbase.zookeeper.leaderport", 3888); 094 095 final String[] serverHosts = conf.getStrings(HConstants.ZOOKEEPER_QUORUM, 096 HConstants.LOCALHOST); 097 String serverHost; 098 String address; 099 String key; 100 for (int i = 0; i < serverHosts.length; ++i) { 101 if (serverHosts[i].contains(":")) { 102 serverHost = serverHosts[i].substring(0, serverHosts[i].indexOf(':')); 103 } else { 104 serverHost = serverHosts[i]; 105 } 106 address = serverHost + ":" + peerPort + ":" + leaderPort; 107 key = "server." + i; 108 zkProperties.put(key, address); 109 } 110 111 return zkProperties; 112 } 113 114 /** 115 * Return the ZK Quorum servers string given the specified configuration 116 * 117 * @param conf 118 * @return Quorum servers String 119 */ 120 private static String getZKQuorumServersStringFromHbaseConfig(Configuration conf) { 121 String defaultClientPort = Integer.toString( 122 conf.getInt(HConstants.ZOOKEEPER_CLIENT_PORT, HConstants.DEFAULT_ZOOKEPER_CLIENT_PORT)); 123 124 // Build the ZK quorum server string with "server:clientport" list, separated by ',' 125 final String[] serverHosts = 126 conf.getStrings(HConstants.ZOOKEEPER_QUORUM, HConstants.LOCALHOST); 127 return buildZKQuorumServerString(serverHosts, defaultClientPort); 128 } 129 130 /** 131 * Return the ZK Quorum servers string given the specified configuration. 132 * @return Quorum servers 133 */ 134 public static String getZKQuorumServersString(Configuration conf) { 135 return getZKQuorumServersStringFromHbaseConfig(conf); 136 } 137 138 /** 139 * Build the ZK quorum server string with "server:clientport" list, separated by ',' 140 * 141 * @param serverHosts a list of servers for ZK quorum 142 * @param clientPort the default client port 143 * @return the string for a list of "server:port" separated by "," 144 */ 145 public static String buildZKQuorumServerString(String[] serverHosts, String clientPort) { 146 StringBuilder quorumStringBuilder = new StringBuilder(); 147 String serverHost; 148 for (int i = 0; i < serverHosts.length; ++i) { 149 if (serverHosts[i].contains(":")) { 150 serverHost = serverHosts[i]; // just use the port specified from the input 151 } else { 152 serverHost = serverHosts[i] + ":" + clientPort; 153 } 154 if (i > 0) { 155 quorumStringBuilder.append(','); 156 } 157 quorumStringBuilder.append(serverHost); 158 } 159 return quorumStringBuilder.toString(); 160 } 161 162 /** 163 * Verifies that the given key matches the expected format for a ZooKeeper cluster key. 164 * The Quorum for the ZK cluster can have one the following formats (see examples below): 165 * 166 * <ol> 167 * <li>s1,s2,s3 (no client port in the list, the client port could be obtained from 168 * clientPort)</li> 169 * <li>s1:p1,s2:p2,s3:p3 (with client port, which could be same or different for each server, 170 * in this case, the clientPort would be ignored)</li> 171 * <li>s1:p1,s2,s3:p3 (mix of (1) and (2) - if port is not specified in a server, it would use 172 * the clientPort; otherwise, it would use the specified port)</li> 173 * </ol> 174 * 175 * @param key the cluster key to validate 176 * @throws IOException if the key could not be parsed 177 */ 178 public static void validateClusterKey(String key) throws IOException { 179 transformClusterKey(key); 180 } 181 182 /** 183 * Separate the given key into the three configurations it should contain: 184 * hbase.zookeeper.quorum, hbase.zookeeper.client.port 185 * and zookeeper.znode.parent 186 * @param key 187 * @return the three configuration in the described order 188 * @throws IOException 189 */ 190 public static ZKClusterKey transformClusterKey(String key) throws IOException { 191 String[] parts = key.split(":"); 192 193 if (parts.length == 3) { 194 if (!parts[2].matches("/.*[^/]")) { 195 throw new IOException("Cluster key passed " + key + " is invalid, the format should be:" + 196 HConstants.ZOOKEEPER_QUORUM + ":" + HConstants.ZOOKEEPER_CLIENT_PORT + ":" 197 + HConstants.ZOOKEEPER_ZNODE_PARENT); 198 } 199 return new ZKClusterKey(parts [0], Integer.parseInt(parts [1]), parts [2]); 200 } 201 202 if (parts.length > 3) { 203 // The quorum could contain client port in server:clientport format, try to transform more. 204 String zNodeParent = parts [parts.length - 1]; 205 if (!zNodeParent.matches("/.*[^/]")) { 206 throw new IOException("Cluster key passed " + key + " is invalid, the format should be:" 207 + HConstants.ZOOKEEPER_QUORUM + ":" + HConstants.ZOOKEEPER_CLIENT_PORT + ":" 208 + HConstants.ZOOKEEPER_ZNODE_PARENT); 209 } 210 211 String clientPort = parts [parts.length - 2]; 212 213 // The first part length is the total length minus the lengths of other parts and minus 2 ":" 214 int endQuorumIndex = key.length() - zNodeParent.length() - clientPort.length() - 2; 215 String quorumStringInput = key.substring(0, endQuorumIndex); 216 String[] serverHosts = quorumStringInput.split(","); 217 218 // The common case is that every server has its own client port specified - this means 219 // that (total parts - the ZNodeParent part - the ClientPort part) is equal to 220 // (the number of "," + 1) - "+ 1" because the last server has no ",". 221 if ((parts.length - 2) == (serverHosts.length + 1)) { 222 return new ZKClusterKey(quorumStringInput, Integer.parseInt(clientPort), zNodeParent); 223 } 224 225 // For the uncommon case that some servers has no port specified, we need to build the 226 // server:clientport list using default client port for servers without specified port. 227 return new ZKClusterKey( 228 buildZKQuorumServerString(serverHosts, clientPort), 229 Integer.parseInt(clientPort), 230 zNodeParent); 231 } 232 233 throw new IOException("Cluster key passed " + key + " is invalid, the format should be:" + 234 HConstants.ZOOKEEPER_QUORUM + ":" + HConstants.ZOOKEEPER_CLIENT_PORT + ":" 235 + HConstants.ZOOKEEPER_ZNODE_PARENT); 236 } 237 238 /** 239 * Get the key to the ZK ensemble for this configuration without 240 * adding a name at the end 241 * @param conf Configuration to use to build the key 242 * @return ensemble key without a name 243 */ 244 public static String getZooKeeperClusterKey(Configuration conf) { 245 return getZooKeeperClusterKey(conf, null); 246 } 247 248 /** 249 * Get the key to the ZK ensemble for this configuration and append 250 * a name at the end 251 * @param conf Configuration to use to build the key 252 * @param name Name that should be appended at the end if not empty or null 253 * @return ensemble key with a name (if any) 254 */ 255 public static String getZooKeeperClusterKey(Configuration conf, String name) { 256 String ensemble = conf.get(HConstants.ZOOKEEPER_QUORUM).replaceAll( 257 "[\\t\\n\\x0B\\f\\r]", ""); 258 StringBuilder builder = new StringBuilder(ensemble); 259 builder.append(":"); 260 builder.append(conf.get(HConstants.ZOOKEEPER_CLIENT_PORT)); 261 builder.append(":"); 262 builder.append(conf.get(HConstants.ZOOKEEPER_ZNODE_PARENT)); 263 if (name != null && !name.isEmpty()) { 264 builder.append(","); 265 builder.append(name); 266 } 267 return builder.toString(); 268 } 269 270 /** 271 * Standardize the ZK quorum string: make it a "server:clientport" list, separated by ',' 272 * @param quorumStringInput a string contains a list of servers for ZK quorum 273 * @param clientPort the default client port 274 * @return the string for a list of "server:port" separated by "," 275 */ 276 @VisibleForTesting 277 public static String standardizeZKQuorumServerString(String quorumStringInput, 278 String clientPort) { 279 String[] serverHosts = quorumStringInput.split(","); 280 return buildZKQuorumServerString(serverHosts, clientPort); 281 } 282 283 // The Quorum for the ZK cluster can have one the following format (see examples below): 284 // (1). s1,s2,s3 (no client port in the list, the client port could be obtained from clientPort) 285 // (2). s1:p1,s2:p2,s3:p3 (with client port, which could be same or different for each server, 286 // in this case, the clientPort would be ignored) 287 // (3). s1:p1,s2,s3:p3 (mix of (1) and (2) - if port is not specified in a server, it would use 288 // the clientPort; otherwise, it would use the specified port) 289 @VisibleForTesting 290 public static class ZKClusterKey { 291 private String quorumString; 292 private int clientPort; 293 private String znodeParent; 294 295 ZKClusterKey(String quorumString, int clientPort, String znodeParent) { 296 this.quorumString = quorumString; 297 this.clientPort = clientPort; 298 this.znodeParent = znodeParent; 299 } 300 301 public String getQuorumString() { 302 return quorumString; 303 } 304 305 public int getClientPort() { 306 return clientPort; 307 } 308 309 public String getZnodeParent() { 310 return znodeParent; 311 } 312 } 313}