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 com.google.common.annotations.VisibleForTesting;
22  
23  import java.io.BufferedReader;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStreamReader;
27  import java.io.InterruptedIOException;
28  import java.io.OutputStream;
29  import java.io.Reader;
30  import java.net.BindException;
31  import java.net.InetSocketAddress;
32  import java.net.Socket;
33  import java.nio.charset.StandardCharsets;
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.Random;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.conf.Configuration;
41  import org.apache.hadoop.hbase.HConstants;
42  import org.apache.hadoop.hbase.classification.InterfaceAudience;
43  import org.apache.hadoop.hbase.classification.InterfaceStability;
44  import org.apache.hadoop.hbase.util.Bytes;
45  import org.apache.zookeeper.server.NIOServerCnxnFactory;
46  import org.apache.zookeeper.server.ZooKeeperServer;
47  import org.apache.zookeeper.server.persistence.FileTxnLog;
48  
49  /**
50   * TODO: Most of the code in this class is ripped from ZooKeeper tests. Instead
51   * of redoing it, we should contribute updates to their code which let us more
52   * easily access testing helper objects.
53   */
54  @InterfaceAudience.Public
55  @InterfaceStability.Evolving
56  public class MiniZooKeeperCluster {
57    private static final Log LOG = LogFactory.getLog(MiniZooKeeperCluster.class);
58  
59    private static final int TICK_TIME = 2000;
60    private static final int DEFAULT_CONNECTION_TIMEOUT = 30000;
61    private int connectionTimeout;
62  
63    private boolean started;
64  
65    /** The default port. If zero, we use a random port. */
66    private int defaultClientPort = 0;
67  
68    private List<NIOServerCnxnFactory> standaloneServerFactoryList;
69    private List<ZooKeeperServer> zooKeeperServers;
70    private List<Integer> clientPortList;
71  
72    private int activeZKServerIndex;
73    private int tickTime = 0;
74  
75    private Configuration configuration;
76  
77    public MiniZooKeeperCluster() {
78      this(new Configuration());
79    }
80  
81    public MiniZooKeeperCluster(Configuration configuration) {
82      this.started = false;
83      this.configuration = configuration;
84      activeZKServerIndex = -1;
85      zooKeeperServers = new ArrayList<ZooKeeperServer>();
86      clientPortList = new ArrayList<Integer>();
87      standaloneServerFactoryList = new ArrayList<NIOServerCnxnFactory>();
88      connectionTimeout = configuration.getInt(HConstants.ZK_SESSION_TIMEOUT + ".localHBaseCluster",
89        DEFAULT_CONNECTION_TIMEOUT);
90    }
91  
92    /**
93     * Add a client port to the list.
94     *
95     * @param clientPort the specified port
96     */
97    public void addClientPort(int clientPort) {
98      clientPortList.add(clientPort);
99    }
100 
101   /**
102    * Get the list of client ports.
103    * @return clientPortList the client port list
104    */
105   @VisibleForTesting
106   public List<Integer> getClientPortList() {
107     return clientPortList;
108   }
109 
110   /**
111    * Check whether the client port in a specific position of the client port list is valid.
112    *
113    * @param index the specified position
114    */
115   private boolean hasValidClientPortInList(int index) {
116     return (clientPortList.size() > index && clientPortList.get(index) > 0);
117   }
118 
119   public void setDefaultClientPort(int clientPort) {
120     if (clientPort <= 0) {
121       throw new IllegalArgumentException("Invalid default ZK client port: "
122           + clientPort);
123     }
124     this.defaultClientPort = clientPort;
125   }
126 
127   /**
128    * Selects a ZK client port.
129    *
130    * @param seedPort the seed port to start with; -1 means first time.
131    * @Returns a valid and unused client port
132    */
133   private int selectClientPort(int seedPort) {
134     int i;
135     int returnClientPort = seedPort + 1;
136     if (returnClientPort == 0) {
137       // If the new port is invalid, find one - starting with the default client port.
138       // If the default client port is not specified, starting with a random port.
139       // The random port is selected from the range between 49152 to 65535. These ports cannot be
140       // registered with IANA and are intended for dynamic allocation (see http://bit.ly/dynports).
141       if (defaultClientPort > 0) {
142         returnClientPort = defaultClientPort;
143       } else {
144         returnClientPort = 0xc000 + new Random().nextInt(0x3f00);
145       }
146     }
147     // Make sure that the port is unused.
148     while (true) {
149       for (i = 0; i < clientPortList.size(); i++) {
150         if (returnClientPort == clientPortList.get(i)) {
151           // Already used. Update the port and retry.
152           returnClientPort++;
153           break;
154         }
155       }
156       if (i == clientPortList.size()) {
157         break; // found a unused port, exit
158       }
159     }
160     return returnClientPort;
161   }
162 
163   public void setTickTime(int tickTime) {
164     this.tickTime = tickTime;
165   }
166 
167   public int getBackupZooKeeperServerNum() {
168     return zooKeeperServers.size()-1;
169   }
170 
171   public int getZooKeeperServerNum() {
172     return zooKeeperServers.size();
173   }
174 
175   // / XXX: From o.a.zk.t.ClientBase
176   private static void setupTestEnv() {
177     // during the tests we run with 100K prealloc in the logs.
178     // on windows systems prealloc of 64M was seen to take ~15seconds
179     // resulting in test failure (client timeout on first session).
180     // set env and directly in order to handle static init/gc issues
181     System.setProperty("zookeeper.preAllocSize", "100");
182     FileTxnLog.setPreallocSize(100 * 1024);
183     // allow all 4 letter words
184     System.setProperty("zookeeper.4lw.commands.whitelist","*");
185   }
186 
187   public int startup(File baseDir) throws IOException, InterruptedException {
188     int numZooKeeperServers = clientPortList.size();
189     if (numZooKeeperServers == 0) {
190       numZooKeeperServers = 1; // need at least 1 ZK server for testing
191     }
192     return startup(baseDir, numZooKeeperServers);
193   }
194 
195   /**
196    * @param baseDir
197    * @param numZooKeeperServers
198    * @return ClientPort server bound to, -1 if there was a
199    *         binding problem and we couldn't pick another port.
200    * @throws IOException
201    * @throws InterruptedException
202    */
203   public int startup(File baseDir, int numZooKeeperServers) throws IOException,
204       InterruptedException {
205     if (numZooKeeperServers <= 0)
206       return -1;
207 
208     setupTestEnv();
209     shutdown();
210 
211     int tentativePort = -1; // the seed port
212     int currentClientPort;
213 
214     // running all the ZK servers
215     for (int i = 0; i < numZooKeeperServers; i++) {
216       File dir = new File(baseDir, "zookeeper_"+i).getAbsoluteFile();
217       createDir(dir);
218       int tickTimeToUse;
219       if (this.tickTime > 0) {
220         tickTimeToUse = this.tickTime;
221       } else {
222         tickTimeToUse = TICK_TIME;
223       }
224 
225       // Set up client port - if we have already had a list of valid ports, use it.
226       if (hasValidClientPortInList(i)) {
227         currentClientPort = clientPortList.get(i);
228       } else {
229         tentativePort = selectClientPort(tentativePort); // update the seed
230         currentClientPort = tentativePort;
231       }
232 
233       ZooKeeperServer server = new ZooKeeperServer(dir, dir, tickTimeToUse);
234       // Setting {min,max}SessionTimeout defaults to be the same as in Zookeeper
235       server.setMinSessionTimeout(configuration.getInt("hbase.zookeeper.property.minSessionTimeout", -1));
236       server.setMaxSessionTimeout(configuration.getInt("hbase.zookeeper.property.maxSessionTimeout", -1));
237       NIOServerCnxnFactory standaloneServerFactory;
238       while (true) {
239         try {
240           standaloneServerFactory = new NIOServerCnxnFactory();
241           standaloneServerFactory.configure(
242             new InetSocketAddress(currentClientPort),
243             configuration.getInt(HConstants.ZOOKEEPER_MAX_CLIENT_CNXNS, 1000));
244         } catch (BindException e) {
245           LOG.debug("Failed binding ZK Server to client port: " +
246               currentClientPort, e);
247           // We're told to use some port but it's occupied, fail
248           if (hasValidClientPortInList(i)) {
249             return -1;
250           }
251           // This port is already in use, try to use another.
252           tentativePort = selectClientPort(tentativePort);
253           currentClientPort = tentativePort;
254           continue;
255         }
256         break;
257       }
258 
259       // Start up this ZK server
260       standaloneServerFactory.startup(server);
261       // Runs a 'stat' against the servers.
262       if (!waitForServerUp(currentClientPort, connectionTimeout)) {
263         throw new IOException("Waiting for startup of standalone server");
264       }
265 
266       // We have selected a port as a client port.  Update clientPortList if necessary.
267       if (clientPortList.size() <= i) { // it is not in the list, add the port
268         clientPortList.add(currentClientPort);
269       }
270       else if (clientPortList.get(i) <= 0) { // the list has invalid port, update with valid port
271         clientPortList.remove(i);
272         clientPortList.add(i, currentClientPort);
273       }
274 
275       standaloneServerFactoryList.add(standaloneServerFactory);
276       zooKeeperServers.add(server);
277     }
278 
279     // set the first one to be active ZK; Others are backups
280     activeZKServerIndex = 0;
281     started = true;
282     int clientPort = clientPortList.get(activeZKServerIndex);
283     LOG.info("Started MiniZooKeeperCluster and ran successful 'stat' " +
284         "on client port=" + clientPort);
285     return clientPort;
286   }
287 
288   private void createDir(File dir) throws IOException {
289     try {
290       if (!dir.exists()) {
291         dir.mkdirs();
292       }
293     } catch (SecurityException e) {
294       throw new IOException("creating dir: " + dir, e);
295     }
296   }
297 
298   /**
299    * @throws IOException
300    */
301   public void shutdown() throws IOException {
302     // shut down all the zk servers
303     for (int i = 0; i < standaloneServerFactoryList.size(); i++) {
304       NIOServerCnxnFactory standaloneServerFactory =
305         standaloneServerFactoryList.get(i);
306       int clientPort = clientPortList.get(i);
307 
308       standaloneServerFactory.shutdown();
309       if (!waitForServerDown(clientPort, connectionTimeout)) {
310         throw new IOException("Waiting for shutdown of standalone server");
311       }
312     }
313     standaloneServerFactoryList.clear();
314 
315     for (ZooKeeperServer zkServer: zooKeeperServers) {
316       //explicitly close ZKDatabase since ZookeeperServer does not close them
317       zkServer.getZKDatabase().close();
318     }
319     zooKeeperServers.clear();
320 
321     // clear everything
322     if (started) {
323       started = false;
324       activeZKServerIndex = 0;
325       clientPortList.clear();
326       LOG.info("Shutdown MiniZK cluster with all ZK servers");
327     }
328   }
329 
330   /**@return clientPort return clientPort if there is another ZK backup can run
331    *         when killing the current active; return -1, if there is no backups.
332    * @throws IOException
333    * @throws InterruptedException
334    */
335   public int killCurrentActiveZooKeeperServer() throws IOException,
336                                         InterruptedException {
337     if (!started || activeZKServerIndex < 0) {
338       return -1;
339     }
340 
341     // Shutdown the current active one
342     NIOServerCnxnFactory standaloneServerFactory =
343       standaloneServerFactoryList.get(activeZKServerIndex);
344     int clientPort = clientPortList.get(activeZKServerIndex);
345 
346     standaloneServerFactory.shutdown();
347     if (!waitForServerDown(clientPort, connectionTimeout)) {
348       throw new IOException("Waiting for shutdown of standalone server");
349     }
350 
351     zooKeeperServers.get(activeZKServerIndex).getZKDatabase().close();
352 
353     // remove the current active zk server
354     standaloneServerFactoryList.remove(activeZKServerIndex);
355     clientPortList.remove(activeZKServerIndex);
356     zooKeeperServers.remove(activeZKServerIndex);
357     LOG.info("Kill the current active ZK servers in the cluster " +
358         "on client port: " + clientPort);
359 
360     if (standaloneServerFactoryList.size() == 0) {
361       // there is no backup servers;
362       return -1;
363     }
364     clientPort = clientPortList.get(activeZKServerIndex);
365     LOG.info("Activate a backup zk server in the cluster " +
366         "on client port: " + clientPort);
367     // return the next back zk server's port
368     return clientPort;
369   }
370 
371   /**
372    * Kill one back up ZK servers
373    * @throws IOException
374    * @throws InterruptedException
375    */
376   public void killOneBackupZooKeeperServer() throws IOException,
377                                         InterruptedException {
378     if (!started || activeZKServerIndex < 0 ||
379         standaloneServerFactoryList.size() <= 1) {
380       return ;
381     }
382 
383     int backupZKServerIndex = activeZKServerIndex+1;
384     // Shutdown the current active one
385     NIOServerCnxnFactory standaloneServerFactory =
386       standaloneServerFactoryList.get(backupZKServerIndex);
387     int clientPort = clientPortList.get(backupZKServerIndex);
388 
389     standaloneServerFactory.shutdown();
390     if (!waitForServerDown(clientPort, connectionTimeout)) {
391       throw new IOException("Waiting for shutdown of standalone server");
392     }
393 
394     zooKeeperServers.get(backupZKServerIndex).getZKDatabase().close();
395 
396     // remove this backup zk server
397     standaloneServerFactoryList.remove(backupZKServerIndex);
398     clientPortList.remove(backupZKServerIndex);
399     zooKeeperServers.remove(backupZKServerIndex);
400     LOG.info("Kill one backup ZK servers in the cluster " +
401         "on client port: " + clientPort);
402   }
403 
404   // XXX: From o.a.zk.t.ClientBase
405   private static boolean waitForServerDown(int port, long timeout) throws IOException {
406     long start = System.currentTimeMillis();
407     while (true) {
408       try {
409         Socket sock = new Socket("localhost", port);
410         try {
411           OutputStream outstream = sock.getOutputStream();
412           outstream.write(Bytes.toBytes("stat"));
413           outstream.flush();
414         } finally {
415           sock.close();
416         }
417       } catch (IOException e) {
418         return true;
419       }
420 
421       if (System.currentTimeMillis() > start + timeout) {
422         break;
423       }
424       try {
425         Thread.sleep(250);
426       } catch (InterruptedException e) {
427         throw (InterruptedIOException)new InterruptedIOException().initCause(e);
428       }
429     }
430     return false;
431   }
432 
433   // XXX: From o.a.zk.t.ClientBase
434   private static boolean waitForServerUp(int port, long timeout) throws IOException {
435     long start = System.currentTimeMillis();
436     while (true) {
437       try {
438         Socket sock = new Socket("localhost", port);
439         BufferedReader reader = null;
440         try {
441           OutputStream outstream = sock.getOutputStream();
442           outstream.write(Bytes.toBytes("stat"));
443           outstream.flush();
444 
445           Reader isr = new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8);
446           reader = new BufferedReader(isr);
447           String line = reader.readLine();
448           if (line != null && line.startsWith("Zookeeper version:")) {
449             return true;
450           }
451         } finally {
452           sock.close();
453           if (reader != null) {
454             reader.close();
455           }
456         }
457       } catch (IOException e) {
458         // ignore as this is expected
459         LOG.info("server localhost:" + port + " not up " + e);
460       }
461 
462       if (System.currentTimeMillis() > start + timeout) {
463         break;
464       }
465       try {
466         Thread.sleep(250);
467       } catch (InterruptedException e) {
468         throw (InterruptedIOException)new InterruptedIOException().initCause(e);
469       }
470     }
471     return false;
472   }
473 
474   public int getClientPort() {
475     return activeZKServerIndex < 0 || activeZKServerIndex >= clientPortList.size() ? -1
476         : clientPortList.get(activeZKServerIndex);
477   }
478 }