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