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; 029 030import org.apache.hbase.thirdparty.com.google.common.base.Splitter; 031 032/** 033 * Utility methods for reading, and building the ZooKeeper configuration. The order and priority for 034 * reading the config are as follows: (1). Property with "hbase.zookeeper.property." prefix from 035 * HBase XML (2). other zookeeper related properties in HBASE XML 036 */ 037@InterfaceAudience.Private 038public final class ZKConfig { 039 040 private static final String VARIABLE_START = "${"; 041 private static final String ZOOKEEPER_JAVA_PROPERTY_PREFIX = "zookeeper."; 042 043 private ZKConfig() { 044 } 045 046 /** 047 * Make a Properties object holding ZooKeeper config. Parses the corresponding config options from 048 * the HBase XML configs and generates the appropriate ZooKeeper properties. 049 * @param conf Configuration to read from. 050 * @return Properties holding mappings representing ZooKeeper config file. 051 */ 052 public static Properties makeZKProps(Configuration conf) { 053 return makeZKPropsFromHbaseConfig(conf); 054 } 055 056 /** 057 * Directly map all the hbase.zookeeper.property.KEY properties. Synchronize on conf so no loading 058 * of configs while we iterate 059 */ 060 private static Properties extractZKPropsFromHBaseConfig(final Configuration conf) { 061 Properties zkProperties = new Properties(); 062 063 synchronized (conf) { 064 for (Entry<String, String> entry : conf) { 065 String key = entry.getKey(); 066 if (key.startsWith(HConstants.ZK_CFG_PROPERTY_PREFIX)) { 067 String zkKey = key.substring(HConstants.ZK_CFG_PROPERTY_PREFIX_LEN); 068 String value = entry.getValue(); 069 // If the value has variables substitutions, need to do a get. 070 if (value.contains(VARIABLE_START)) { 071 value = conf.get(key); 072 } 073 zkProperties.setProperty(zkKey, value); 074 } 075 } 076 } 077 078 return zkProperties; 079 } 080 081 /** 082 * Make a Properties object holding ZooKeeper config. Parses the corresponding config options from 083 * the HBase XML configs and generates the appropriate ZooKeeper properties. 084 * @param conf Configuration to read from. 085 * @return Properties holding mappings representing ZooKeeper config file. 086 */ 087 private static Properties makeZKPropsFromHbaseConfig(Configuration conf) { 088 Properties zkProperties = extractZKPropsFromHBaseConfig(conf); 089 090 // If clientPort is not set, assign the default. 091 if (zkProperties.getProperty(HConstants.CLIENT_PORT_STR) == null) { 092 zkProperties.put(HConstants.CLIENT_PORT_STR, HConstants.DEFAULT_ZOOKEEPER_CLIENT_PORT); 093 } 094 095 // Create the server.X properties. 096 int peerPort = conf.getInt("hbase.zookeeper.peerport", 2888); 097 int leaderPort = conf.getInt("hbase.zookeeper.leaderport", 3888); 098 099 final String[] serverHosts = conf.getStrings(HConstants.ZOOKEEPER_QUORUM, HConstants.LOCALHOST); 100 String serverHost; 101 String address; 102 String key; 103 for (int i = 0; i < serverHosts.length; ++i) { 104 if (serverHosts[i].contains(":")) { 105 serverHost = serverHosts[i].substring(0, serverHosts[i].indexOf(':')); 106 } else { 107 serverHost = serverHosts[i]; 108 } 109 address = serverHost + ":" + peerPort + ":" + leaderPort; 110 key = "server." + i; 111 zkProperties.put(key, address); 112 } 113 114 return zkProperties; 115 } 116 117 /** 118 * Return the ZK Quorum servers string given the specified configuration 119 * @return Quorum servers String 120 */ 121 private static String getZKQuorumServersStringFromHbaseConfig(Configuration conf) { 122 String defaultClientPort = Integer.toString( 123 conf.getInt(HConstants.ZOOKEEPER_CLIENT_PORT, HConstants.DEFAULT_ZOOKEEPER_CLIENT_PORT)); 124 125 // Build the ZK quorum server string with "server:clientport" list, separated by ',' 126 final String[] serverHosts = 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 setZooKeeperClientSystemProperties(HConstants.ZK_CFG_PROPERTY_PREFIX, conf); 136 return getZKQuorumServersStringFromHbaseConfig(conf); 137 } 138 139 /** 140 * Build the ZK quorum server string with "server:clientport" list, separated by ',' 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 InetAddressValidator validator = new InetAddressValidator(); 149 for (int i = 0; i < serverHosts.length; ++i) { 150 if (serverHosts[i].startsWith("[")) { 151 int index = serverHosts[i].indexOf("]"); 152 if (index < 0) { 153 throw new IllegalArgumentException( 154 serverHosts[i] + " starts with '[' but has no matching ']:'"); 155 } 156 if (index + 2 == serverHosts[i].length()) { 157 throw new IllegalArgumentException(serverHosts[i] + " doesn't have a port after colon"); 158 } 159 // check the IPv6 address e.g. [2001:db8::1] 160 String serverHostWithoutBracket = serverHosts[i].substring(1, index); 161 if (!validator.isValidInet6Address(serverHostWithoutBracket)) { 162 throw new IllegalArgumentException(serverHosts[i] + " is not a valid IPv6 address"); 163 } 164 serverHost = serverHosts[i]; 165 if ((index + 1 == serverHosts[i].length())) { 166 serverHost = serverHosts[i] + ":" + clientPort; 167 } 168 } else { 169 if (serverHosts[i].contains(":")) { 170 serverHost = serverHosts[i]; // just use the port specified from the input 171 } else { 172 serverHost = serverHosts[i] + ":" + clientPort; 173 } 174 } 175 176 if (i > 0) { 177 quorumStringBuilder.append(','); 178 } 179 quorumStringBuilder.append(serverHost); 180 } 181 return quorumStringBuilder.toString(); 182 } 183 184 /** 185 * Verifies that the given key matches the expected format for a ZooKeeper cluster key. The Quorum 186 * for the ZK cluster can have one the following formats (see examples below): 187 * <ol> 188 * <li>s1,s2,s3 (no client port in the list, the client port could be obtained from 189 * clientPort)</li> 190 * <li>s1:p1,s2:p2,s3:p3 (with client port, which could be same or different for each server, in 191 * this case, the clientPort would be ignored)</li> 192 * <li>s1:p1,s2,s3:p3 (mix of (1) and (2) - if port is not specified in a server, it would use the 193 * clientPort; otherwise, it would use the specified port)</li> 194 * </ol> 195 * @param key the cluster key to validate 196 * @throws IOException if the key could not be parsed 197 */ 198 public static void validateClusterKey(String key) throws IOException { 199 transformClusterKey(key); 200 } 201 202 /** 203 * Separate the given key into the three configurations it should contain: hbase.zookeeper.quorum, 204 * hbase.zookeeper.client.port and zookeeper.znode.parent 205 * @return the three configuration in the described order 206 */ 207 public static ZKClusterKey transformClusterKey(String key) throws IOException { 208 List<String> parts = Splitter.on(':').splitToList(key); 209 String[] partsArray = parts.toArray(new String[parts.size()]); 210 211 if (partsArray.length == 3) { 212 if (!partsArray[2].matches("/.*[^/]")) { 213 throw new IOException("Cluster key passed " + key + " is invalid, the format should be:" 214 + HConstants.ZOOKEEPER_QUORUM + ":" + HConstants.ZOOKEEPER_CLIENT_PORT + ":" 215 + HConstants.ZOOKEEPER_ZNODE_PARENT); 216 } 217 return new ZKClusterKey(partsArray[0], Integer.parseInt(partsArray[1]), partsArray[2]); 218 } 219 220 if (partsArray.length > 3) { 221 // The quorum could contain client port in server:clientport format, try to transform more. 222 String zNodeParent = partsArray[partsArray.length - 1]; 223 if (!zNodeParent.matches("/.*[^/]")) { 224 throw new IOException("Cluster key passed " + key + " is invalid, the format should be:" 225 + HConstants.ZOOKEEPER_QUORUM + ":" + HConstants.ZOOKEEPER_CLIENT_PORT + ":" 226 + HConstants.ZOOKEEPER_ZNODE_PARENT); 227 } 228 229 String clientPort = partsArray[partsArray.length - 2]; 230 231 // The first part length is the total length minus the lengths of other parts and minus 2 ":" 232 int endQuorumIndex = key.length() - zNodeParent.length() - clientPort.length() - 2; 233 String quorumStringInput = key.substring(0, endQuorumIndex); 234 String[] serverHosts = quorumStringInput.split(","); 235 236 // The common case is that every server has its own client port specified - this means 237 // that (total parts - the ZNodeParent part - the ClientPort part) is equal to 238 // (the number of "," + 1) - "+ 1" because the last server has no ",". 239 if ((partsArray.length - 2) == (serverHosts.length + 1)) { 240 return new ZKClusterKey(quorumStringInput, Integer.parseInt(clientPort), zNodeParent); 241 } 242 243 // For the uncommon case that some servers has no port specified, we need to build the 244 // server:clientport list using default client port for servers without specified port. 245 return new ZKClusterKey(buildZKQuorumServerString(serverHosts, clientPort), 246 Integer.parseInt(clientPort), zNodeParent); 247 } 248 249 throw new IOException("Cluster key passed " + key + " is invalid, the format should be:" 250 + HConstants.ZOOKEEPER_QUORUM + ":" + HConstants.ZOOKEEPER_CLIENT_PORT + ":" 251 + HConstants.ZOOKEEPER_ZNODE_PARENT); 252 } 253 254 /** 255 * Get the key to the ZK ensemble for this configuration without adding a name at the end 256 * @param conf Configuration to use to build the key 257 * @return ensemble key without a name 258 */ 259 public static String getZooKeeperClusterKey(Configuration conf) { 260 return getZooKeeperClusterKey(conf, null); 261 } 262 263 /** 264 * Get the key to the ZK ensemble for this configuration and append a name at the end 265 * @param conf Configuration to use to build the key 266 * @param name Name that should be appended at the end if not empty or null 267 * @return ensemble key with a name (if any) 268 */ 269 public static String getZooKeeperClusterKey(Configuration conf, String name) { 270 String ensemble = conf.get(HConstants.ZOOKEEPER_QUORUM).replaceAll("[\\t\\n\\x0B\\f\\r]", ""); 271 StringBuilder builder = new StringBuilder(ensemble); 272 builder.append(":"); 273 builder.append(conf.get(HConstants.ZOOKEEPER_CLIENT_PORT)); 274 builder.append(":"); 275 builder.append(conf.get(HConstants.ZOOKEEPER_ZNODE_PARENT)); 276 if (name != null && !name.isEmpty()) { 277 builder.append(","); 278 builder.append(name); 279 } 280 return builder.toString(); 281 } 282 283 /** 284 * Standardize the ZK quorum string: make it a "server:clientport" list, separated by ',' 285 * @param quorumStringInput a string contains a list of servers for ZK quorum 286 * @param clientPort the default client port 287 * @return the string for a list of "server:port" separated by "," 288 */ 289 public static String standardizeZKQuorumServerString(String quorumStringInput, 290 String clientPort) { 291 String[] serverHosts = quorumStringInput.split(","); 292 return buildZKQuorumServerString(serverHosts, clientPort); 293 } 294 295 // The Quorum for the ZK cluster can have one the following format (see examples below): 296 // (1). s1,s2,s3 (no client port in the list, the client port could be obtained from clientPort) 297 // (2). s1:p1,s2:p2,s3:p3 (with client port, which could be same or different for each server, 298 // in this case, the clientPort would be ignored) 299 // (3). s1:p1,s2,s3:p3 (mix of (1) and (2) - if port is not specified in a server, it would use 300 // the clientPort; otherwise, it would use the specified port) 301 public static class ZKClusterKey { 302 private String quorumString; 303 private int clientPort; 304 private String znodeParent; 305 306 ZKClusterKey(String quorumString, int clientPort, String znodeParent) { 307 this.quorumString = quorumString; 308 this.clientPort = clientPort; 309 this.znodeParent = znodeParent; 310 } 311 312 public String getQuorumString() { 313 return quorumString; 314 } 315 316 public int getClientPort() { 317 return clientPort; 318 } 319 320 public String getZnodeParent() { 321 return znodeParent; 322 } 323 } 324 325 /** 326 * Get the client ZK Quorum servers string 327 * @param conf the configuration to read 328 * @return Client quorum servers, or null if not specified 329 */ 330 public static String getClientZKQuorumServersString(Configuration conf) { 331 setZooKeeperClientSystemProperties(HConstants.ZK_CFG_PROPERTY_PREFIX, conf); 332 String clientQuromServers = conf.get(HConstants.CLIENT_ZOOKEEPER_QUORUM); 333 if (clientQuromServers == null) { 334 return null; 335 } 336 int defaultClientPort = 337 conf.getInt(HConstants.ZOOKEEPER_CLIENT_PORT, HConstants.DEFAULT_ZOOKEEPER_CLIENT_PORT); 338 String clientZkClientPort = 339 Integer.toString(conf.getInt(HConstants.CLIENT_ZOOKEEPER_CLIENT_PORT, defaultClientPort)); 340 // Build the ZK quorum server string with "server:clientport" list, separated by ',' 341 final String[] serverHosts = StringUtils.getStrings(clientQuromServers); 342 return buildZKQuorumServerString(serverHosts, clientZkClientPort); 343 } 344 345 private static void setZooKeeperClientSystemProperties(String prefix, Configuration conf) { 346 Properties zkProperties = extractZKPropsFromHBaseConfig(conf); 347 for (Entry<Object, Object> entry : zkProperties.entrySet()) { 348 String key = entry.getKey().toString().trim(); 349 String value = entry.getValue().toString().trim(); 350 if (System.getProperty(ZOOKEEPER_JAVA_PROPERTY_PREFIX + key) == null) { 351 System.setProperty(ZOOKEEPER_JAVA_PROPERTY_PREFIX + key, value); 352 } 353 } 354 } 355}