/* * Copyright 2016 LINE Corporation * * LINE Corporation 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 com.linecorp.armeria.server.zookeeper; import static org.assertj.core.api.Assertions.fail; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooKeeper; import org.assertj.core.api.Assertions; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import com.linecorp.armeria.client.Endpoint; import com.linecorp.armeria.client.zookeeper.TestBase; import com.linecorp.armeria.common.http.AggregatedHttpMessage; import com.linecorp.armeria.common.http.HttpHeaders; import com.linecorp.armeria.common.http.HttpRequest; import com.linecorp.armeria.common.http.HttpResponseWriter; import com.linecorp.armeria.common.http.HttpSessionProtocols; import com.linecorp.armeria.common.http.HttpStatus; import com.linecorp.armeria.common.util.CompletionActions; import com.linecorp.armeria.common.zookeeper.NodeValueCodec; import com.linecorp.armeria.server.Server; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.http.AbstractHttpService; import junitextensions.OptionAssert; import zookeeperjunit.CloseableZooKeeper; import zookeeperjunit.ZooKeeperAssert; public class ZooKeeperRegistrationTest extends TestBase implements ZooKeeperAssert, OptionAssert { protected static final KeeperState[] expectedStates = { KeeperState.Disconnected, KeeperState.Expired, KeeperState.SyncConnected, KeeperState.SyncConnected, KeeperState.Disconnected }; List<Server> servers; List<ZooKeeperRegistration> zkConnectors; List<ZooKeeperUpdatingListener> listeners; @Before public void startServer() { servers = new ArrayList<>(); zkConnectors = new ArrayList<>(); listeners = new ArrayList<>(); try { for (Endpoint endpoint : sampleEndpoints) { ServerBuilder sb = new ServerBuilder(); Server server = sb.serviceAt("/", new EchoService()).port(endpoint.port(), HttpSessionProtocols.HTTP).build(); ZooKeeperUpdatingListener listener; listener = new ZooKeeperUpdatingListener(instance().connectString().get(), zNode, sessionTimeout, endpoint); server.addListener(listener); server.start().get(); listeners.add(listener); zkConnectors.add(listener.getConnector()); servers.add(server); } } catch (Exception e) { fail(e.getMessage()); } } @Test public void testServerNodeCreateAndDelete() { //all servers start and with zNode created sampleEndpoints.forEach( endpoint -> assertExists(zNode + '/' + endpoint.host() + '_' + endpoint.port())); try (CloseableZooKeeper zkClient = connection()) { try { sampleEndpoints.forEach(endpoint -> { try { Assertions.assertThat(NodeValueCodec.DEFAULT.decode( zkClient.getData(zNode + '/' + endpoint.host() + '_' + endpoint.port()).get())) .isEqualTo( endpoint); } catch (Throwable throwable) { fail(throwable.getMessage()); } }); //stop one server and check its ZooKeeper node if (servers.size() > 1) { servers.get(0).stop().get(); servers.remove(0); zkConnectors.remove(0); ZooKeeperUpdatingListener stoppedServerListener = listeners.remove(0); assertNotExists(zNode + '/' + stoppedServerListener.getEndpoint().host() + '_' + stoppedServerListener.getEndpoint().port()); //the other server will not influenced assertExists(zNode + '/' + listeners.get(0).getEndpoint().host() + '_' + listeners.get(0).getEndpoint().port()); } } catch (Throwable throwable) { fail(throwable.getMessage()); } } } @Ignore // FIXME: https://github.com/line/armeria/issues/477 @Test public void testConnectionRecovery() throws Exception { ZooKeeperRegistration zkConnector = zkConnectors.get(0); zkConnector.enableStateRecording(); ZooKeeper zkHandler1 = zkConnector.underlyingClient(); CountDownLatch latch = new CountDownLatch(1); ZooKeeper zkHandler2; //create a new handler with the same sessionId and password zkHandler2 = new ZooKeeper(instance().connectString().get(), sessionTimeout, event -> { if (event.getState() == KeeperState.SyncConnected) { latch.countDown(); } }, zkHandler1.getSessionId(), zkHandler1.getSessionPasswd()); latch.await(); //once connected, close the new handler to cause the original handler session expire zkHandler2.close(); for (KeeperState state : expectedStates) { assertEquals(state, zkConnector.stateQueue().take()); } //connection will recover and our ZooKeeper node also exists testServerNodeCreateAndDelete(); } @After public void stopServer() { try { for (Server server : servers) { server.stop().get(); } } catch (Exception ex) { fail(ex.getMessage()); } } private static class EchoService extends AbstractHttpService { @Override protected final void doPost(ServiceRequestContext ctx, HttpRequest req, HttpResponseWriter res) { req.aggregate() .thenAccept(aReq -> echo(aReq, res)) .exceptionally(CompletionActions::log); } protected void echo(AggregatedHttpMessage aReq, HttpResponseWriter res) { res.write(HttpHeaders.of(HttpStatus.OK)); res.write(aReq.content()); res.close(); } } }