|
@@ -0,0 +1,239 @@
|
|
|
+/**
|
|
|
+ * 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.server;
|
|
|
+
|
|
|
+import io.netty.channel.Channel;
|
|
|
+import io.netty.channel.EventLoop;
|
|
|
+import org.apache.zookeeper.PortAssignment;
|
|
|
+import org.apache.zookeeper.ZKTestCase;
|
|
|
+import org.apache.zookeeper.ZooKeeper;
|
|
|
+import org.apache.zookeeper.metrics.MetricsUtils;
|
|
|
+import org.apache.zookeeper.test.ClientBase;
|
|
|
+import org.apache.zookeeper.test.QuorumUtil;
|
|
|
+import org.junit.Assert;
|
|
|
+import org.junit.Test;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.net.InetSocketAddress;
|
|
|
+import java.net.Socket;
|
|
|
+import java.nio.ByteBuffer;
|
|
|
+import java.nio.channels.*;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+
|
|
|
+import static org.apache.zookeeper.server.NIOServerCnxnFactory.ZOOKEEPER_NIO_SESSIONLESS_CNXN_TIMEOUT;
|
|
|
+import static org.mockito.Matchers.any;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+public class ConnectionMetricsTest extends ZKTestCase {
|
|
|
+ protected static final Logger LOG = LoggerFactory.getLogger(ConnectionMetricsTest.class);
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testRevalidateCount() throws Exception {
|
|
|
+ ServerMetrics.getMetrics().resetAll();
|
|
|
+ QuorumUtil util = new QuorumUtil(1); // create a quorum of 3 servers
|
|
|
+ // disable local session to make sure we create a global session
|
|
|
+ util.enableLocalSession(false);
|
|
|
+ util.startAll();
|
|
|
+
|
|
|
+ int follower1 = (int)util.getFollowerQuorumPeers().get(0).getId();
|
|
|
+ int follower2 = (int)util.getFollowerQuorumPeers().get(1).getId();
|
|
|
+ LOG.info("connecting to server: {}", follower1);
|
|
|
+ ClientBase.CountdownWatcher watcher = new ClientBase.CountdownWatcher();
|
|
|
+ // create a connection to follower
|
|
|
+ ZooKeeper zk = new ZooKeeper(util.getConnectionStringForServer(follower1), ClientBase.CONNECTION_TIMEOUT, watcher);
|
|
|
+ watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT);
|
|
|
+ LOG.info("connected");
|
|
|
+
|
|
|
+ // update the connection to allow to connect to the other follower
|
|
|
+ zk.updateServerList(util.getConnectionStringForServer(follower2));
|
|
|
+
|
|
|
+ // follower is shut down and zk should be disconnected
|
|
|
+ util.shutdown(follower1);
|
|
|
+ watcher.waitForDisconnected(ClientBase.CONNECTION_TIMEOUT);
|
|
|
+ LOG.info("disconnected");
|
|
|
+ // should reconnect to another follower, will ask leader to revalidate
|
|
|
+ watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT);
|
|
|
+ LOG.info("reconnected");
|
|
|
+
|
|
|
+ Map<String, Object> values = MetricsUtils.currentServerMetrics();
|
|
|
+ Assert.assertEquals(1L, values.get("connection_revalidate_count"));
|
|
|
+ Assert.assertEquals(1L, values.get("revalidate_count"));
|
|
|
+
|
|
|
+ zk.close();
|
|
|
+ util.shutdownAll();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private class MockNIOServerCnxn extends NIOServerCnxn {
|
|
|
+ public MockNIOServerCnxn(ZooKeeperServer zk, SocketChannel sock,
|
|
|
+ SelectionKey sk, NIOServerCnxnFactory factory,
|
|
|
+ NIOServerCnxnFactory.SelectorThread selectorThread) throws IOException {
|
|
|
+ super(zk, sock, sk, factory, selectorThread);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected boolean isSocketOpen() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class FakeSK extends SelectionKey {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SelectableChannel channel() {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Selector selector() {
|
|
|
+ return mock(Selector.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean isValid() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void cancel() {
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int interestOps() {
|
|
|
+ return ops;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int ops = OP_WRITE + OP_READ;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SelectionKey interestOps(int ops) {
|
|
|
+ this.ops = ops;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int readyOps() {
|
|
|
+ return ops;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private NIOServerCnxn createMockNIOCnxn() throws IOException {
|
|
|
+ InetSocketAddress socketAddr = new InetSocketAddress(80);
|
|
|
+ Socket socket = mock(Socket.class);
|
|
|
+ when(socket.getRemoteSocketAddress()).thenReturn(socketAddr);
|
|
|
+ SocketChannel sock = mock(SocketChannel.class);
|
|
|
+ when(sock.socket()).thenReturn(socket);
|
|
|
+ when(sock.read(any(ByteBuffer.class))).thenReturn(-1);
|
|
|
+
|
|
|
+ return new MockNIOServerCnxn(mock(ZooKeeperServer.class), sock, null, mock(NIOServerCnxnFactory.class), null);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testNIOConnectionDropCount() throws Exception {
|
|
|
+ ServerMetrics.getMetrics().resetAll();
|
|
|
+
|
|
|
+ NIOServerCnxn cnxn = createMockNIOCnxn();
|
|
|
+ cnxn.doIO(new FakeSK());
|
|
|
+
|
|
|
+ Map<String, Object> values = MetricsUtils.currentServerMetrics();
|
|
|
+ Assert.assertEquals(1L, values.get("connection_drop_count"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testNettyConnectionDropCount() throws Exception {
|
|
|
+ InetSocketAddress socketAddr = new InetSocketAddress(80);
|
|
|
+ Channel channel = mock(Channel.class);
|
|
|
+ when(channel.isOpen()).thenReturn(false);
|
|
|
+ when(channel.remoteAddress()).thenReturn(socketAddr);
|
|
|
+ EventLoop eventLoop = mock(EventLoop.class);
|
|
|
+ when(channel.eventLoop()).thenReturn(eventLoop);
|
|
|
+
|
|
|
+ ServerMetrics.getMetrics().resetAll();
|
|
|
+
|
|
|
+ NettyServerCnxnFactory factory = new NettyServerCnxnFactory();
|
|
|
+ NettyServerCnxn cnxn = new NettyServerCnxn(channel, mock(ZooKeeperServer.class), factory);
|
|
|
+
|
|
|
+ // pretend it's connected
|
|
|
+ factory.cnxns.add(cnxn);
|
|
|
+ cnxn.close();
|
|
|
+
|
|
|
+ Map<String, Object> values = MetricsUtils.currentServerMetrics();
|
|
|
+ Assert.assertEquals(1L, values.get("connection_drop_count"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testSessionlessConnectionsExpired() throws Exception {
|
|
|
+ ServerCnxnFactory factory = new NIOServerCnxnFactory();
|
|
|
+ factory.configure(new InetSocketAddress(PortAssignment.unique()), 1000);
|
|
|
+ factory.start();
|
|
|
+ int timeout = Integer.getInteger(
|
|
|
+ ZOOKEEPER_NIO_SESSIONLESS_CNXN_TIMEOUT, 10000);
|
|
|
+
|
|
|
+ ServerMetrics.getMetrics().resetAll();
|
|
|
+ // add two connections w/o touching them so they will expire
|
|
|
+ ((NIOServerCnxnFactory) factory).touchCnxn(createMockNIOCnxn());
|
|
|
+ ((NIOServerCnxnFactory) factory).touchCnxn(createMockNIOCnxn());
|
|
|
+
|
|
|
+ Map<String, Object> values = MetricsUtils.currentServerMetrics();
|
|
|
+ int sleptTime = 0;
|
|
|
+ while (values.get("sessionless_connections_expired") == null || sleptTime < 2*timeout){
|
|
|
+ Thread.sleep(100);
|
|
|
+ sleptTime += 100;
|
|
|
+ values = MetricsUtils.currentServerMetrics();
|
|
|
+ }
|
|
|
+
|
|
|
+ Assert.assertEquals(2L, values.get("sessionless_connections_expired"));
|
|
|
+
|
|
|
+ factory.shutdown();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testStaleSessionsExpired() throws Exception {
|
|
|
+ int tickTime = 1000;
|
|
|
+ SessionTrackerImpl tracker = new SessionTrackerImpl(mock(ZooKeeperServer.class),
|
|
|
+ new ConcurrentHashMap<>(), tickTime, 1L, null);
|
|
|
+
|
|
|
+ tracker.sessionsById.put(1L, mock(SessionTrackerImpl.SessionImpl.class));
|
|
|
+ tracker.sessionsById.put(2L, mock(SessionTrackerImpl.SessionImpl.class));
|
|
|
+
|
|
|
+ tracker.touchSession(1L, tickTime);
|
|
|
+ tracker.touchSession(2L, tickTime);
|
|
|
+
|
|
|
+ ServerMetrics.getMetrics().resetAll();
|
|
|
+
|
|
|
+ tracker.start();
|
|
|
+
|
|
|
+ Map<String, Object> values = MetricsUtils.currentServerMetrics();
|
|
|
+ int sleptTime = 0;
|
|
|
+ while (values.get("stale_sessions_expired") == null || sleptTime < 2*tickTime) {
|
|
|
+ Thread.sleep(100);
|
|
|
+ sleptTime += 100;
|
|
|
+ values = MetricsUtils.currentServerMetrics();
|
|
|
+ }
|
|
|
+
|
|
|
+ Assert.assertEquals(2L, values.get("stale_sessions_expired"));
|
|
|
+
|
|
|
+ tracker.shutdown();
|
|
|
+ }
|
|
|
+}
|