/**
* 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 org.apache.aurora.scheduler.discovery;
import com.google.common.collect.ImmutableSet;
import org.apache.aurora.codec.ThriftBinaryCodec;
import org.apache.aurora.common.thrift.ServiceInstance;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.utils.ZKPaths;
import org.apache.zookeeper.CreateMode;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class CuratorServiceGroupMonitorTest extends BaseCuratorDiscoveryTest {
@Test
public void testNominalLifecycle() throws Exception {
startGroupMonitor();
getGroupMonitor().close();
}
@Test
public void testNeverStarted() throws Exception {
// Close on a non-started or failed-to-start monitor should be allowed.
getGroupMonitor().close();
}
@Test
public void testAlreadyStopped() throws Exception {
startGroupMonitor();
getGroupMonitor().close();
// Multiple closes on a started monitor should be allowed.
getGroupMonitor().close();
}
@Test
public void testNoHosts() throws Exception {
assertEquals(ImmutableSet.of(), getGroupMonitor().get());
startGroupMonitor();
assertEquals(ImmutableSet.of(), getGroupMonitor().get());
}
@Test
public void testHostUpdates() throws Exception {
startGroupMonitor();
ServiceInstance one = serviceInstance("one");
String onePath = createMember(one);
ServiceInstance two = serviceInstance("two");
String twoPath = createMember(two);
assertEquals(ImmutableSet.of(one, two), getGroupMonitor().get());
deleteChild(twoPath);
assertEquals(ImmutableSet.of(one), getGroupMonitor().get());
deleteChild(onePath);
ServiceInstance three = serviceInstance("three");
String threePath = createMember(three);
assertEquals(ImmutableSet.of(three), getGroupMonitor().get());
deleteChild(threePath);
assertEquals(ImmutableSet.of(), getGroupMonitor().get());
}
@Test
public void testMixedNodes() throws Exception {
startGroupMonitor();
String nonMemberPath = createNonMember();
assertEquals(ImmutableSet.of(), getGroupMonitor().get());
ServiceInstance member = serviceInstance("member");
String memberPath = createMember(member);
assertEquals(ImmutableSet.of(member), getGroupMonitor().get());
deleteChild(memberPath);
assertEquals(ImmutableSet.of(), getGroupMonitor().get());
deleteChild(nonMemberPath);
assertEquals(ImmutableSet.of(), getGroupMonitor().get());
}
@Test
public void testInvalidMemberNode() throws Exception {
startGroupMonitor();
createMember(ThriftBinaryCodec.encode(serviceInstance("invalid")));
ServiceInstance member = serviceInstance("member");
createMember(member);
// Invalid member should be ignored.
assertEquals(ImmutableSet.of(member), getGroupMonitor().get());
}
@Test
public void testStartBlocksOnInitialMembership() throws Exception {
ServiceInstance one = serviceInstance("one");
createMember(one, false /* waitForGroupEvent */);
ServiceInstance two = serviceInstance("two");
createMember(two, false /* waitForGroupEvent */);
// Not started yet, should see no group members.
assertEquals(ImmutableSet.of(), getGroupMonitor().get());
startGroupMonitor();
assertEquals(ImmutableSet.of(one, two), getGroupMonitor().get());
}
private void deleteChild(String twoPath) throws Exception {
getClient().delete().forPath(twoPath);
expectGroupEvent(PathChildrenCacheEvent.Type.CHILD_REMOVED);
}
private String createMember(ServiceInstance serviceInstance) throws Exception {
return createMember(serviceInstance, true /* waitForGroupEvent */);
}
private String createMember(ServiceInstance serviceInstance, boolean waitForGroupEvent)
throws Exception {
return createMember(serialize(serviceInstance), waitForGroupEvent);
}
private String createMember(byte[] nodeData) throws Exception {
return createMember(nodeData, true /* waitForGroupEvent */);
}
private String createMember(byte[] nodeData, boolean waitForGroupEvent) throws Exception {
String path = getClient().create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(ZKPaths.makePath(GROUP_PATH, MEMBER_TOKEN), nodeData);
if (waitForGroupEvent) {
expectGroupEvent(PathChildrenCacheEvent.Type.CHILD_ADDED);
}
return path;
}
private String createNonMember() throws Exception {
String path = getClient().create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(ZKPaths.makePath(GROUP_PATH, "not-a-member"));
expectGroupEvent(PathChildrenCacheEvent.Type.CHILD_ADDED);
return path;
}
}