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

Reply via email to