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