View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.zookeeper;
20  
21  import java.io.IOException;
22  import java.util.Map.Entry;
23  import java.util.Properties;
24  
25  import com.google.common.annotations.VisibleForTesting;
26  import org.apache.hadoop.conf.Configuration;
27  import org.apache.hadoop.hbase.HConstants;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  
30  /**
31   * Utility methods for reading, and building the ZooKeeper configuration.
32   *
33   * The order and priority for reading the config are as follows:
34   * (1). Property with "hbase.zookeeper.property." prefix from HBase XML
35   * (2). other zookeeper related properties in HBASE XML
36   */
37  @InterfaceAudience.Private
38  public final class ZKConfig {
39  
40    private static final String VARIABLE_START = "${";
41  
42    private ZKConfig() {
43    }
44  
45    /**
46     * Make a Properties object holding ZooKeeper config.
47     * Parses the corresponding config options from the HBase XML configs
48     * and generates the appropriate ZooKeeper properties.
49     * @param conf Configuration to read from.
50     * @return Properties holding mappings representing ZooKeeper config file.
51     */
52    public static Properties makeZKProps(Configuration conf) {
53      return makeZKPropsFromHbaseConfig(conf);
54    }
55  
56    /**
57     * Make a Properties object holding ZooKeeper config.
58     * Parses the corresponding config options from the HBase XML configs
59     * and generates the appropriate ZooKeeper properties.
60     *
61     * @param conf Configuration to read from.
62     * @return Properties holding mappings representing ZooKeeper config file.
63     */
64    private static Properties makeZKPropsFromHbaseConfig(Configuration conf) {
65      Properties zkProperties = new Properties();
66  
67      // Directly map all of the hbase.zookeeper.property.KEY properties.
68      // Synchronize on conf so no loading of configs while we iterate
69      synchronized (conf) {
70        for (Entry<String, String> entry : conf) {
71          String key = entry.getKey();
72          if (key.startsWith(HConstants.ZK_CFG_PROPERTY_PREFIX)) {
73            String zkKey = key.substring(HConstants.ZK_CFG_PROPERTY_PREFIX_LEN);
74            String value = entry.getValue();
75            // If the value has variables substitutions, need to do a get.
76            if (value.contains(VARIABLE_START)) {
77              value = conf.get(key);
78            }
79            zkProperties.setProperty(zkKey, value);
80          }
81        }
82      }
83  
84      // If clientPort is not set, assign the default.
85      if (zkProperties.getProperty(HConstants.CLIENT_PORT_STR) == null) {
86        zkProperties.put(HConstants.CLIENT_PORT_STR,
87            HConstants.DEFAULT_ZOOKEPER_CLIENT_PORT);
88      }
89  
90      // Create the server.X properties.
91      int peerPort = conf.getInt("hbase.zookeeper.peerport", 2888);
92      int leaderPort = conf.getInt("hbase.zookeeper.leaderport", 3888);
93  
94      final String[] serverHosts = conf.getStrings(HConstants.ZOOKEEPER_QUORUM,
95                                                   HConstants.LOCALHOST);
96      String serverHost;
97      String address;
98      String key;
99      for (int i = 0; i < serverHosts.length; ++i) {
100       if (serverHosts[i].contains(":")) {
101         serverHost = serverHosts[i].substring(0, serverHosts[i].indexOf(':'));
102       } else {
103         serverHost = serverHosts[i];
104       }
105       address = serverHost + ":" + peerPort + ":" + leaderPort;
106       key = "server." + i;
107       zkProperties.put(key, address);
108     }
109 
110     return zkProperties;
111   }
112 
113   /**
114    * Return the ZK Quorum servers string given the specified configuration
115    *
116    * @param conf
117    * @return Quorum servers String
118    */
119   private static String getZKQuorumServersStringFromHbaseConfig(Configuration conf) {
120     String defaultClientPort = Integer.toString(
121         conf.getInt(HConstants.ZOOKEEPER_CLIENT_PORT, HConstants.DEFAULT_ZOOKEPER_CLIENT_PORT));
122 
123     // Build the ZK quorum server string with "server:clientport" list, separated by ','
124     final String[] serverHosts =
125         conf.getStrings(HConstants.ZOOKEEPER_QUORUM, HConstants.LOCALHOST);
126     return buildZKQuorumServerString(serverHosts, defaultClientPort);
127   }
128 
129   /**
130    * Return the ZK Quorum servers string given the specified configuration.
131    * @return Quorum servers
132    */
133   public static String getZKQuorumServersString(Configuration conf) {
134     return getZKQuorumServersStringFromHbaseConfig(conf);
135   }
136 
137   /**
138    * Build the ZK quorum server string with "server:clientport" list, separated by ','
139    *
140    * @param serverHosts a list of servers for ZK quorum
141    * @param clientPort the default client port
142    * @return the string for a list of "server:port" separated by ","
143    */
144   public static String buildZKQuorumServerString(String[] serverHosts, String clientPort) {
145     StringBuilder quorumStringBuilder = new StringBuilder();
146     String serverHost;
147     for (int i = 0; i < serverHosts.length; ++i) {
148       if (serverHosts[i].contains(":")) {
149         serverHost = serverHosts[i]; // just use the port specified from the input
150       } else {
151         serverHost = serverHosts[i] + ":" + clientPort;
152       }
153       if (i > 0) {
154         quorumStringBuilder.append(',');
155       }
156       quorumStringBuilder.append(serverHost);
157     }
158     return quorumStringBuilder.toString();
159   }
160 
161   /**
162    * Verifies that the given key matches the expected format for a ZooKeeper cluster key.
163    * The Quorum for the ZK cluster can have one the following formats (see examples below):
164    *
165    * <ol>
166    *   <li>s1,s2,s3 (no client port in the list, the client port could be obtained from
167    *       clientPort)</li>
168    *   <li>s1:p1,s2:p2,s3:p3 (with client port, which could be same or different for each server,
169    *       in this case, the clientPort would be ignored)</li>
170    *   <li>s1:p1,s2,s3:p3 (mix of (1) and (2) - if port is not specified in a server, it would use
171    *       the clientPort; otherwise, it would use the specified port)</li>
172    * </ol>
173    *
174    * @param key the cluster key to validate
175    * @throws IOException if the key could not be parsed
176    */
177   public static void validateClusterKey(String key) throws IOException {
178     transformClusterKey(key);
179   }
180 
181   /**
182    * Separate the given key into the three configurations it should contain:
183    * hbase.zookeeper.quorum, hbase.zookeeper.client.port
184    * and zookeeper.znode.parent
185    * @param key
186    * @return the three configuration in the described order
187    * @throws IOException
188    */
189   public static ZKClusterKey transformClusterKey(String key) throws IOException {
190     String[] parts = key.split(":");
191 
192     if (parts.length == 3) {
193       return new ZKClusterKey(parts [0], Integer.parseInt(parts [1]), parts [2]);
194     }
195 
196     if (parts.length > 3) {
197       // The quorum could contain client port in server:clientport format, try to transform more.
198       String zNodeParent = parts [parts.length - 1];
199       String clientPort = parts [parts.length - 2];
200 
201       // The first part length is the total length minus the lengths of other parts and minus 2 ":"
202       int endQuorumIndex = key.length() - zNodeParent.length() - clientPort.length() - 2;
203       String quorumStringInput = key.substring(0, endQuorumIndex);
204       String[] serverHosts = quorumStringInput.split(",");
205 
206       // The common case is that every server has its own client port specified - this means
207       // that (total parts - the ZNodeParent part - the ClientPort part) is equal to
208       // (the number of "," + 1) - "+ 1" because the last server has no ",".
209       if ((parts.length - 2) == (serverHosts.length + 1)) {
210         return new ZKClusterKey(quorumStringInput, Integer.parseInt(clientPort), zNodeParent);
211       }
212 
213       // For the uncommon case that some servers has no port specified, we need to build the
214       // server:clientport list using default client port for servers without specified port.
215       return new ZKClusterKey(
216           buildZKQuorumServerString(serverHosts, clientPort),
217           Integer.parseInt(clientPort),
218           zNodeParent);
219     }
220 
221     throw new IOException("Cluster key passed " + key + " is invalid, the format should be:" +
222         HConstants.ZOOKEEPER_QUORUM + ":" + HConstants.ZOOKEEPER_CLIENT_PORT + ":"
223         + HConstants.ZOOKEEPER_ZNODE_PARENT);
224   }
225 
226   /**
227    * Get the key to the ZK ensemble for this configuration without
228    * adding a name at the end
229    * @param conf Configuration to use to build the key
230    * @return ensemble key without a name
231    */
232   public static String getZooKeeperClusterKey(Configuration conf) {
233     return getZooKeeperClusterKey(conf, null);
234   }
235 
236   /**
237    * Get the key to the ZK ensemble for this configuration and append
238    * a name at the end
239    * @param conf Configuration to use to build the key
240    * @param name Name that should be appended at the end if not empty or null
241    * @return ensemble key with a name (if any)
242    */
243   public static String getZooKeeperClusterKey(Configuration conf, String name) {
244     String ensemble = conf.get(HConstants.ZOOKEEPER_QUORUM).replaceAll(
245         "[\\t\\n\\x0B\\f\\r]", "");
246     StringBuilder builder = new StringBuilder(ensemble);
247     builder.append(":");
248     builder.append(conf.get(HConstants.ZOOKEEPER_CLIENT_PORT));
249     builder.append(":");
250     builder.append(conf.get(HConstants.ZOOKEEPER_ZNODE_PARENT));
251     if (name != null && !name.isEmpty()) {
252       builder.append(",");
253       builder.append(name);
254     }
255     return builder.toString();
256   }
257 
258   /**
259    * Standardize the ZK quorum string: make it a "server:clientport" list, separated by ','
260    * @param quorumStringInput a string contains a list of servers for ZK quorum
261    * @param clientPort the default client port
262    * @return the string for a list of "server:port" separated by ","
263    */
264   @VisibleForTesting
265   public static String standardizeZKQuorumServerString(String quorumStringInput,
266       String clientPort) {
267     String[] serverHosts = quorumStringInput.split(",");
268     return buildZKQuorumServerString(serverHosts, clientPort);
269   }
270 
271   // The Quorum for the ZK cluster can have one the following format (see examples below):
272   // (1). s1,s2,s3 (no client port in the list, the client port could be obtained from clientPort)
273   // (2). s1:p1,s2:p2,s3:p3 (with client port, which could be same or different for each server,
274   //      in this case, the clientPort would be ignored)
275   // (3). s1:p1,s2,s3:p3 (mix of (1) and (2) - if port is not specified in a server, it would use
276   //      the clientPort; otherwise, it would use the specified port)
277   @VisibleForTesting
278   public static class ZKClusterKey {
279     private String quorumString;
280     private int clientPort;
281     private String znodeParent;
282 
283     ZKClusterKey(String quorumString, int clientPort, String znodeParent) {
284       this.quorumString = quorumString;
285       this.clientPort = clientPort;
286       this.znodeParent = znodeParent;
287     }
288 
289     public String getQuorumString() {
290       return quorumString;
291     }
292 
293     public int getClientPort() {
294       return clientPort;
295     }
296 
297     public String getZnodeParent() {
298       return znodeParent;
299     }
300   }
301 }