org.apache.sling.discovery.etcd.EtcdServiceTest.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sling.discovery.etcd.EtcdServiceTest.java

Source

/*
 * 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.sling.discovery.etcd;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPOutputStream;

import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.discovery.etcd.gzip.GzipRequestInterceptor;
import org.apache.sling.discovery.etcd.gzip.GzipResponseInterceptor;
import org.apache.sling.etcd.client.EtcdClient;
import org.apache.sling.etcd.client.LeaderStatsResponse;
import org.apache.sling.etcd.client.MemberStatsResponse;
import org.apache.sling.etcd.client.MembersResponse;
import org.apache.sling.etcd.client.VersionResponse;
import org.apache.sling.etcd.client.impl.LeaderStatsResponseImpl;
import org.apache.sling.etcd.client.impl.MemberStatsResponseImpl;
import org.apache.sling.etcd.client.impl.MembersResponseImpl;
import org.apache.sling.etcd.client.impl.VersionResponseImpl;
import org.apache.sling.etcd.common.ErrorCodes;
import org.apache.sling.etcd.client.EtcdNode;
import org.apache.sling.etcd.client.KeyResponse;
import org.apache.sling.etcd.client.impl.EtcdClientImpl;
import org.apache.sling.etcd.client.impl.KeyResponseImpl;
import junit.framework.Assert;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Test;

public class EtcdServiceTest {

    private Server server;

    private EtcdClient etcdClient;

    private CloseableHttpClient httpClient;

    private PoolingHttpClientConnectionManager connectionManager;

    @After
    public void tearDown() throws Exception {
        IOUtils.closeQuietly(httpClient);
        IOUtils.closeQuietly(connectionManager);
        if (server != null) {
            server.stop();
        }
    }

    @Test
    public void testAnnounceLocalInstance() throws Exception {
        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void service(HttpServletRequest req, HttpServletResponse res)
                    throws ServletException, IOException {
                if (!"POST".equals(req.getMethod())) {
                    throw new IllegalArgumentException("create key requires POST");
                }
                String ttl = req.getParameter("ttl");
                if (ttl == null) {
                    throw new IllegalArgumentException("ttl not specified");
                }
                String value = req.getParameter("value");
                if (value == null) {
                    throw new IllegalArgumentException("value not specified");
                }
                res.setStatus(201);
                res.getWriter()
                        .write(IOUtils.toString(getClass().getResourceAsStream("/announce-local-instance.json")));
            }
        };
        AnnounceData annData = new AnnounceData("sling-id", "server-info", "default-cluster", 1926);
        server = startServer(servlet, "/v2/keys/discovery/announces");
        EtcdService etcdService = buildEtcdService(serverPort(server));
        EtcdNode annNode = etcdService.createAnnounce(annData.toString(), 10);
        Assert.assertNotNull(annNode);
        Assert.assertEquals(annNode.key(), "/discovery/announces/244");
    }

    @Test(expected = IOException.class)
    public void testAnnounceLocalInstanceIOError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(), "/discovery");
        try {
            etcdService.createAnnounce("/discovery/announces/244", 10);
        } catch (IOException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveIoError());
            throw e;
        }
    }

    @Test(expected = EtcdServiceException.class)
    public void testAnnounceLocalInstanceEtcdError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(ErrorCodes.RAFT_INTERNAL_ERROR),
                "/discovery");
        try {
            etcdService.createAnnounce("/discovery/announces/244", 10);
        } catch (EtcdServiceException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveEtcdError(ErrorCodes.RAFT_INTERNAL_ERROR,
                    Integer.MAX_VALUE));
            throw e;
        }
    }

    @Test
    public void testSendLocalProperties() throws Exception {
        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void service(HttpServletRequest req, HttpServletResponse res)
                    throws ServletException, IOException {
                if (!"PUT".equals(req.getMethod())) {
                    throw new IllegalArgumentException("send props requires PUT");
                }
                String value = req.getParameter("value");
                if (value == null) {
                    throw new IllegalArgumentException("value not specified");
                }
                res.setStatus(201);
                res.getWriter().write(IOUtils.toString(getClass().getResourceAsStream("/send-properties.json")));
            }
        };
        server = startServer(servlet, "/v2/keys/discovery/properties/sling-id");
        EtcdService etcdService = buildEtcdService(serverPort(server));
        long lastModified = etcdService.sendInstanceProperties(Collections.singletonMap("n1", "v1"), "sling-id");
        Assert.assertEquals(253L, lastModified);
    }

    @Test(expected = IOException.class)
    public void testSendLocalPropertiesIOError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(), "/discovery");
        try {
            etcdService.sendInstanceProperties(Collections.<String, String>emptyMap(), "sling-id");
        } catch (IOException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveIoError());
            throw e;
        }
    }

    @Test(expected = EtcdServiceException.class)
    public void testSendLocalPropertiesEtcdError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(ErrorCodes.RAFT_INTERNAL_ERROR),
                "/discovery");
        try {
            etcdService.sendInstanceProperties(Collections.<String, String>emptyMap(), "sling-id");
        } catch (EtcdServiceException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveEtcdError(ErrorCodes.RAFT_INTERNAL_ERROR,
                    Integer.MAX_VALUE));
            throw e;
        }
    }

    @Test
    public void testGetAnnounces() throws Exception {
        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void service(HttpServletRequest req, HttpServletResponse res)
                    throws ServletException, IOException {
                if (!"GET".equals(req.getMethod())) {
                    throw new IllegalArgumentException("get announces requires GET");
                }
                String recursive = req.getParameter("recursive");
                if (recursive == null || "false".equals(recursive)) {
                    throw new IllegalArgumentException("recursive must be true");
                }
                String sorted = req.getParameter("sorted");
                if (sorted == null || "false".equals(sorted)) {
                    throw new IllegalArgumentException("sorted must be true");
                }
                res.setStatus(200);
                res.getWriter().write(IOUtils.toString(getClass().getResourceAsStream("/get-announces.json")));
            }
        };
        server = startServer(servlet, "/v2/keys/discovery/announces");
        EtcdService etcdService = buildEtcdService(serverPort(server));
        List<EtcdNode> announces = etcdService.getAnnounces();
        Assert.assertNotNull(announces);
        Assert.assertEquals(2, announces.size());
    }

    @Test(expected = IOException.class)
    public void testGetAnnouncesIOError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(), "/discovery");
        try {
            etcdService.getAnnounces();
        } catch (IOException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveIoError());
            throw e;
        }
    }

    @Test(expected = EtcdServiceException.class)
    public void testGetAnnouncesEtcdError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(ErrorCodes.RAFT_INTERNAL_ERROR),
                "/discovery");
        try {
            etcdService.getAnnounces();
        } catch (EtcdServiceException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveEtcdError(ErrorCodes.RAFT_INTERNAL_ERROR,
                    Integer.MAX_VALUE));
            throw e;
        }
    }

    @Test
    public void testGetAnnouncesKeyNotFound() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(ErrorCodes.KEY_NOT_FOUND),
                "/discovery");
        List<EtcdNode> announces = etcdService.getAnnounces();
        Assert.assertEquals(0, announces.size());
    }

    @Test
    public void testGetProperties() throws Exception {
        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void service(HttpServletRequest req, HttpServletResponse res)
                    throws ServletException, IOException {
                if (!"GET".equals(req.getMethod())) {
                    throw new IllegalArgumentException("get properties requires GET");
                }
                String recursive = req.getParameter("recursive");
                if (recursive == null || "false".equals(recursive)) {
                    throw new IllegalArgumentException("recursive must be true");
                }
                res.setStatus(200);
                res.getWriter().write(IOUtils.toString(getClass().getResourceAsStream("/get-properties.json")));
            }
        };
        server = startServer(servlet, "/v2/keys/discovery/properties/sling-id");
        EtcdService etcdService = buildEtcdService(serverPort(server));
        Map<String, String> properties = etcdService.getProperties("sling-id");
        Assert.assertNotNull(properties);
        Assert.assertEquals(1, properties.size());
    }

    @Test(expected = IOException.class)
    public void testGetPropertiesIOError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(), "/discovery");
        try {
            etcdService.getProperties("sling-id");
        } catch (IOException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveIoError());
            throw e;
        }
    }

    @Test(expected = EtcdServiceException.class)
    public void testGetPropertiesEtcdError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(ErrorCodes.RAFT_INTERNAL_ERROR),
                "/discovery");
        try {
            etcdService.getProperties("sling-id");
        } catch (EtcdServiceException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveEtcdError(ErrorCodes.RAFT_INTERNAL_ERROR,
                    Integer.MAX_VALUE));
            throw e;
        }
    }

    @Test
    public void testGetPropertiesKeyNotFound() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(ErrorCodes.KEY_NOT_FOUND),
                "/discovery");
        Map<String, String> properties = etcdService.getProperties("sling-id");
        Assert.assertNotNull(properties);
        Assert.assertEquals(0, properties.size());
    }

    @Test
    public void testGetAllInstancesProperties() throws Exception {
        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void service(HttpServletRequest req, HttpServletResponse res)
                    throws ServletException, IOException {
                if (!"GET".equals(req.getMethod())) {
                    throw new IllegalArgumentException("get all properties requires GET");
                }
                String recursive = req.getParameter("recursive");
                if (recursive == null || "false".equals(recursive)) {
                    throw new IllegalArgumentException("recursive must be true");
                }
                res.setStatus(200);
                res.getWriter().write(IOUtils.toString(getClass().getResourceAsStream("/get-all-properties.json")));
            }
        };
        server = startServer(servlet, "/v2/keys/discovery/properties");
        EtcdService etcdService = buildEtcdService(serverPort(server));
        Map<String, Map<String, String>> properties = etcdService.getInstancesProperties();
        Assert.assertNotNull(properties);
        Assert.assertEquals(2, properties.size());
        Map<String, String> props = properties.get("sling-id");
        Assert.assertNotNull(props);
        Assert.assertEquals(1, props.size());
    }

    @Test(expected = IOException.class)
    public void testGetAllInstancesPropertiesIOError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(), "/discovery");
        try {
            etcdService.getInstancesProperties();
        } catch (IOException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveIoError());
            throw e;
        }
    }

    @Test(expected = EtcdServiceException.class)
    public void testGetAllInstancesPropertiesEtcdError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(ErrorCodes.RAFT_INTERNAL_ERROR),
                "/discovery");
        try {
            etcdService.getInstancesProperties();
        } catch (EtcdServiceException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveEtcdError(ErrorCodes.RAFT_INTERNAL_ERROR,
                    Integer.MAX_VALUE));
            throw e;
        }
    }

    @Test
    public void testRefreshAnnounce() throws Exception {
        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void service(HttpServletRequest req, HttpServletResponse res)
                    throws ServletException, IOException {
                if (!"PUT".equals(req.getMethod())) {
                    throw new IllegalArgumentException("refresh announce requires PUT");
                }
                String recursive = req.getParameter("prevExist");
                if (recursive == null || "false".equals(recursive)) {
                    throw new IllegalArgumentException("prevExist must be true");
                }
                String value = req.getParameter("value");
                if (value == null) {
                    throw new IllegalArgumentException("value not specified");
                }
                res.setStatus(200);
                res.getWriter()
                        .write(IOUtils.toString(getClass().getResourceAsStream("/refresh-existing-announce.json")));
            }
        };
        server = startServer(servlet, "/v2/keys/discovery/announces/265");
        EtcdService etcdService = buildEtcdService(serverPort(server));
        AnnounceData annData = new AnnounceData("sling-id-2", "server-info-2", "default-cluster", 1928);
        etcdService.refreshAnnounce("/discovery/announces/265", annData.toString(), 20000);
    }

    @Test(expected = IOException.class)
    public void testRefreshAnnounceIOError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(), "/discovery");
        try {
            AnnounceData annData = new AnnounceData("sling-id-2", "server-info-2", "default-cluster", 1928);
            etcdService.refreshAnnounce("/discovery/announces/265", annData.toString(), 20000);
        } catch (IOException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveIoError());
            throw e;
        }
    }

    @Test(expected = EtcdServiceException.class)
    public void testRefreshAnnounceEtcdError() throws Exception {
        EtcdService etcdService = new EtcdService(new IoExceptionEtcdClient(ErrorCodes.RAFT_INTERNAL_ERROR),
                "/discovery");
        try {
            AnnounceData annData = new AnnounceData("sling-id-2", "server-info-2", "default-cluster", 1928);
            etcdService.refreshAnnounce("/discovery/announces/265", annData.toString(), 20000);
        } catch (EtcdServiceException e) {
            Assert.assertEquals(1, etcdService.getErrStats().consecutiveEtcdError(ErrorCodes.RAFT_INTERNAL_ERROR,
                    Integer.MAX_VALUE));
            throw e;
        }
    }

    @Test
    public void testGetGzipped() throws Exception {
        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void service(HttpServletRequest req, HttpServletResponse res)
                    throws ServletException, IOException {
                String acceptEncoding = req.getHeader("Accept-Encoding");
                if (acceptEncoding != null && acceptEncoding.equalsIgnoreCase("gzip")) {
                    res.setHeader("Content-Encoding", "gzip");
                    res.setStatus(200);
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    GZIPOutputStream gzip = new GZIPOutputStream(out);
                    String data = IOUtils.toString(getClass().getResourceAsStream("/get-properties.json"), "UTF-8");
                    gzip.write(data.getBytes("UTF-8"));
                    gzip.close();
                    res.getOutputStream().write(out.toByteArray());
                } else {
                    throw new IllegalArgumentException("accept-encoding not found or not gzip");
                }
            }
        };
        server = startServer(servlet, "/v2/keys/discovery/properties/sling-id");
        EtcdService etcdService = buildEtcdService(serverPort(server));
        Map<String, String> properties = etcdService.getProperties("sling-id");
        Assert.assertNotNull(properties);
        Assert.assertEquals(1, properties.size());
    }

    private class IoExceptionEtcdClient implements EtcdClient {

        final boolean throwIoException;

        final int errorCode;

        public IoExceptionEtcdClient() {
            throwIoException = true;
            errorCode = -1;
        }

        public IoExceptionEtcdClient(int errorCode) {
            throwIoException = false;
            this.errorCode = errorCode;
        }

        @Nonnull
        public KeyResponse getKey(@Nonnull String s, @Nonnull Map<String, String> map) throws IOException {
            return throwOrReturnError();
        }

        @Nonnull
        public KeyResponse putKey(@Nonnull String s, String s1, @Nonnull Map<String, String> map)
                throws IOException {
            return throwOrReturnError();
        }

        @Nonnull
        public KeyResponse putKey(@Nonnull String s, @Nonnull InputStream inputStream,
                @Nonnull Map<String, String> map) throws IOException {
            return throwOrReturnError();
        }

        @Nonnull
        public KeyResponse postKey(@Nonnull String s, String s1, @Nonnull Map<String, String> map)
                throws IOException {
            return throwOrReturnError();
        }

        @Nonnull
        public KeyResponse postKey(@Nonnull String s, @Nonnull InputStream inputStream,
                @Nonnull Map<String, String> map) throws IOException {
            return throwOrReturnError();
        }

        @Nonnull
        public KeyResponse deleteKey(@Nonnull String s, @Nonnull Map<String, String> map) throws IOException {
            return throwOrReturnError();
        }

        @Nonnull
        public MembersResponse getMembers() throws IOException {
            if (throwIoException) {
                throw new IOException();
            }
            JSONObject members = new JSONObject();
            try {
                members.putOpt("members", new JSONArray());
            } catch (JSONException e) {
                e.printStackTrace();
            }
            return new MembersResponseImpl(200, "OK", Collections.<String, List<String>>emptyMap(), members);
        }

        @Nonnull
        public LeaderStatsResponse getLeaderStats(@Nonnull URI leaderPeerEndpoint) throws IOException {
            if (throwIoException) {
                throw new IOException();
            }
            JSONObject stats = new JSONObject();
            try {
                stats.putOpt("leader", "some-leader-id");
                stats.putOpt("followers", new JSONObject());
            } catch (JSONException e) {
                e.printStackTrace();
            }
            return new LeaderStatsResponseImpl(200, "OK", Collections.<String, List<String>>emptyMap(), stats);
        }

        @Nonnull
        public MemberStatsResponse getMemberStats(@Nonnull URI peerEndpoint) throws IOException {
            if (throwIoException) {
                throw new IOException();
            }
            JSONObject stats = new JSONObject();
            try {
                stats.putOpt("name", "c3");
                stats.putOpt("id", "324473db0474a678");
                stats.putOpt("state", "StateLeader");
                stats.putOpt("startTime", "2015-05-09T15:50:26.274028984+02:00");
                stats.putOpt("startTime", "2015-05-09T15:50:26.274028984+02:00");
                JSONObject leaderInfo = new JSONObject();
                leaderInfo.putOpt("leader", "324473db0474a678");
                leaderInfo.putOpt("uptime", "8h45m20.069720963s");
                leaderInfo.putOpt("uptime", "8h45m20.069720963s");
                leaderInfo.putOpt("startTime", "2015-05-09T15:50:33.77877435+02:00");
                stats.putOpt("leaderInfo", leaderInfo);
                stats.putOpt("recvAppendRequestCnt", 0L);
                stats.putOpt("sendAppendRequestCnt", 245368L);
                stats.putOpt("sendPkgRate", 9.302272541567971);
                stats.putOpt("sendBandwidthRate", 763.6235529373149);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            return new MemberStatsResponseImpl(200, "OK", Collections.<String, List<String>>emptyMap(), stats);
        }

        @Nonnull
        public VersionResponse getVersion(@Nonnull URI peerEndpoint) throws IOException {
            if (throwIoException) {
                throw new IOException();
            }
            return new VersionResponseImpl(200, "OK", Collections.<String, List<String>>emptyMap(), "etcd 2.0.8");
        }

        private KeyResponse throwOrReturnError() throws IOException {
            if (throwIoException) {
                throw new IOException();
            }
            JSONObject errorData = new JSONObject();
            try {
                errorData.putOpt("errorCode", errorCode);
                errorData.putOpt("message", "error");
                errorData.putOpt("cause", "/test/is/the/cause");
                errorData.putOpt("index", 12);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            return new KeyResponseImpl(400, "error", Collections.<String, List<String>>emptyMap(), errorData);
        }

    }

    private EtcdService buildEtcdService(int port) throws Exception {
        connectionManager = new PoolingHttpClientConnectionManager();
        final RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000)
                .setRedirectsEnabled(true).setStaleConnectionCheckEnabled(true).build();
        httpClient = HttpClients.custom().addInterceptorFirst(new GzipRequestInterceptor())
                .addInterceptorFirst(new GzipResponseInterceptor()).setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig).build();
        etcdClient = new EtcdClientImpl(httpClient, new URI("http://localhost:" + port));
        return new EtcdService(etcdClient, "/discovery");
    }

    private static Server startServer(HttpServlet servlet, String pathSpec) throws Exception {
        Server server = new Server();
        server.setConnectors(new Connector[] { new SelectChannelConnector() });
        ServletContextHandler sch = new ServletContextHandler(null, "/", false, false);
        sch.addServlet(new ServletHolder(servlet), pathSpec);
        server.setHandler(sch);
        server.start();
        return server;
    }

    private static int serverPort(Server server) {
        return server.getConnectors()[0].getLocalPort();
    }
}