/*
* 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.client.zookeeper;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooKeeper;
import com.google.common.annotations.VisibleForTesting;
import com.linecorp.armeria.client.Endpoint;
import com.linecorp.armeria.client.endpoint.DynamicEndpointGroup;
import com.linecorp.armeria.client.endpoint.EndpointGroup;
import com.linecorp.armeria.common.zookeeper.NodeValueCodec;
import com.linecorp.armeria.common.zookeeper.ZooKeeperConnector;
import com.linecorp.armeria.common.zookeeper.ZooKeeperException;
import com.linecorp.armeria.common.zookeeper.ZooKeeperListener;
/**
* A ZooKeeper-based {@link EndpointGroup} implementation. This {@link EndpointGroup} retrieves the list of
* {@link Endpoint}s from a ZooKeeper depending on {@link Mode} and updates it when the children of the
* zNode changes.
*/
public class ZooKeeperEndpointGroup extends DynamicEndpointGroup {
private final NodeValueCodec nodeValueCodec;
private final ZooKeeperConnector zooKeeperConnector;
/**
* Create a ZooKeeper-based {@link EndpointGroup}, endpoints will be retrieved from a node's all children's
* node value or a node's value depending on value store type {@link Mode}.
* @param zkConnectionStr a connection string containing a comma separated list of {@code host:port} pairs,
* each corresponding to a ZooKeeper server
* @param zNodePath a zNode path e.g. {@code "/groups/productionGroups"}
* @param sessionTimeout Zookeeper session timeout in milliseconds
* @param mode where data was stored, as a node value or as a node's all children
*/
public ZooKeeperEndpointGroup(String zkConnectionStr, String zNodePath, int sessionTimeout,
Mode mode) {
this(zkConnectionStr, zNodePath, sessionTimeout, NodeValueCodec.DEFAULT, mode);
}
/**
* Create a ZooKeeper-based {@link EndpointGroup}, endpoints will be retrieved from a node's all children's
* node value or a node's value depending on value store type {@link Mode}.
* @param zkConnectionStr a connection string containing a comma separated list of {@code host:port} pairs,
* each corresponding to a ZooKeeper server
* @param zNodePath a zNode path e.g. {@code "/groups/productionGroups"}
* @param sessionTimeout Zookeeper session timeout in milliseconds
* @param nodeValueCodec the nodeValueCodec
* @param mode where data was stored, as a node value or as a node's all children
*/
public ZooKeeperEndpointGroup(String zkConnectionStr, String zNodePath, int sessionTimeout,
NodeValueCodec nodeValueCodec, Mode mode) {
requireNonNull(mode, "mode");
zooKeeperConnector = new ZooKeeperConnector(zkConnectionStr, zNodePath, sessionTimeout,
createListener(mode));
this.nodeValueCodec = requireNonNull(nodeValueCodec, "nodeValueCodec");
zooKeeperConnector.connect();
}
@Override
public void close() {
zooKeeperConnector.close(true);
}
/**
* Value store type. As a node value or as a node's all Children.
*/
public enum Mode {
IN_CHILD_NODES, IN_NODE_VALUE
}
/**
* Create a {@link ZooKeeperListener} listens specific ZooKeeper events.
* @param mode mode
* @return {@link ZooKeeperListener}
*/
private ZooKeeperListener createListener(Mode mode) {
switch (mode) {
case IN_CHILD_NODES:
return new ZooKeeperListener() {
@Override
public void nodeChildChange(Map<String, String> newChildrenValue) {
List<Endpoint> newData = newChildrenValue.values().stream()
.map(nodeValueCodec::decode)
.filter(Objects::nonNull)
.collect(toImmutableList());
final List<Endpoint> prevData = endpoints();
if (prevData == null || !prevData.equals(newData)) {
setEndpoints(newData);
}
}
@Override
public void nodeValueChange(String newValue) {
//ignore value change event
}
@Override
public void connected() {
}
};
case IN_NODE_VALUE:
return new ZooKeeperListener() {
@Override
public void nodeChildChange(Map<String, String> newChildrenValue) {
}
@Override
public void nodeValueChange(String newValue) {
final List<Endpoint> prevData = endpoints();
List<Endpoint> newData = nodeValueCodec.decodeAll(newValue).stream()
.filter(Objects::nonNull)
.collect(toImmutableList());
if (prevData == null || !prevData.equals(newData)) {
setEndpoints(newData);
}
}
@Override
public void connected() {
}
};
default:
throw new ZooKeeperException("unknown listener type: " + mode);
}
}
@VisibleForTesting
void enableStateRecording() {
zooKeeperConnector.enableStateRecording();
}
@VisibleForTesting
ZooKeeper underlyingClient() {
return zooKeeperConnector.underlyingClient();
}
@VisibleForTesting
BlockingQueue<KeeperState> stateQueue() {
return zooKeeperConnector.stateQueue();
}
}