com.pinterest.pinlater.commons.config.ConfigFileServerSet.java Source code

Java tutorial

Introduction

Here is the source code for com.pinterest.pinlater.commons.config.ConfigFileServerSet.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 com.pinterest.pinlater.commons.config;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.twitter.common.base.Command;
import com.twitter.common.base.MorePreconditions;
import com.twitter.common.zookeeper.Group;
import com.twitter.common.zookeeper.ServerSet;
import com.twitter.thrift.Endpoint;
import com.twitter.thrift.ServiceInstance;
import com.twitter.thrift.Status;
import com.twitter.util.ExceptionalFunction;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Map;

/**
 * Implementation of the ServerSet interface that uses a file on local disk instead of talking
 * to ZooKeeper directly. The file is typically downloaded by an external daemon process that
 * registers a watch on the actual ZooKeeper server set. Consumers of this class simply need to
 * provide the ZooKeeper server set path. All other details are handled automatically, since the
 * external daemon is configured to use a standard and consistent file path scheme.
 *
 * Note that this implementation only supports monitor() and not join(). Use the standard
 * ZooKeeper implementation for join().
 *
 */
public class ConfigFileServerSet implements ServerSet {

    private static Logger LOG = LoggerFactory.getLogger(ConfigFileServerSet.class);

    protected final ConfigFileWatcher configFileWatcher;
    protected final String serverSetFilePath;

    /**
     * Create a ConfigFileServerSet instance.
     *
     * @param serverSetZKPath ZooKeeper server set path
     */
    public ConfigFileServerSet(String serverSetZKPath) {
        this(ConfigFileWatcher.defaultInstance(), serverSetZKPath);
    }

    /**
     * Internal constructor. This is provided for use by unit test.
     *
     * @param configFileWatcher ConfigFileWatcher instance to use.
     * @param serverSetFilePath Path the server set file on local disk. This is expected to contain
     *                          a list of host:port pairs, one per line. An external daemon will be
     *                          responsible for keeping this in sync with the actual server set in
     *                          ZooKeeper.
     */
    @VisibleForTesting
    ConfigFileServerSet(ConfigFileWatcher configFileWatcher, String serverSetFilePath) {
        this.serverSetFilePath = MorePreconditions.checkNotBlank(serverSetFilePath);
        this.configFileWatcher = Preconditions.checkNotNull(configFileWatcher);

        File file = new File(serverSetFilePath);
        if (!file.exists()) {
            String message = String.format("Server set file: %s doesn't exist", serverSetFilePath);
            throw new IllegalArgumentException(message);
        }
    }

    @Override
    public EndpointStatus join(InetSocketAddress endpoint, Map<String, InetSocketAddress> additionalEndpoints,
            Status status) throws Group.JoinException, InterruptedException {
        throw new UnsupportedOperationException("ConfigFileServerSet does not support join()");
    }

    @Override
    public EndpointStatus join(InetSocketAddress endpoint, Map<String, InetSocketAddress> additionalEndpoints)
            throws Group.JoinException, InterruptedException {
        throw new UnsupportedOperationException("ConfigFileServerSet does not support join()");
    }

    @Override
    public EndpointStatus join(InetSocketAddress endpoint, Map<String, InetSocketAddress> additionalEndpoints,
            int shardId) throws Group.JoinException, InterruptedException {
        throw new UnsupportedOperationException("ConfigFileServerSet does not support join()");
    }

    @Override
    public Command watch(final HostChangeMonitor<ServiceInstance> monitor) throws MonitorException {
        monitor(monitor);
        return null;
    }

    @Override
    public void monitor(final HostChangeMonitor<ServiceInstance> monitor) throws MonitorException {
        Preconditions.checkNotNull(monitor);
        try {
            // Each call to monitor registers a new file watch. This is a bit inefficient if there
            // are many calls to monitor(), since each watch needs to parse the file contents
            // independently. But it is simple and delegates keeping track of a list of monitors to the
            // ConfigFileWatcher. In practice, we don't really expect multiple calls to monitor anyway.
            configFileWatcher.addWatch(serverSetFilePath, new ExceptionalFunction<byte[], Void>() {
                @Override
                public Void applyE(byte[] newContents) throws Exception {
                    ImmutableSet<ServiceInstance> newServerSet = readServerSet(newContents);
                    monitor.onChange(newServerSet);
                    return null;
                }
            });
        } catch (IOException e) {
            throw new MonitorException("Error setting up watch on dynamic server set file:" + serverSetFilePath, e);
        }
    }

    protected static ImmutableSet<ServiceInstance> readServerSet(byte[] fileContent) throws IOException {
        ImmutableSet.Builder<ServiceInstance> builder = new ImmutableSet.Builder<ServiceInstance>();
        InputStream stream = new ByteArrayInputStream(fileContent);
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        while (true) {
            String line = reader.readLine();
            if (line == null) {
                // EOF.
                break;
            } else if (line.isEmpty()) {
                // Skip empty lines.
                continue;
            }

            // We expect each line to be of the form "hostname:port". Note that host names can
            // contain ':' themselves (e.g. ipv6 addresses).
            int index = line.lastIndexOf(':');
            Preconditions.checkArgument(index > 0 && index < line.length() - 1);

            String host = line.substring(0, index);
            int port = Integer.parseInt(line.substring(index + 1));
            builder.add(new ServiceInstance(new Endpoint(host, port), // endpoint
                    Collections.<String, Endpoint>emptyMap(), // additional endpoints
                    Status.ALIVE)); // status
        }
        return builder.build();
    }
}