com.spotify.helios.servicescommon.coordination.ZooKeeperUpdatingPersistentDirectoryTest.java Source code

Java tutorial

Introduction

Here is the source code for com.spotify.helios.servicescommon.coordination.ZooKeeperUpdatingPersistentDirectoryTest.java

Source

/*
 * Copyright (c) 2014 Spotify AB.
 *
 * Licensed 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.spotify.helios.servicescommon.coordination;

import com.spotify.helios.Parallelized;
import com.spotify.helios.ZooKeeperTestingServerManager;

import org.apache.commons.io.FileUtils;
import org.apache.curator.utils.ZKPaths;
import org.apache.zookeeper.KeeperException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.concurrent.Callable;

import static com.spotify.helios.Polling.await;
import static com.spotify.helios.servicescommon.coordination.ZooKeeperModelReporter.noop;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.zookeeper.KeeperException.NodeExistsException;
import static org.junit.Assert.assertArrayEquals;

@RunWith(Parallelized.class)
public class ZooKeeperUpdatingPersistentDirectoryTest {

    private static final String PARENT_PATH = "/foobar";

    private Path stateFile;
    private Path backupDir;

    private static final byte[] BAR1_DATA = "bar1".getBytes();
    private static final byte[] BAR2_DATA = "bar2".getBytes();
    private static final byte[] BAR3_DATA = "bar2".getBytes();
    private static final String FOO_NODE = "foo";
    private static final String BAZ_NODE = "baz";
    private static final String FOO_PATH = ZKPaths.makePath(PARENT_PATH, FOO_NODE);
    private static final String BAZ_PATH = ZKPaths.makePath(PARENT_PATH, BAZ_NODE);

    private ZooKeeperTestingServerManager zk = new ZooKeeperTestingServerManager();

    private ZooKeeperUpdatingPersistentDirectory sut;

    @Before
    public void setUp() throws Exception {
        backupDir = Files.createTempDirectory("helios-zk-updating-persistent-dir-test-backup-");
        stateFile = Files.createTempFile("helios-zk-updating-persistent-dir-test-", "");
        zk.curatorWithSuperAuth().newNamespaceAwareEnsurePath(PARENT_PATH)
                .ensure(zk.curatorWithSuperAuth().getZookeeperClient());
        setupDirectory();
    }

    private void setupDirectory() throws IOException, InterruptedException {
        final DefaultZooKeeperClient client = new DefaultZooKeeperClient(zk.curatorWithSuperAuth());
        final ZooKeeperClientProvider provider = new ZooKeeperClientProvider(client, noop());
        sut = ZooKeeperUpdatingPersistentDirectory.create("test", provider, stateFile, PARENT_PATH);
        sut.startAsync();
    }

    @After
    public void tearDown() throws Exception {
        sut.stopAsync().awaitTerminated();
        zk.close();
        FileUtils.deleteQuietly(stateFile.toFile());
        FileUtils.deleteQuietly(backupDir.toFile());
    }

    @Test
    public void verifyCreatesAndRemovesNode() throws Exception {
        sut.put(FOO_NODE, BAR1_DATA);
        final byte[] remote = awaitNode(FOO_PATH);
        assertArrayEquals(BAR1_DATA, remote);
        sut.remove(FOO_NODE);
        awaitNoNode(FOO_PATH);
    }

    @Test
    public void verifyUpdatesDifferingNode() throws Exception {
        try {
            zk.curatorWithSuperAuth().create().forPath(FOO_PATH, "old".getBytes());
        } catch (NodeExistsException ignore) {
        }
        sut.put(FOO_NODE, BAR1_DATA);
        awaitNodeWithData(FOO_PATH, BAR1_DATA);
    }

    @Test
    public void verifyRemovesUndesiredNode() throws Exception {
        zk.ensure(FOO_PATH);
        zk.stop();
        zk.start();
        awaitNoNode(FOO_PATH);
    }

    @Test
    public void verifyRecoversFromBackupRestoreOnline() throws Exception {
        // Create backup
        try {
            zk.curatorWithSuperAuth().create().forPath("/version", "1".getBytes());
        } catch (NodeExistsException ignore) {
        }
        sut.put(FOO_NODE, BAR1_DATA);
        awaitNodeWithData(FOO_PATH, BAR1_DATA);
        zk.backup(backupDir);

        // Write data after backup
        zk.curatorWithSuperAuth().setData().forPath("/version", "2".getBytes());
        sut.put(FOO_NODE, BAR2_DATA);
        sut.put(BAZ_NODE, BAR3_DATA);
        awaitNodeWithData(FOO_PATH, BAR2_DATA);
        awaitNodeWithData(BAZ_PATH, BAR3_DATA);

        // Restore backup
        zk.stop();
        zk.restore(backupDir);
        zk.start();
        assertArrayEquals("1".getBytes(), zk.curatorWithSuperAuth().getData().forPath("/version"));

        // Verify that latest data is pushed
        awaitNodeWithData(FOO_PATH, BAR2_DATA);
        awaitNodeWithData(BAZ_PATH, BAR3_DATA);
    }

    @Test
    public void verifyRecoversFromBackupRestoreOffline() throws Exception {
        // Create backup
        try {
            zk.curatorWithSuperAuth().create().forPath("/version", "1".getBytes());
        } catch (NodeExistsException ignore) {
        }
        sut.put(FOO_NODE, BAR1_DATA);
        awaitNodeWithData(FOO_PATH, BAR1_DATA);
        zk.backup(backupDir);

        // Write data after backup
        zk.curatorWithSuperAuth().setData().forPath("/version", "2".getBytes());
        sut.put(FOO_NODE, BAR2_DATA);
        sut.put(BAZ_NODE, BAR3_DATA);
        awaitNodeWithData(FOO_PATH, BAR2_DATA);
        awaitNodeWithData(BAZ_PATH, BAR3_DATA);

        // Stop persistent directory
        sut.stopAsync().awaitTerminated();

        // Restore backup
        zk.stop();
        zk.restore(backupDir);
        zk.start();
        assertArrayEquals("1".getBytes(), zk.curatorWithSuperAuth().getData().forPath("/version"));

        // Start new persistent directory
        setupDirectory();

        // Verify that latest data is pushed
        awaitNodeWithData(FOO_PATH, BAR2_DATA);
        awaitNodeWithData(BAZ_PATH, BAR3_DATA);
    }

    private byte[] awaitNode(final String path) throws Exception {
        return await(30, SECONDS, new Callable<byte[]>() {
            @Override
            public byte[] call() throws Exception {
                try {
                    return zk.curatorWithSuperAuth().getData().forPath(path);
                } catch (KeeperException.NoNodeException e) {
                    return null;
                }
            }
        });
    }

    private void awaitNodeWithData(final String path, final byte[] data) throws Exception {
        await(30, SECONDS, new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                try {
                    final byte[] remote = zk.curatorWithSuperAuth().getData().forPath(path);
                    return Arrays.equals(data, remote) ? true : null;
                } catch (KeeperException.NoNodeException e) {
                    return null;
                }
            }
        });
    }

    private void awaitNoNode(final String path) throws Exception {
        await(30, SECONDS, new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                try {
                    zk.curatorWithSuperAuth().getData().forPath(path);
                    return null;
                } catch (KeeperException.NoNodeException e) {
                    return true;
                }
            }
        });
    }
}