/*
* 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 org.apache.brooklyn.entity.nosql.cassandra;
import static org.testng.Assert.assertEquals;
import java.math.BigInteger;
import java.util.Map;
import java.util.Set;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessSshDriver;
import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
import org.apache.brooklyn.test.EntityTestUtils;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.text.TemplateProcessor;
import org.apache.brooklyn.util.javalang.JavaClassNames;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
public class CassandraDatacenterTest extends BrooklynAppUnitTestSupport {
private static final Logger log = LoggerFactory.getLogger(CassandraDatacenterTest.class);
private LocalhostMachineProvisioningLocation loc;
private CassandraDatacenter cluster;
@BeforeMethod(alwaysRun=true)
@Override
public void setUp() throws Exception {
super.setUp();
loc = mgmt.getLocationManager().createLocation(LocationSpec.create(LocalhostMachineProvisioningLocation.class));
}
@Test
public void testPopulatesInitialSeeds() throws Exception {
cluster = app.createAndManageChild(EntitySpec.create(CassandraDatacenter.class)
.configure(CassandraDatacenter.INITIAL_SIZE, 2)
.configure(CassandraDatacenter.TOKEN_SHIFT, BigInteger.ZERO)
.configure(CassandraDatacenter.DELAY_BEFORE_ADVERTISING_CLUSTER, Duration.ZERO)
.configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class)));
app.start(ImmutableList.of(loc));
EmptySoftwareProcess e1 = (EmptySoftwareProcess) Iterables.get(cluster.getMembers(), 0);
EmptySoftwareProcess e2 = (EmptySoftwareProcess) Iterables.get(cluster.getMembers(), 1);
EntityTestUtils.assertAttributeEqualsEventually(cluster, CassandraDatacenter.CURRENT_SEEDS, ImmutableSet.<Entity>of(e1, e2));
}
@Test(groups="Integration") // because takes approx 2 seconds
public void testUpdatesSeedsOnFailuresAndAdditions() throws Exception {
doTestUpdatesSeedsOnFailuresAndAdditions(true, false);
}
protected void doTestUpdatesSeedsOnFailuresAndAdditions(boolean fast, boolean checkSeedsConstantOnRejoining) throws Exception {
cluster = app.createAndManageChild(EntitySpec.create(CassandraDatacenter.class)
.configure(CassandraDatacenter.INITIAL_SIZE, 2)
.configure(CassandraDatacenter.TOKEN_SHIFT, BigInteger.ZERO)
.configure(CassandraDatacenter.DELAY_BEFORE_ADVERTISING_CLUSTER, Duration.ZERO)
.configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class)));
app.start(ImmutableList.of(loc));
EmptySoftwareProcess e1 = (EmptySoftwareProcess) Iterables.get(cluster.getMembers(), 0);
EmptySoftwareProcess e2 = (EmptySoftwareProcess) Iterables.get(cluster.getMembers(), 1);
EntityTestUtils.assertAttributeEqualsEventually(cluster, CassandraDatacenter.CURRENT_SEEDS, ImmutableSet.<Entity>of(e1, e2));
log.debug("Test "+JavaClassNames.niceClassAndMethod()+", cluster "+cluster+" has "+cluster.getMembers()+"; e1="+e1+" e2="+e2);
// calling the driver stop for this entity will cause SERVICE_UP to become false, and stay false
// (and that's all it does, incidentally); if we just set the attribute it will become true on serviceUp sensor feed
log.info("Simulating failure of cassandra node "+e1);
((EmptySoftwareProcess)e1).getDriver().stop();
// not necessary, but speeds things up:
if (fast)
e1.sensors().set(Attributes.SERVICE_UP, false);
EntityTestUtils.assertAttributeEqualsEventually(cluster, CassandraDatacenter.CURRENT_SEEDS, ImmutableSet.<Entity>of(e2));
cluster.resize(3);
EmptySoftwareProcess e3 = (EmptySoftwareProcess) Iterables.getOnlyElement(Sets.difference(ImmutableSet.copyOf(cluster.getMembers()), ImmutableSet.of(e1,e2)));
log.debug("Test "+JavaClassNames.niceClassAndMethod()+", cluster "+cluster+" has "+cluster.getMembers()+"; e3="+e3);
try {
EntityTestUtils.assertAttributeEqualsEventually(cluster, CassandraDatacenter.CURRENT_SEEDS, ImmutableSet.<Entity>of(e2, e3));
} finally {
log.debug("Test "+JavaClassNames.niceClassAndMethod()+", cluster "+cluster+" has "+cluster.getMembers()+"; seeds "+cluster.getAttribute(CassandraDatacenter.CURRENT_SEEDS));
}
if (!checkSeedsConstantOnRejoining) {
// cluster should not revert to e1+e2, simply because e1 has come back; but e1 should rejoin the group
// (not that important, and waits for 1s, so only done as part of integration)
((EmptySoftwareProcessSshDriver)(((EmptySoftwareProcess)e1).getDriver())).launch();
if (fast)
e1.sensors().set(Attributes.SERVICE_UP, true);
EntityTestUtils.assertAttributeEqualsEventually(e1, CassandraNode.SERVICE_UP, true);
EntityTestUtils.assertAttributeEqualsContinually(cluster, CassandraDatacenter.CURRENT_SEEDS, ImmutableSet.<Entity>of(e2, e3));
}
}
@Test
public void testPopulatesInitialTokens() throws Exception {
cluster = app.createAndManageChild(EntitySpec.create(CassandraDatacenter.class)
.configure(CassandraDatacenter.INITIAL_SIZE, 2)
.configure(CassandraDatacenter.TOKEN_SHIFT, BigInteger.ZERO)
.configure(CassandraDatacenter.DELAY_BEFORE_ADVERTISING_CLUSTER, Duration.ZERO)
.configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class)));
app.start(ImmutableList.of(loc));
Set<BigInteger> tokens = Sets.newLinkedHashSet();
for (Entity member : cluster.getMembers()) {
Set<BigInteger > memberTokens = member.getConfig(CassandraNode.TOKENS);
if (memberTokens != null) tokens.addAll(memberTokens);
}
assertEquals(tokens, ImmutableSet.of(new BigInteger("-9223372036854775808"), BigInteger.ZERO));
}
@Test
public void testDoesNotPopulateInitialTokens() throws Exception {
cluster = app.createAndManageChild(EntitySpec.create(CassandraDatacenter.class)
.configure(CassandraDatacenter.INITIAL_SIZE, 2)
.configure(CassandraDatacenter.USE_VNODES, true)
.configure(CassandraDatacenter.DELAY_BEFORE_ADVERTISING_CLUSTER, Duration.ZERO)
.configure(CassandraDatacenter.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class)));
app.start(ImmutableList.of(loc));
Set<BigInteger> tokens = Sets.newLinkedHashSet();
for (Entity member : cluster.getMembers()) {
Set<BigInteger > memberTokens = member.getConfig(CassandraNode.TOKENS);
if (memberTokens != null) tokens.addAll(memberTokens);
}
assertEquals(tokens, ImmutableSet.of());
}
public static class MockInputForTemplate {
public BigInteger getToken() { return new BigInteger("-9223372036854775808"); }
public String getTokensAsString() { return "" + getToken(); }
public int getNumTokensPerNode() { return 1; }
public String getSeeds() { return ""; }
public int getGossipPort() { return 1234; }
public int getSslGossipPort() { return 1234; }
public int getThriftPort() { return 1234; }
public int getNativeTransportPort() { return 1234; }
public String getClusterName() { return "Mock"; }
public String getEndpointSnitchName() { return ""; }
public String getListenAddress() { return "0"; }
public String getBroadcastAddress() { return "0"; }
public String getRpcAddress() { return "0"; }
public String getRunDir() { return "/tmp/mock"; }
}
@Test
public void testBigIntegerFormattedCorrectly() {
Map<String, Object> substitutions = ImmutableMap.<String, Object>builder()
.put("entity", new MockInputForTemplate())
.put("driver", new MockInputForTemplate())
.build();
String templatedUrl = CassandraNode.CASSANDRA_CONFIG_TEMPLATE_URL.getDefaultValue();
String url = TemplateProcessor.processTemplateContents(templatedUrl, ImmutableMap.of("entity", ImmutableMap.of("majorMinorVersion", "1.2")));
String templateContents = new ResourceUtils(this).getResourceAsString(url);
String processedTemplate = TemplateProcessor.processTemplateContents(templateContents, substitutions);
Assert.assertEquals(processedTemplate.indexOf("775,808"), -1);
Assert.assertTrue(processedTemplate.indexOf("-9223372036854775808") > 0);
}
@Test(groups="Integration") // because takes approx 30 seconds
public void testUpdatesSeedsFastishManyTimes() throws Exception {
final int COUNT = 20;
for (int i=0; i<COUNT; i++) {
log.info("Test "+JavaClassNames.niceClassAndMethod()+", iteration "+(i+1)+" of "+COUNT);
try {
doTestUpdatesSeedsOnFailuresAndAdditions(true, true);
tearDown();
setUp();
} catch (Exception e) {
log.warn("Error in "+JavaClassNames.niceClassAndMethod()+", iteration "+(i+1)+" of "+COUNT, e);
throw e;
}
}
}
@Test(groups="Integration") // because takes approx 5 seconds
public void testUpdateSeedsSlowAndRejoining() throws Exception {
final int COUNT = 1;
for (int i=0; i<COUNT; i++) {
log.info("Test "+JavaClassNames.niceClassAndMethod()+", iteration "+(i+1)+" of "+COUNT);
doTestUpdatesSeedsOnFailuresAndAdditions(false, true);
tearDown();
setUp();
}
}
}