All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.linecorp.centraldogma.server.ZooKeeperReplicationConfig Maven / Gradle / Ivy

Go to download

Highly-available version-controlled service configuration repository based on Git, ZooKeeper and HTTP/2 (centraldogma-server)

There is a newer version: 0.70.0
Show newest version
/*
 * Copyright 2017 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:
 *
 *   https://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.centraldogma.server;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.linecorp.centraldogma.server.CentralDogmaConfig.convertValue;
import static java.util.Objects.requireNonNull;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;

import io.netty.util.NetUtil;

/**
 * ZooKeeper-based replication configuration.
 */
public final class ZooKeeperReplicationConfig implements ReplicationConfig {

    private static final int DEFAULT_TIMEOUT_MILLIS = 10000;
    private static final int DEFAULT_NUM_WORKERS = 16;
    private static final int DEFAULT_MAX_LOG_COUNT = 1024;
    private static final long DEFAULT_MIN_LOG_AGE_MILLIS = TimeUnit.DAYS.toMillis(1);
    private static final String DEFAULT_SECRET = "ch4n63m3";

    private final int serverId;
    private final Map servers;
    @Nullable
    private final String secret;
    private final Map additionalProperties;
    private final int timeoutMillis;
    private final int numWorkers;
    private final int maxLogCount;
    private final long minLogAgeMillis;

    /**
     * Creates a new replication configuration.
     *
     * @param serverId the ID of this ZooKeeper server in {@code servers}
     * @param servers the ZooKeeper server addresses, keyed by their ZooKeeper server IDs
     */
    public ZooKeeperReplicationConfig(int serverId, Map servers) {
        this(serverId, servers, null, null, null, null, null, null);
    }

    @VisibleForTesting
    ZooKeeperReplicationConfig(
            int serverId, Map servers, String secret,
            Map additionalProperties,
            int timeoutMillis, int numWorkers, int maxLogCount, long minLogAgeMillis) {
        this(Integer.valueOf(serverId), servers, secret, additionalProperties, Integer.valueOf(timeoutMillis),
             Integer.valueOf(numWorkers), Integer.valueOf(maxLogCount), Long.valueOf(minLogAgeMillis));
    }

    @JsonCreator
    ZooKeeperReplicationConfig(@JsonProperty("serverId") @Nullable Integer serverId,
                               @JsonProperty(value = "servers", required = true)
                               @JsonDeserialize(keyAs = Integer.class, contentAs = ZooKeeperServerConfig.class)
                               Map servers,
                               @JsonProperty("secret") @Nullable String secret,
                               @JsonProperty("additionalProperties")
                               @JsonDeserialize(keyAs = String.class, contentAs = String.class)
                               @Nullable Map additionalProperties,
                               @JsonProperty("timeoutMillis") @Nullable Integer timeoutMillis,
                               @JsonProperty("numWorkers") @Nullable Integer numWorkers,
                               @JsonProperty("maxLogCount") @Nullable Integer maxLogCount,
                               @JsonProperty("minLogAgeMillis") @Nullable Long minLogAgeMillis) {

        requireNonNull(servers, "servers");
        this.serverId = serverId != null ? serverId : findServerId(servers);
        checkArgument(this.serverId > 0, "serverId: %s (expected: > 0)", serverId);
        this.secret = secret;
        checkArgument(!secret().isEmpty(), "secret is empty.");

        servers.forEach((id, server) -> {
            checkArgument(id > 0, "'servers' contains non-positive server ID: %s (expected: > 0)", id);
        });
        this.servers = ImmutableMap.copyOf(servers);

        checkArgument(!this.servers.isEmpty(), "servers is empty.");
        checkArgument(this.servers.containsKey(this.serverId),
                      "servers must contain the server '%s'.", this.serverId);

        this.additionalProperties = firstNonNull(additionalProperties, ImmutableMap.of());

        this.timeoutMillis =
                timeoutMillis == null || timeoutMillis <= 0 ? DEFAULT_TIMEOUT_MILLIS : timeoutMillis;

        this.numWorkers =
                numWorkers == null || numWorkers <= 0 ? DEFAULT_NUM_WORKERS : numWorkers;

        this.maxLogCount =
                maxLogCount == null || maxLogCount <= 0 ? DEFAULT_MAX_LOG_COUNT : maxLogCount;

        this.minLogAgeMillis =
                minLogAgeMillis == null || minLogAgeMillis <= 0 ? DEFAULT_MIN_LOG_AGE_MILLIS : minLogAgeMillis;
    }

