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