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