    private static int findServerId(Map servers) {
        int serverId = -1;
        try {
            for (final Enumeration e = NetworkInterface.getNetworkInterfaces();
                 e.hasMoreElements();) {
                serverId = findServerId(servers, serverId, e.nextElement());
            }
        } catch (SocketException e) {
            throw new IllegalStateException("failed to retrieve the network interface list", e);
        }

        if (serverId < 0) {
            throw new IllegalStateException(
                    "failed to auto-detect server ID because there is no matching IP address.");
        }

        return serverId;
    }

    private static int findServerId(Map servers, int currentServerId,
                                    NetworkInterface iface) {
        for (final Enumeration ea = iface.getInetAddresses(); ea.hasMoreElements();) {
            currentServerId = findServerId(servers, currentServerId, ea.nextElement());
        }
        return currentServerId;
    }

    private static int findServerId(Map servers, int currentServerId,
                                    InetAddress addr) {
        final String ip = NetUtil.toAddressString(addr, true);
        for (Entry entry : servers.entrySet()) {
            final String zkAddr;
            try {
                zkAddr = NetUtil.toAddressString(InetAddress.getByName(entry.getValue().host()), true);
            } catch (UnknownHostException uhe) {
                throw new IllegalStateException(
                        "failed to resolve the IP address of the server name: " + entry.getValue().host());
            }

            if (zkAddr.equals(ip)) {
                final int serverId = entry.getKey().intValue();
                if (currentServerId < 0) {
                    currentServerId = serverId;
                } else if (currentServerId != serverId) {
                    throw new IllegalStateException(
                            "cannot auto-detect server ID because there are more than one IP address match. " +
                            "Both server ID " + currentServerId + " and " + serverId +
                            " have a matching IP address. Consider specifying server ID explicitly.");
                }
            }
        }
        return currentServerId;
    }

    @Override
    public ReplicationMethod method() {
        return ReplicationMethod.ZOOKEEPER;
    }

    /**
     * Returns the ID of this ZooKeeper server in {@link #servers()}.
     */
    @JsonProperty
    public int serverId() {
        return serverId;
    }

    /**
     * Returns the configuration of this ZooKeeper server in {@link #servers()}.
     */
    public ZooKeeperServerConfig serverConfig() {
        return servers.get(serverId);
    }

    /**
     * Returns the configuration of all ZooKeeper servers, keyed by their server IDs.
     */
    @JsonProperty
    public Map servers() {
        return servers;
    }

    /**
     * Returns the secret string used for authenticating the ZooKeeper peers.
     */
    public String secret() {
        return firstNonNull(convertValue(secret, "replication.secret"), DEFAULT_SECRET);
    }

    /**
     * Returns the additional ZooKeeper properties.
     * If unspecified, an empty {@link Map} is returned.
     */
    @JsonProperty
    public Map additionalProperties() {
        return additionalProperties;
    }

    /**
     * Returns the ZooKeeper timeout, in milliseconds.
     * If unspecified, the default of {@value #DEFAULT_TIMEOUT_MILLIS} is returned.
     */
    @JsonProperty
    public int timeoutMillis() {
        return timeoutMillis;
    }

    /**
     * Returns the number of worker threads dedicated for replication.
     * If unspecified, the default of {@value #DEFAULT_NUM_WORKERS} is returned.
     */
    @JsonProperty
    public int numWorkers() {
        return numWorkers;
    }

    /**
     * Returns the maximum number of log items to keep in ZooKeeper. Note that the log items will still not be
     * removed if they are younger than {@link #minLogAgeMillis()}.
     * If unspecified, the default of {@value #DEFAULT_MAX_LOG_COUNT} is returned.
     */
    @JsonProperty
    public int maxLogCount() {
        return maxLogCount;
    }

    /**
     * Returns the minimum allowed age of log items before they are removed from ZooKeeper.
     * If unspecified, the default of 1 hour is returned.
     */
    @JsonProperty
    public long minLogAgeMillis() {
        return minLogAgeMillis;
    }

    @Override
    public int hashCode() {
        return serverId;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof ZooKeeperReplicationConfig)) {
            return false;
        }

        if (obj == this) {
            return true;
        }

        final ZooKeeperReplicationConfig that = (ZooKeeperReplicationConfig) obj;

        return serverId() == that.serverId() &&
               servers().equals(that.servers()) &&
               additionalProperties().equals(that.additionalProperties()) &&
               timeoutMillis() == that.timeoutMillis() &&
               numWorkers() == that.numWorkers() &&
               maxLogCount() == that.maxLogCount() &&
               minLogAgeMillis() == that.minLogAgeMillis();
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                          .add("serverId", serverId())
                          .add("servers", servers())
                          .add("additionalProperties", additionalProperties())
                          .add("timeoutMillis", timeoutMillis())
                          .add("numWorkers", numWorkers())
                          .add("maxLogCount", maxLogCount())
                          .add("minLogAgeMillis", minLogAgeMillis()).toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy