publish connection url sensor, use that in pillowfight; and ensure nodes have their public hostname, since previously the first node uses its private ip and thus rebalance and other REST commands don't work reliably, fixing rebalance; and use better timings so it starts faster. now couchbase w pillowfight working very nicely
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/1064cd5d Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/1064cd5d Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/1064cd5d Branch: refs/heads/master Commit: 1064cd5df9cd3c49856efea7a1622745b0dd4ea6 Parents: 94bf299 Author: Alex Heneveld <[email protected]> Authored: Thu Aug 28 22:36:06 2014 -0400 Committer: Alex Heneveld <[email protected]> Committed: Mon Sep 1 17:07:28 2014 +0100 ---------------------------------------------------------------------- .../main/java/brooklyn/util/http/HttpTool.java | 9 +- .../brooklynnode/BrooklynEntityMirrorImpl.java | 3 +- .../nosql/couchbase/CouchbaseCluster.java | 9 +- .../nosql/couchbase/CouchbaseClusterImpl.java | 98 ++++++++++++++------ .../nosql/couchbase/CouchbaseNodeImpl.java | 49 ++++++++++ .../nosql/couchbase/CouchbaseNodeSshDriver.java | 30 +++--- .../entity/nosql/couchbase/pillowfight.yaml | 74 ++++++--------- .../src/test/resources/couchbase-w-loadgen.yaml | 54 +++++++++++ .../test/resources/couchbase-w-pillowfight.yaml | 35 +++++++ .../launcher/src/test/resources/couchbase.yaml | 13 ++- 10 files changed, 280 insertions(+), 94 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/core/src/main/java/brooklyn/util/http/HttpTool.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/http/HttpTool.java b/core/src/main/java/brooklyn/util/http/HttpTool.java index 440f699..61a5b7a 100644 --- a/core/src/main/java/brooklyn/util/http/HttpTool.java +++ b/core/src/main/java/brooklyn/util/http/HttpTool.java @@ -26,12 +26,14 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Map; +import org.apache.commons.codec.binary.Base64; import org.apache.http.ConnectionReuseStrategy; import org.apache.http.HttpEntity; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; @@ -159,7 +161,8 @@ public class HttpTool { return this; } public HttpClient build() { - final DefaultHttpClient httpClient = new DefaultHttpClient(clientConnectionManager, httpParams); + final DefaultHttpClient httpClient = new DefaultHttpClient(clientConnectionManager); + httpClient.setParams(httpParams); // support redirects for POST (similar to `curl --post301 -L`) // http://stackoverflow.com/questions/3658721/httpclient-4-error-302-how-to-redirect @@ -327,4 +330,8 @@ public class HttpTool { public static boolean isStatusCodeHealthy(int code) { return (code>=200 && code<=299); } + public static String toBasicAuthorizationValue(UsernamePasswordCredentials credentials) { + return "Basic "+Base64.encodeBase64String( (credentials.getUserName()+":"+credentials.getPassword()).getBytes() ); + } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java index 24c3eeb..2743e54 100644 --- a/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java +++ b/software/base/src/main/java/brooklyn/entity/brooklynnode/BrooklynEntityMirrorImpl.java @@ -53,6 +53,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Suppliers; +import com.google.common.net.MediaType; import com.google.gson.Gson; public class BrooklynEntityMirrorImpl extends AbstractEntity implements BrooklynEntityMirror { @@ -137,7 +138,7 @@ public class BrooklynEntityMirrorImpl extends AbstractEntity implements Brooklyn HttpToolResponse result = null; byte[] content; try { - result = HttpTool.httpPost(client, uri, MutableMap.of(com.google.common.net.HttpHeaders.CONTENT_TYPE, "application/json"), + result = HttpTool.httpPost(client, uri, MutableMap.of(com.google.common.net.HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString()), Jsonya.of(args).toString().getBytes()); content = result.getContent(); } catch (Exception e) { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseCluster.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseCluster.java b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseCluster.java index c478b07..25a2b8d 100644 --- a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseCluster.java +++ b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseCluster.java @@ -56,14 +56,17 @@ public interface CouchbaseCluster extends DynamicCluster { -1); @SetFromFlag("delayBeforeAdvertisingCluster") - ConfigKey<Duration> DELAY_BEFORE_ADVERTISING_CLUSTER = ConfigKeys.newConfigKey(Duration.class, "couchbase.cluster.delayBeforeAdvertisingCluster", "Delay after cluster is started before checking and advertising its availability", Duration.THIRTY_SECONDS); + ConfigKey<Duration> DELAY_BEFORE_ADVERTISING_CLUSTER = ConfigKeys.newConfigKey(Duration.class, "couchbase.cluster.delayBeforeAdvertisingCluster", "Delay after cluster is started before checking and advertising its availability", Duration.TEN_SECONDS); - @SetFromFlag("serviceUpTimeOut") - ConfigKey<Duration> SERVICE_UP_TIME_OUT = ConfigKeys.newConfigKey(Duration.class, "couchbase.cluster.serviceUpTimeOut", "Service up time out duration for all the couchbase nodes", Duration.seconds(3 * 60)); + // TODO not sure if this is needed; previously waited 3m (SERVICE_UP_TIME_OUT) but that seems absurdly long + @SetFromFlag("postStartStabilizationDelay") + ConfigKey<Duration> NODES_STARTED_STABILIZATION_DELAY = ConfigKeys.newConfigKey(Duration.class, "couchbase.cluster.postStartStabilizationDelay", "Delay after nodes have been started before treating it as a cluster", Duration.TEN_SECONDS); @SuppressWarnings("serial") AttributeSensor<List<String>> COUCHBASE_CLUSTER_UP_NODE_ADDRESSES = Sensors.newSensor(new TypeToken<List<String>>() {}, "couchbase.cluster.node.addresses", "List of host:port of all active nodes in the cluster (http admin port, and public hostname/IP)"); + AttributeSensor<String> COUCHBASE_CLUSTER_CONNECTION_URL = Sensors.newStringSensor( + "couchbase.cluster.connection.url", "Couchbase-style URL to connect to the cluster (e.g. http://127.0.0.1:8091/ or couchbase://10.0.0.1,10.0.0.2/)"); // Interesting stats AttributeSensor<Double> OPS_PER_NODE = Sensors.newDoubleSensor("couchbase.stats.cluster.per.node.ops", http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java index 69b038a..6abf2f7 100644 --- a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java +++ b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseClusterImpl.java @@ -20,7 +20,6 @@ package brooklyn.entity.nosql.couchbase; import static brooklyn.util.JavaGroovyEquivalents.groovyTruth; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -37,7 +36,6 @@ import brooklyn.entity.Entity; import brooklyn.entity.basic.Attributes; import brooklyn.entity.basic.Entities; import brooklyn.entity.basic.EntityInternal; -import brooklyn.entity.basic.Lifecycle; import brooklyn.entity.basic.QuorumCheck; import brooklyn.entity.basic.ServiceStateLogic; import brooklyn.entity.group.AbstractMembershipTrackingPolicy; @@ -50,14 +48,18 @@ import brooklyn.event.feed.http.HttpFeed; import brooklyn.event.feed.http.HttpPollConfig; import brooklyn.event.feed.http.HttpValueFunctions; import brooklyn.event.feed.http.JsonFunctions; -import brooklyn.location.Location; import brooklyn.policy.PolicySpec; +import brooklyn.util.collections.CollectionFunctionals; import brooklyn.util.collections.MutableSet; +import brooklyn.util.exceptions.Exceptions; import brooklyn.util.guava.Functionals; +import brooklyn.util.guava.IfFunctions; +import brooklyn.util.math.MathPredicates; import brooklyn.util.task.DynamicTasks; import brooklyn.util.task.TaskBuilder; import brooklyn.util.task.Tasks; import brooklyn.util.text.ByteSizeStrings; +import brooklyn.util.text.StringFunctions; import brooklyn.util.text.Strings; import brooklyn.util.time.Time; @@ -87,17 +89,23 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas .transforming(COUCHBASE_CLUSTER_UP_NODES) .from(this) .publishing(COUCHBASE_CLUSTER_UP_NODE_ADDRESSES) - .computing(new Function<Set<Entity>, List<String>>() { - @Override public List<String> apply(Set<Entity> input) { - List<String> addresses = Lists.newArrayList(); - for (Entity entity : input) { - addresses.add(String.format("%s:%s", entity.getAttribute(Attributes.ADDRESS), - entity.getAttribute(CouchbaseNode.COUCHBASE_WEB_ADMIN_PORT))); - } - return addresses; - } - }).build() - ); + .computing(new ListOfHostAndPort()).build() ); + addEnricher( + Enrichers.builder() + .transforming(COUCHBASE_CLUSTER_UP_NODE_ADDRESSES) + .from(this) + .publishing(COUCHBASE_CLUSTER_CONNECTION_URL) + .computing( + IfFunctions.<List<String>>ifPredicate( + Predicates.compose(MathPredicates.lessThan(getConfig(CouchbaseCluster.INITIAL_QUORUM_SIZE)), + CollectionFunctionals.sizeFunction(0)) ) + .value((String)null) + .defaultApply( + Functionals.chain( + CollectionFunctionals.<String,List<String>>limit(4), + StringFunctions.joiner(","), + StringFunctions.formatter("http://%s/"))) ) + .build() ); Map<? extends AttributeSensor<? extends Number>, ? extends AttributeSensor<? extends Number>> enricherSetup = ImmutableMap.<AttributeSensor<? extends Number>, AttributeSensor<? extends Number>>builder() @@ -143,19 +151,22 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas } @Override - public void start(Collection<? extends Location> locations) { - super.start(locations); + protected void doStart() { + super.doStart(); connectSensors(); setAttribute(BUCKET_CREATION_IN_PROGRESS, false); //start timeout before adding the servers - Time.sleep(getConfig(SERVICE_UP_TIME_OUT)); + Tasks.setBlockingDetails("Pausing while Couchbase stabilizes"); + Time.sleep(getConfig(NODES_STARTED_STABILIZATION_DELAY)); Optional<Set<Entity>> upNodes = Optional.<Set<Entity>>fromNullable(getAttribute(COUCHBASE_CLUSTER_UP_NODES)); if (upNodes.isPresent() && !upNodes.get().isEmpty()) { + Tasks.setBlockingDetails("Adding servers to Couchbase"); + //TODO: select a new primary node if this one fails Entity primaryNode = upNodes.get().iterator().next(); ((EntityInternal) primaryNode).setAttribute(CouchbaseNode.IS_PRIMARY_NODE, true); @@ -178,9 +189,16 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas ((CouchbaseNode)getPrimaryNode()).rebalance(); - if (Optional.fromNullable(CREATE_BUCKETS).isPresent()) { - createBuckets(); - DependentConfiguration.waitInTaskForAttributeReady(this, CouchbaseCluster.BUCKET_CREATION_IN_PROGRESS, Predicates.equalTo(false)); + if (getConfig(CREATE_BUCKETS)!=null) { + try { + Tasks.setBlockingDetails("Creating buckets in Couchbase"); + + createBuckets(); + DependentConfiguration.waitInTaskForAttributeReady(this, CouchbaseCluster.BUCKET_CREATION_IN_PROGRESS, Predicates.equalTo(false)); + + } finally { + Tasks.resetBlockingDetails(); + } } setAttribute(IS_CLUSTER_INITIALIZED, true); @@ -190,9 +208,8 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas //check Repeater. } } else { - ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE); + throw new IllegalStateException("No up nodes available after starting"); } - } @Override @@ -209,6 +226,17 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas .configure("group", this)); } + private final static class ListOfHostAndPort implements Function<Set<Entity>, List<String>> { + @Override public List<String> apply(Set<Entity> input) { + List<String> addresses = Lists.newArrayList(); + for (Entity entity : input) { + addresses.add(String.format("%s:%s", entity.getAttribute(Attributes.ADDRESS), + entity.getAttribute(CouchbaseNode.COUCHBASE_WEB_ADMIN_PORT))); + } + return addresses; + } + } + public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy { @Override protected void onEntityChange(Entity member) { ((CouchbaseClusterImpl)entity).onServerPoolMemberChanged(member); @@ -311,14 +339,25 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas @Override protected void initEnrichers() { + addEnricher(Enrichers.builder().updatingMap(ServiceStateLogic.SERVICE_NOT_UP_INDICATORS) + .from(COUCHBASE_CLUSTER_UP_NODES) + .computing(new Function<Set<Entity>, Object>() { + @Override + public Object apply(Set<Entity> input) { + if (input==null) return "Couchbase up nodes not set"; + if (input.isEmpty()) return "No Couchbase up nodes"; + if (input.size() < getQuorumSize()) return "Couchbase up nodes not quorate"; + return null; + } + }).build()); + if (getConfigRaw(UP_QUORUM_CHECK, false).isAbsent()) { class CouchbaseQuorumCheck implements QuorumCheck { @Override public boolean isQuorate(int sizeHealthy, int totalSize) { // check members count passed in AND the sensor if (sizeHealthy < getQuorumSize()) return false; - Set<Entity> upNodes = getAttribute(COUCHBASE_CLUSTER_UP_NODES); - return (upNodes != null && !upNodes.isEmpty() && upNodes.size() >= getQuorumSize()); + return true; } } setConfig(UP_QUORUM_CHECK, new CouchbaseQuorumCheck()); @@ -370,7 +409,8 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas for (Map<String, Object> bucketMap : bucketsToCreate) { String bucketName = bucketMap.containsKey("bucket") ? (String) bucketMap.get("bucket") : "default"; String bucketType = bucketMap.containsKey("bucket-type") ? (String) bucketMap.get("bucket-type") : "couchbase"; - Integer bucketPort = bucketMap.containsKey("bucket-port") ? (Integer) bucketMap.get("bucket-port") : 11222; + // default bucket must be on this port; other buckets can (must) specify their own (unique) port + Integer bucketPort = bucketMap.containsKey("bucket-port") ? (Integer) bucketMap.get("bucket-port") : 11211; Integer bucketRamSize = bucketMap.containsKey("bucket-ramsize") ? (Integer) bucketMap.get("bucket-ramsize") : 200; Integer bucketReplica = bucketMap.containsKey("bucket-replica") ? (Integer) bucketMap.get("bucket-replica") : 1; @@ -415,9 +455,13 @@ public class CouchbaseClusterImpl extends DynamicClusterImpl implements Couchbas .onFailureOrException(new Function<Object, Boolean>() { @Override public Boolean apply(Object input) { - if (((brooklyn.util.http.HttpToolResponse) input).getResponseCode() == 404) { - return true; + if (input instanceof brooklyn.util.http.HttpToolResponse) { + if (((brooklyn.util.http.HttpToolResponse) input).getResponseCode() == 404) { + return true; + } } + if (input instanceof Throwable) + Exceptions.propagate((Throwable)input); throw new IllegalStateException("Unexpected response when creating bucket:" + input); } })) http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeImpl.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeImpl.java index 11e36bc..80d563f 100644 --- a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeImpl.java +++ b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeImpl.java @@ -20,10 +20,15 @@ package brooklyn.entity.nosql.couchbase; import static java.lang.String.format; +import java.net.URI; import java.util.Collection; import java.util.Map; import java.util.Set; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import brooklyn.entity.basic.Attributes; import brooklyn.entity.basic.SoftwareProcessImpl; import brooklyn.event.AttributeSensor; @@ -36,22 +41,29 @@ import brooklyn.event.feed.http.JsonFunctions; import brooklyn.location.MachineProvisioningLocation; import brooklyn.location.access.BrooklynAccessUtils; import brooklyn.location.cloud.CloudLocationConfig; +import brooklyn.util.collections.MutableMap; import brooklyn.util.collections.MutableSet; import brooklyn.util.config.ConfigBag; +import brooklyn.util.exceptions.Exceptions; import brooklyn.util.guava.Functionals; import brooklyn.util.guava.MaybeFunctions; import brooklyn.util.guava.TypeTokens; +import brooklyn.util.http.HttpTool; import brooklyn.util.http.HttpToolResponse; +import brooklyn.util.net.Urls; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Preconditions; import com.google.common.net.HostAndPort; +import com.google.common.net.MediaType; import com.google.gson.JsonArray; import com.google.gson.JsonElement; public class CouchbaseNodeImpl extends SoftwareProcessImpl implements CouchbaseNode { + private static final Logger log = LoggerFactory.getLogger(CouchbaseNodeImpl.class); + HttpFeed httpFeed; @Override @@ -140,6 +152,43 @@ public class CouchbaseNodeImpl extends SoftwareProcessImpl implements CouchbaseN .onFailureOrException(Functions.<T>constant(null)); } + @Override + protected void postStart() { + super.postStart(); + renameServerToPublicHostname(); + } + + protected void renameServerToPublicHostname() { + // http://docs.couchbase.com/couchbase-manual-2.5/cb-install/#couchbase-getting-started-hostnames + URI apiUri = null; + try { + String hostname = getAttribute(Attributes.HOSTNAME); + String port = ""+getAttribute(COUCHBASE_WEB_ADMIN_PORT); + apiUri = new URI("http://"+hostname+":"+port+"/node/controller/rename"); + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials( + getConfig(COUCHBASE_ADMIN_USERNAME), getConfig(COUCHBASE_ADMIN_PASSWORD)); + HttpToolResponse response = HttpTool.httpPost(HttpTool.httpClientBuilder() + // the uri is required by the HttpClientBuilder in order to set the AuthScope of the credentials + .uri(apiUri) + .credentials(credentials) + .build(), + apiUri, + MutableMap.of( + com.google.common.net.HttpHeaders.CONTENT_TYPE, MediaType.FORM_DATA.toString(), + com.google.common.net.HttpHeaders.ACCEPT, "*/*", + // this appears needed; without it we get org.apache.http.NoHttpResponseException !? + com.google.common.net.HttpHeaders.AUTHORIZATION, HttpTool.toBasicAuthorizationValue(credentials)), + ("hostname="+Urls.encode(hostname)).getBytes()); + log.debug("Renamed Couchbase server "+this+" via "+apiUri+": "+response); + if (!HttpTool.isStatusCodeHealthy(response.getResponseCode())) { + log.warn("Invalid response code, renaming "+apiUri+": "+response); + } + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Error renaming server, using "+apiUri+": "+e, e); + } + } + public void connectSensors() { super.connectSensors(); connectServiceUpIsRunning(); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeSshDriver.java ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeSshDriver.java b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeSshDriver.java index 67fa30a..6dc3cfb 100644 --- a/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeSshDriver.java +++ b/software/nosql/src/main/java/brooklyn/entity/nosql/couchbase/CouchbaseNodeSshDriver.java @@ -195,7 +195,7 @@ public class CouchbaseNodeSshDriver extends AbstractSoftwareProcessSshDriver imp } private String getWebPort() { - return entity.getConfig(CouchbaseNode.COUCHBASE_WEB_ADMIN_PORT).iterator().next().toString(); + return ""+entity.getAttribute(CouchbaseNode.COUCHBASE_WEB_ADMIN_PORT); } private String getCouchbaseHostnameAndCredentials() { @@ -209,25 +209,27 @@ public class CouchbaseNodeSshDriver extends AbstractSoftwareProcessSshDriver imp private String getClusterInitRamSize() { return entity.getConfig(CouchbaseNode.COUCHBASE_CLUSTER_INIT_RAM_SIZE).toString(); } - + @Override public void rebalance() { + entity.setAttribute(CouchbaseNode.REBALANCE_STATUS, "Rebalance Started"); newScript("rebalance") .body.append( couchbaseCli("rebalance") + getCouchbaseHostnameAndCredentials()) .failOnNonZeroResultCode() .execute(); - entity.setAttribute(CouchbaseNode.REBALANCE_STATUS, "Rebalance Started"); - // wait until the re-balance is complete + + // wait until the re-balance is started + // (if it's quick, this might miss it, but it will only block for 30s if so) Repeater.create() - .every(Duration.millis(500)) + .backoff(Duration.millis(10), 2, Duration.millis(500)) .limitTimeTo(Duration.THIRTY_SECONDS) .until(new Callable<Boolean>() { @Override public Boolean call() throws Exception { - for (String nodeHostName : CouchbaseNodeSshDriver.this.getNodeHostNames()) { - if (isNodeRebalancing(nodeHostName)) { + for (String nodeHostAndPort : CouchbaseNodeSshDriver.this.getNodesHostAndPort()) { + if (isNodeRebalancing(nodeHostAndPort)) { return true; } } @@ -235,14 +237,16 @@ public class CouchbaseNodeSshDriver extends AbstractSoftwareProcessSshDriver imp } }) .run(); + + // then wait until the re-balance is complete Repeater.create() .every(Duration.FIVE_SECONDS) .limitTimeTo(Duration.FIVE_MINUTES) .until(new Callable<Boolean>() { @Override public Boolean call() throws Exception { - for (String nodeHostName : CouchbaseNodeSshDriver.this.getNodeHostNames()) { - if (isNodeRebalancing(nodeHostName)) { + for (String nodeHostAndPort : getNodesHostAndPort()) { + if (isNodeRebalancing(nodeHostAndPort)) { return false; } } @@ -253,7 +257,7 @@ public class CouchbaseNodeSshDriver extends AbstractSoftwareProcessSshDriver imp log.info("rebalanced cluster via primary node {}", getEntity()); } - private Iterable<String> getNodeHostNames() throws URISyntaxException { + private Iterable<String> getNodesHostAndPort() throws URISyntaxException { Function<JsonElement, Iterable<String>> getNodesAsList = new Function<JsonElement, Iterable<String>>() { @Override public Iterable<String> apply(JsonElement input) { if (input == null) { @@ -276,12 +280,12 @@ public class CouchbaseNodeSshDriver extends AbstractSoftwareProcessSshDriver imp ).apply(nodesResponse); } - private boolean isNodeRebalancing(String nodeHostName) throws URISyntaxException { - HttpToolResponse response = getAPIResponse("http://" + nodeHostName + "/pools/nodes/rebalanceProgress"); + private boolean isNodeRebalancing(String nodeHostAndPort) throws URISyntaxException { + HttpToolResponse response = getAPIResponse("http://" + nodeHostAndPort + "/pools/nodes/rebalanceProgress"); if (response.getResponseCode() != 200) { throw new IllegalStateException("failed to rebalance cluster: " + response); } - return !HttpValueFunctions.jsonContents("status", String.class).apply(response).equals("none"); + return !"none".equals(HttpValueFunctions.jsonContents("status", String.class).apply(response)); } private HttpToolResponse getAPIResponse(String uri) throws URISyntaxException { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/software/nosql/src/main/resources/brooklyn/entity/nosql/couchbase/pillowfight.yaml ---------------------------------------------------------------------- diff --git a/software/nosql/src/main/resources/brooklyn/entity/nosql/couchbase/pillowfight.yaml b/software/nosql/src/main/resources/brooklyn/entity/nosql/couchbase/pillowfight.yaml index 9442725..01f4027 100644 --- a/software/nosql/src/main/resources/brooklyn/entity/nosql/couchbase/pillowfight.yaml +++ b/software/nosql/src/main/resources/brooklyn/entity/nosql/couchbase/pillowfight.yaml @@ -19,73 +19,59 @@ services: - type: brooklyn.entity.basic.VanillaSoftwareProcess name: CBC Pillowfight - checkRunning.command: "" - stop.command: "" launch.command: | sudo wget -O/etc/apt/sources.list.d/couchbase.list http://packages.couchbase.com/ubuntu/couchbase-ubuntu1204.list sudo wget -O- http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add - sudo apt-get update sudo apt-get install -y libcouchbase2-libevent libcouchbase-dev libcouchbase2-bin + provisioning.properties: + # CentOS requires a different launch command, see below + osFamily: ubuntu + checkRunning.command: "" + stop.command: "" + + brooklyn.config: + base_url: http://127.0.0.1:8091/ + brooklyn.initializers: - type: brooklyn.entity.software.ssh.SshCommandEffector brooklyn.config: - name: cbcPillowFight + name: pillow_fight description: runs cbc pillowfight - command: > - cbc pillowfight - `if [ -n "$host" ]; then echo -h $host; fi` - `if [ -n "$bucket" ]; then echo -b $bucket; fi` - `if [ -n "$username" ]; then echo -u $username; fi` - `if [ -n "$password" ]; then echo -p $password; fi` - `if [ -n "$iterations" ]; then echo -i $iterations; fi` - `if [ -n "$numItems" ]; then echo -I $numItems; fi` - `if [ -n "$keyPrefix" ]; then echo -p $keyPrefix; fi` - `if [ -n "$numThreads" ]; then echo -t $numThreads; fi` - `if [ -n "$numInstances" ]; then echo -Q $numInstances; fi` - `if [ -n "$randomSeed" ]; then echo -s $randomSeed; fi` - `if [ -n "$minSize" ]; then echo -m $minSize; fi` - `if [ -n "$maxSize" ]; then echo -M $maxSize; fi` + command: | + cbc-pillowfight -U ${base_url}${bucket} \ + `if [ -n "$username" ]; then echo -u $username; fi` \ + `if [ -n "$password" ]; then echo -P $password; fi` \ + `if [ -n "$num_cycles" ]; then echo -c $num_cycles; fi` \ + `if [ -n "$min_size" ]; then echo -m $min_size; fi` \ + `if [ -n "$max_size" ]; then echo -M $max_size; fi` \ + `if [ -n "$ratio" ]; then echo -r $ratio; fi` parameters: - host: - description: list of hosts to connect to - defaultValue: 127.0.0.1:8091 + base_url: + description: base URL (http or couchbases) and list of hosts/port to connect to, including trailing slash + defaultValue: $brooklyn:config("base_url") bucket: description: bucket to use defaultValue: default username: - description: username used for authentication to the cluster + description: username to authenticate to the bucket password: - description: password used for authentication to the cluster - iterations: + description: password to authenticate to the bucket + num_cycles: description: number of iterations to run - defaultValue: 1000 - numItems: - description: number of items to operate on - defaultValue: 1000 - keyPrefix: - description: prefix for keys - numThreads: - description: number of threads to use - defaultValue: 1 - numInstances: - description: number of connection instances to put into the shared connection pool defaultValue: 1 - randomSeed: - description: random seed - defaultValue: 0 + min_size: + description: minimum payload size + defaultValue: 50 + max_size: + description: maximum payload size + defaultValue: 5120 ratio: description: "specify SET/GET command ratio (default: 33, i.e. 33% SETs and 67% GETs)" defaultValue: 33 - minSize: - description: minimum size of payload, i.e. document body - defaultValue: 50 - maxSize: - description: maximum size of payload, i.e. document body - defaultValue: 5120 # For CentOS, use the following launch command: # launch.command: | # sudo wget -O/etc/yum.repos.d/couchbase.repo http://packages.couchbase.com/rpm/couchbase-centos55-x86_64.repo # sudo yum check-update # sudo yum install -y libcouchbase2-libevent libcouchbase-devel libcouchbase2-bin - \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml ---------------------------------------------------------------------- diff --git a/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml b/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml new file mode 100644 index 0000000..10daa61 --- /dev/null +++ b/usage/launcher/src/test/resources/couchbase-w-loadgen.yaml @@ -0,0 +1,54 @@ +# +# 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. +# +name: Couchbase with Elastic Load Generator + +services: + +- type: brooklyn.entity.nosql.couchbase.CouchbaseCluster + id: cb-cluster + adminUsername: Administrator + adminPassword: Password + initialSize: 3 + createBuckets: [ { bucket: default } ] + brooklyn.config: + provisioning.properties: + minRam: 16384 + minCores: 4 + brooklyn.policies: + - type: brooklyn.policy.autoscaling.AutoScalerPolicy + brooklyn.config: + metric: $brooklyn:sensor("brooklyn.entity.nosql.couchbase.CouchbaseCluster", + "couchbase.stats.cluster.per.node.ops") + metricLowerBound: 500 + metricUpperBound: 1000 + minPoolSize: 3 + maxPoolSize: 8 + +- type: brooklyn.entity.webapp.ControlledDynamicWebAppCluster + name: Web Couchbase Load Gen Cluster + war: https://github.com/neykov/web-load-gen/raw/master/load-gen.war + brooklyn.config: + provisioning.properties: + minCores: 4 + java.sysprops: + brooklyn.example.couchbase.nodes: $brooklyn:formatString("'%s'", + component("cb-cluster").attributeWhenReady("couchbase.cluster.node.addresses")) + initialSize: 2 + +location: aws-ec2:us-east-1 http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/usage/launcher/src/test/resources/couchbase-w-pillowfight.yaml ---------------------------------------------------------------------- diff --git a/usage/launcher/src/test/resources/couchbase-w-pillowfight.yaml b/usage/launcher/src/test/resources/couchbase-w-pillowfight.yaml new file mode 100644 index 0000000..3cc2851 --- /dev/null +++ b/usage/launcher/src/test/resources/couchbase-w-pillowfight.yaml @@ -0,0 +1,35 @@ +# +# 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. +# +name: Couchbase w Pillow Fight + +location: aws-ec2 + +services: +- type: brooklyn.entity.nosql.couchbase.CouchbaseCluster + id: couchbase + adminUsername: Administrator + adminPassword: Password + initialSize: 3 + createBuckets: + - bucket: default + bucket-port: 11211 + +- type: "classpath://brooklyn/entity/nosql/couchbase/pillowfight.yaml" + brooklyn.config: + base_url: $brooklyn:entity("couchbase").attributeWhenReady("couchbase.cluster.connection.url") http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/1064cd5d/usage/launcher/src/test/resources/couchbase.yaml ---------------------------------------------------------------------- diff --git a/usage/launcher/src/test/resources/couchbase.yaml b/usage/launcher/src/test/resources/couchbase.yaml index 14e1e15..bba8670 100644 --- a/usage/launcher/src/test/resources/couchbase.yaml +++ b/usage/launcher/src/test/resources/couchbase.yaml @@ -16,14 +16,17 @@ # specific language governing permissions and limitations # under the License. # -name: Couchbase +name: Couchbase Two Bucket -location: aws-ec2 +location: softlayer:wdc01 services: - type: brooklyn.entity.nosql.couchbase.CouchbaseCluster + initialSize: 3 adminUsername: Administrator adminPassword: Password - initialSize: 3 - -- type: "classpath://brooklyn/entity/nosql/couchbase/pillowfight.yaml" + createBuckets: + - bucket: default + bucket-port: 11211 + - bucket: my_bucket_2 + bucket-port: 11222 \ No newline at end of file
