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