/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.zookeeper.test; import java.io.ByteArrayOutputStream; import java.io.LineNumberReader; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import junit.framework.Assert; import org.apache.log4j.Layout; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.WriterAppender; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.NotReadOnlyException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZKTestCase; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooKeeper.States; import org.apache.zookeeper.test.ClientBase.CountdownWatcher; import org.junit.After; import org.junit.Before; import org.junit.Test; public class ReadOnlyModeTest extends ZKTestCase { private static int CONNECTION_TIMEOUT = QuorumBase.CONNECTION_TIMEOUT; private QuorumUtil qu = new QuorumUtil(1); @Before public void setUp() throws Exception { qu.startQuorum(); } @After public void tearDown() throws Exception { qu.tearDown(); } /** * Basic test of read-only client functionality. Tries to read and write * during read-only mode, then regains a quorum and tries to write again. */ @Test public void testReadOnlyClient() throws Exception { CountdownWatcher watcher = new CountdownWatcher(); ZooKeeper zk = new ZooKeeper(qu.getConnString(), CONNECTION_TIMEOUT, watcher, true); watcher.waitForConnected(CONNECTION_TIMEOUT); // ensure zk got connected final String data = "Data to be read in RO mode"; final String node = "/tnode"; zk.create(node, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); watcher.reset(); qu.shutdown(2); watcher.waitForConnected(CONNECTION_TIMEOUT); // read operation during r/o mode String remoteData = new String(zk.getData(node, false, null)); Assert.assertEquals(data, remoteData); try { zk.setData(node, "no way".getBytes(), -1); Assert.fail("Write operation has succeeded during RO mode"); } catch (NotReadOnlyException e) { // ok } watcher.reset(); qu.start(2); Assert.assertTrue("waiting for server up", ClientBase.waitForServerUp( "127.0.0.1:" + qu.getPeer(2).clientPort, CONNECTION_TIMEOUT)); watcher.waitForConnected(CONNECTION_TIMEOUT); zk.setData(node, "We're in the quorum now".getBytes(), -1); zk.close(); } /** * Ensures that upon connection to a read-only server client receives * ConnectedReadOnly state notification. */ @Test public void testConnectionEvents() throws Exception { final List<KeeperState> states = new ArrayList<KeeperState>(); ZooKeeper zk = new ZooKeeper(qu.getConnString(), CONNECTION_TIMEOUT, new Watcher() { public void process(WatchedEvent event) { states.add(event.getState()); } }, true); boolean success = false; for (int i = 0; i < 30; i++) { try { zk.create("/test", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); success=true; break; } catch(KeeperException.ConnectionLossException e) { Thread.sleep(1000); } } Assert.assertTrue("Did not succeed in connecting in 30s", success); // kill peer and wait no more than 5 seconds for read-only server // to be started (which should take one tickTime (2 seconds)) qu.shutdown(2); long start = System.currentTimeMillis(); while (!(zk.getState() == States.CONNECTEDREADONLY)) { Thread.sleep(200); Assert.assertTrue("Can't connect to the server", System .currentTimeMillis() - start < 5000); } // At this point states list should contain, in the given order, // SyncConnected, Disconnected, and ConnectedReadOnly states Assert.assertTrue("ConnectedReadOnly event wasn't received", states .get(2) == KeeperState.ConnectedReadOnly); zk.close(); } /** * Tests a situation when client firstly connects to a read-only server and * then connects to a majority server. Transition should be transparent for * the user. */ @Test public void testSessionEstablishment() throws Exception { qu.shutdown(2); CountdownWatcher watcher = new CountdownWatcher(); ZooKeeper zk = new ZooKeeper(qu.getConnString(), CONNECTION_TIMEOUT, watcher, true); watcher.waitForConnected(CONNECTION_TIMEOUT); Assert.assertSame("should be in r/o mode", States.CONNECTEDREADONLY, zk .getState()); long fakeId = zk.getSessionId(); watcher.reset(); qu.start(2); Assert.assertTrue("waiting for server up", ClientBase.waitForServerUp( "127.0.0.1:" + qu.getPeer(2).clientPort, CONNECTION_TIMEOUT)); watcher.waitForConnected(CONNECTION_TIMEOUT); zk.create("/test", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); Assert.assertFalse("fake session and real session have same id", zk .getSessionId() == fakeId); zk.close(); } /** * Ensures that client seeks for r/w servers while it's connected to r/o * server. */ @SuppressWarnings("deprecation") @Test public void testSeekForRwServer() throws Exception { // setup the logger to capture all logs Layout layout = Logger.getRootLogger().getAppender("CONSOLE") .getLayout(); ByteArrayOutputStream os = new ByteArrayOutputStream(); WriterAppender appender = new WriterAppender(layout, os); appender.setImmediateFlush(true); appender.setThreshold(Level.INFO); Logger zlogger = Logger.getLogger("org.apache.zookeeper"); zlogger.addAppender(appender); try { qu.shutdown(2); CountdownWatcher watcher = new CountdownWatcher(); ZooKeeper zk = new ZooKeeper(qu.getConnString(), CONNECTION_TIMEOUT, watcher, true); watcher.waitForConnected(CONNECTION_TIMEOUT); // if we don't suspend a peer it will rejoin a quorum qu.getPeer(1).peer.suspend(); // start two servers to form a quorum; client should detect this and // connect to one of them watcher.reset(); qu.start(2); qu.start(3); ClientBase.waitForServerUp(qu.getConnString(), 2000); watcher.waitForConnected(CONNECTION_TIMEOUT); zk.create("/test", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // resume poor fellow qu.getPeer(1).peer.resume(); } finally { zlogger.removeAppender(appender); } os.close(); LineNumberReader r = new LineNumberReader(new StringReader(os .toString())); String line; Pattern p = Pattern.compile(".*Majority server found.*"); boolean found = false; while ((line = r.readLine()) != null) { if (p.matcher(line).matches()) { found = true; break; } } Assert.assertTrue( "Majority server wasn't found while connected to r/o server", found); } }