You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2014/11/05 01:37:25 UTC

[1/6] git commit: Fix ApplicationResourceIntegrationTest

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master c398136f7 -> fcac7738c


Fix ApplicationResourceIntegrationTest

- Accept stopped or “stopped”.
- Not sure if entirely necessary, but suspect might be based on 
  behaviour seen on jenkins for some other tests!


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/7a2c54e4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/7a2c54e4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/7a2c54e4

Branch: refs/heads/master
Commit: 7a2c54e46a29cd0c4b535b69da49bcebc6fec83d
Parents: b18b189
Author: Aled Sage <al...@gmail.com>
Authored: Mon Nov 3 23:53:09 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Nov 4 00:09:06 2014 +0000

----------------------------------------------------------------------
 .../rest/resources/ApplicationResourceIntegrationTest.java         | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/7a2c54e4/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
index d8074b3..955e592 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/ApplicationResourceIntegrationTest.java
@@ -112,7 +112,7 @@ public class ApplicationResourceIntegrationTest extends BrooklynRestResourceTest
         Asserts.succeedsEventually(MutableMap.of("timeout", 60 * 1000), new Runnable() {
             public void run() {
                 String val = client().resource(stateSensor).get(String.class);
-                assertTrue(expectedStatus.equalsIgnoreCase(val));
+                assertTrue(expectedStatus.equalsIgnoreCase(val) || ("\""+expectedStatus+"\"").equalsIgnoreCase(val), "state="+val);
             }
         });
     }


[5/6] git commit: Fix Riak install on Ubuntu

Posted by al...@apache.org.
Fix Riak install on Ubuntu


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/5345fba7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/5345fba7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/5345fba7

Branch: refs/heads/master
Commit: 5345fba7313ed93b2cd37271f43b5de2c7c670af
Parents: 2995857
Author: Aled Sage <al...@gmail.com>
Authored: Tue Nov 4 00:39:58 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Nov 4 12:25:36 2014 +0000

----------------------------------------------------------------------
 .../brooklyn/entity/nosql/riak/RiakNode.java    | 13 +++++--
 .../entity/nosql/riak/RiakNodeSshDriver.java    | 39 ++++++++++++--------
 2 files changed, 33 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5345fba7/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNode.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNode.java b/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNode.java
index 50089bc..6285059 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNode.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNode.java
@@ -71,10 +71,15 @@ public interface RiakNode extends SoftwareProcess {
             "URL pattern for downloading the linux RPM installer (will substitute things like ${version} automatically)",
             "http://s3.amazonaws.com/downloads.basho.com/riak/${entity.majorVersion}/${entity.fullVersion}/rhel/${entity.osMajorVersion}/riak-${entity.fullVersion}-1.el6.x86_64.rpm");
 
-    @SetFromFlag("downloadUrlUbuntuDebian")
-    AttributeSensorAndConfigKey DOWNLOAD_URL_UBUNTU_DEBIAN = new TemplatedStringAttributeSensorAndConfigKey("download.url.ubuntudebian",
-            "URL pattern for downloading the linux RPM installer (will substitute things like ${version} automatically)",
-            "http://s3.amazonaws.com/downloads.basho.com/riak/${entity.majorVersion}/${entity.fullVersion}/$OS_NAME/$OS_RELEASE/riak_${entity.fullVersion}-1_amd64.deb");
+    @SetFromFlag("downloadUrlUbuntu")
+    AttributeSensorAndConfigKey DOWNLOAD_URL_UBUNTU = new TemplatedStringAttributeSensorAndConfigKey("download.url.ubuntu",
+            "URL pattern for downloading the linux Ubuntu installer (will substitute things like ${version} automatically)",
+            "http://s3.amazonaws.com/downloads.basho.com/riak/${entity.majorVersion}/${entity.fullVersion}/ubuntu/$OS_RELEASE/riak_${entity.fullVersion}-1_amd64.deb");
+
+    @SetFromFlag("downloadUrlDebian")
+    AttributeSensorAndConfigKey DOWNLOAD_URL_DEBIAN = new TemplatedStringAttributeSensorAndConfigKey("download.url.debian",
+            "URL pattern for downloading the linux Debian installer (will substitute things like ${version} automatically)",
+            "http://s3.amazonaws.com/downloads.basho.com/riak/${entity.majorVersion}/${entity.fullVersion}/debian/$OS_RELEASE/riak_${entity.fullVersion}-1_amd64.deb");
 
     @SetFromFlag("downloadUrlMac")
     AttributeSensorAndConfigKey DOWNLOAD_URL_MAC = new TemplatedStringAttributeSensorAndConfigKey("download.url.mac",

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5345fba7/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNodeSshDriver.java b/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNodeSshDriver.java
index 42189f6..62ca6b3 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNodeSshDriver.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNodeSshDriver.java
@@ -18,7 +18,13 @@
  */
 package brooklyn.entity.nosql.riak;
 
-import static brooklyn.util.ssh.BashCommands.*;
+import static brooklyn.util.ssh.BashCommands.INSTALL_CURL;
+import static brooklyn.util.ssh.BashCommands.INSTALL_TAR;
+import static brooklyn.util.ssh.BashCommands.alternatives;
+import static brooklyn.util.ssh.BashCommands.chainGroup;
+import static brooklyn.util.ssh.BashCommands.commandToDownloadUrlAs;
+import static brooklyn.util.ssh.BashCommands.ok;
+import static brooklyn.util.ssh.BashCommands.sudo;
 import static java.lang.String.format;
 
 import java.util.List;
@@ -44,14 +50,6 @@ import brooklyn.util.text.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.List;
-import java.util.Map;
-
-import static brooklyn.util.ssh.BashCommands.*;
-import static java.lang.String.format;
 
 // TODO: Alter -env ERL_CRASH_DUMP path in vm.args
 public class RiakNodeSshDriver extends AbstractSoftwareProcessSshDriver implements RiakNodeDriver {
@@ -120,18 +118,29 @@ public class RiakNodeSshDriver extends AbstractSoftwareProcessSshDriver implemen
         String saveAsYum = "riak.rpm";
         String saveAsApt = "riak.deb";
         OsDetails osDetails = getMachine().getOsDetails();
+        
+        String downloadUrl;
+        String osReleaseCmd;
+        if ("debian".equalsIgnoreCase(osDetails.getName())) {
+            // TODO osDetails.getName() is returning "linux", instead of debian/ubuntu on AWS with jenkins image,
+            //      running as integration test targetting localhost. 
+            // TODO Debian support (default debian image fails with 'sudo: command not found')
+            downloadUrl = entity.getAttribute(RiakNode.DOWNLOAD_URL_DEBIAN);
+            osReleaseCmd = osDetails.getVersion().substring(0, osDetails.getVersion().indexOf("."));
+        } else {
+            // assume Ubuntu
+            downloadUrl = entity.getAttribute(RiakNode.DOWNLOAD_URL_UBUNTU);
+            osReleaseCmd = "`lsb_release -sc` && " +
+                    "export OS_RELEASE=`([[ \"lucid natty precise\" =~ (^| )\\$OS_RELEASE($| ) ]] && echo $OS_RELEASE || echo precise)`";
+        }
         String apt = chainGroup(
                 //debian fix
                 "export PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                 "which apt-get",
                 ok(sudo("apt-get -y --allow-unauthenticated install logrotate libpam0g-dev libssl0.9.8")),
                 "export OS_NAME=" + Strings.toLowerCase(osDetails.getName()),
-                // TODO: Debian support (default debian image fails with 'sudo: command not found')
-                "debian".equals(osDetails.getName()) ?
-                    "export OS_RELEASE=" + osDetails.getVersion().substring(0, osDetails.getVersion().indexOf(".")) :
-                    "export OS_RELEASE=`lsb_release -sc` && " +
-                    "export OS_RELEASE=`([[ \"lucid natty precise\" =~ (^| )\\$OS_RELEASE($| ) ]] && echo $OS_RELEASE || echo precise)`",
-                String.format("wget -O %s %s", saveAsApt, entity.getAttribute(RiakNode.DOWNLOAD_URL_UBUNTU_DEBIAN)),
+                "export OS_RELEASE=" + osReleaseCmd,
+                String.format("wget -O %s %s", saveAsApt, downloadUrl),
                 sudo(String.format("dpkg -i %s", saveAsApt)));
         String yum = chainGroup(
                 "which yum",


[4/6] git commit: MongoDB deployment integration test: fix assert

Posted by al...@apache.org.
MongoDB deployment integration test: fix assert


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/b586bcc9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/b586bcc9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/b586bcc9

Branch: refs/heads/master
Commit: b586bcc925d704b178a011d78e0293688e7b108b
Parents: 7a2c54e
Author: Aled Sage <al...@gmail.com>
Authored: Mon Nov 3 23:53:48 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Nov 4 00:09:07 2014 +0000

----------------------------------------------------------------------
 .../mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/b586bcc9/software/nosql/src/test/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java b/software/nosql/src/test/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java
index bfa00e8..9348431 100644
--- a/software/nosql/src/test/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java
+++ b/software/nosql/src/test/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java
@@ -72,7 +72,7 @@ public class MongoDBShardedDeploymentIntegrationTest extends BrooklynAppLiveTest
     public void testCanStartAndStopDeployment() {
         MongoDBShardedDeployment deployment = makeAndStartDeployment();
         deployment.stop();
-        Assert.assertFalse(deployment.getAttribute(Startable.SERVICE_UP));
+        EntityTestUtils.assertAttributeEqualsEventually(deployment, Startable.SERVICE_UP, false);
     }
     
     @Test(groups = "Integration")


[6/6] git commit: This closes #293

Posted by al...@apache.org.
This closes #293


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/fcac7738
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/fcac7738
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/fcac7738

Branch: refs/heads/master
Commit: fcac7738cf7dd9cbd4d28031cb2347bec15988e5
Parents: c398136 5345fba
Author: Aled Sage <al...@gmail.com>
Authored: Wed Nov 5 00:36:44 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Nov 5 00:36:44 2014 +0000

----------------------------------------------------------------------
 .../nosql/mongodb/MongoDBReplicaSetImpl.java    |   9 +-
 .../sharding/MongoDBShardClusterImpl.java       | 111 ++++++++++++++++---
 .../sharding/MongoDBShardedDeployment.java      |   4 +-
 .../brooklyn/entity/nosql/riak/RiakNode.java    |  13 ++-
 .../entity/nosql/riak/RiakNodeSshDriver.java    |  31 ++++--
 ...MongoDBShardedDeploymentIntegrationTest.java |   2 +-
 .../ApplicationResourceIntegrationTest.java     |   2 +-
 7 files changed, 139 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/fcac7738/software/nosql/src/main/java/brooklyn/entity/nosql/riak/RiakNodeSshDriver.java
----------------------------------------------------------------------


[3/6] git commit: MongoDB deployment: fix infinite loop

Posted by al...@apache.org.
MongoDB deployment: fix infinite loop


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/62b3ff7b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/62b3ff7b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/62b3ff7b

Branch: refs/heads/master
Commit: 62b3ff7b2acf314a45b8c9994998ec9e5675e7db
Parents: b586bcc
Author: Aled Sage <al...@gmail.com>
Authored: Mon Nov 3 23:54:06 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Nov 4 00:09:07 2014 +0000

----------------------------------------------------------------------
 .../entity/nosql/mongodb/sharding/MongoDBShardedDeployment.java  | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/62b3ff7b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeployment.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeployment.java b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeployment.java
index 611eea5..143b953 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeployment.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeployment.java
@@ -24,6 +24,7 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.Group;
 import brooklyn.entity.basic.ConfigKeys;
 import brooklyn.entity.nosql.mongodb.MongoDBReplicaSet;
+import brooklyn.entity.nosql.mongodb.MongoDBServer;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.proxying.ImplementedBy;
 import brooklyn.entity.trait.Startable;
@@ -75,7 +76,8 @@ public interface MongoDBShardedDeployment extends Entity, Startable {
             new TypeToken<EntitySpec<?>>() {},
             "mongodb.replicaset.spec", 
             "Spec for Replica Set",
-            EntitySpec.create(MongoDBReplicaSet.class));
+            EntitySpec.create(MongoDBReplicaSet.class)
+                    .configure(MongoDBReplicaSet.MEMBER_SPEC, EntitySpec.create(MongoDBServer.class)));
 
     @SuppressWarnings("serial")
     ConfigKey<EntitySpec<?>> MONGODB_CONFIG_SERVER_SPEC = ConfigKeys.newConfigKey(


[2/6] git commit: MongoDB: fix race in adding shards

Posted by al...@apache.org.
MongoDB: fix race in adding shards

- if adding the shard fails, then try again


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/2995857b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/2995857b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/2995857b

Branch: refs/heads/master
Commit: 2995857ba93c244035157fc3f7fbb5d48107657f
Parents: 62b3ff7
Author: Aled Sage <al...@gmail.com>
Authored: Tue Nov 4 00:08:23 2014 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Nov 4 00:09:07 2014 +0000

----------------------------------------------------------------------
 .../nosql/mongodb/MongoDBReplicaSetImpl.java    |   9 +-
 .../sharding/MongoDBShardClusterImpl.java       | 111 ++++++++++++++++---
 2 files changed, 101 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2995857b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java
index 6d09b4c..a106ec1 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/MongoDBReplicaSetImpl.java
@@ -218,6 +218,7 @@ public class MongoDBReplicaSetImpl extends DynamicClusterImpl implements MongoDB
      * seconds time (in the hope that next time the primary will be available).
      */
     private void addSecondaryWhenPrimaryIsNonNull(final MongoDBServer secondary) {
+        // TODO Don't use executor, use ExecutionManager
         executor.submit(new Runnable() {
             @Override
             public void run() {
@@ -377,12 +378,18 @@ public class MongoDBReplicaSetImpl extends DynamicClusterImpl implements MongoDB
         //  - if the set is being stopped forever it's irrelevant
         //  - if the set might be restarted I think it just inconveniences us
         // Terminate the executor immediately.
-        // Note that after this the executor will not run if the set is restarted.
+        // TODO Note that after this the executor will not run if the set is restarted.
         executor.shutdownNow();
         super.stop();
         setAttribute(Startable.SERVICE_UP, false);
     }
 
+    @Override
+    public void onManagementStopped() {
+        super.onManagementStopped();
+        executor.shutdownNow();
+    }
+    
     public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy {
         @Override protected void onEntityChange(Entity member) {
             // Ignored

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2995857b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardClusterImpl.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardClusterImpl.java b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardClusterImpl.java
index 5b465ca..47c32ae 100644
--- a/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardClusterImpl.java
+++ b/software/nosql/src/main/java/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardClusterImpl.java
@@ -21,6 +21,10 @@ package brooklyn.entity.nosql.mongodb.sharding;
 import java.net.UnknownHostException;
 import java.util.Collection;
 import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,15 +41,30 @@ import brooklyn.event.SensorEventListener;
 import brooklyn.location.Location;
 import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
 
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.Sets;
 
 public class MongoDBShardClusterImpl extends DynamicClusterImpl implements MongoDBShardCluster {
 
     private static final Logger LOG = LoggerFactory.getLogger(MongoDBShardClusterImpl.class);
+    
     // TODO: Need to use attributes for this in order to support brooklyn restart 
     private Set<Entity> addedMembers = Sets.newConcurrentHashSet();
 
+    // TODO: Need to use attributes for this in order to support brooklyn restart 
+    private Set<Entity> addingMembers = Sets.newConcurrentHashSet();
+
+    /**
+     * For shard addition and removal.
+     * Used for retrying.
+     * 
+     * TODO Should use ExecutionManager.
+     */
+    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+
     @Override
     protected EntitySpec<?> getMemberSpec() {
         EntitySpec<?> result = super.getMemberSpec();
@@ -72,33 +91,89 @@ public class MongoDBShardClusterImpl extends DynamicClusterImpl implements Mongo
                     addShards();
             }
         });
-        
+    }
+
+    @Override
+    public void stop() {
+        // TODO Note that after this the executor will not run if the set is restarted.
+        executor.shutdownNow();
+        super.stop();
+    }
+    
+    @Override
+    public void onManagementStopped() {
+        super.onManagementStopped();
+        executor.shutdownNow();
     }
 
     protected void addShards() {
         MongoDBRouter router = getParent().getAttribute(MongoDBShardedDeployment.ROUTER_CLUSTER).getAttribute(MongoDBRouterCluster.ANY_RUNNING_ROUTER);
-        if (router == null)
+        if (router == null) {
+            if (LOG.isTraceEnabled()) LOG.trace("Not adding shards because no running router in {}", this);
             return;
-        
-        MongoDBClientSupport client;
-        try {
-            client = MongoDBClientSupport.forServer(router);
-        } catch (UnknownHostException e) {
-            throw Exceptions.propagate(e);
         }
+        
         for (Entity member : this.getMembers()) {
-            if (member.getAttribute(Startable.SERVICE_UP) && !addedMembers.contains(member)) {
-                MongoDBServer primary = member.getAttribute(MongoDBReplicaSet.PRIMARY_ENTITY);
-                if (primary != null) {
-                    String addr = Strings.removeFromStart(primary.getAttribute(MongoDBServer.MONGO_SERVER_ENDPOINT), "http://");
-                    String replicaSetURL = ((MongoDBReplicaSet) member).getName() + "/" + addr;
-                    LOG.info("Using {} to add shard URL {}...", router, replicaSetURL);
-                    client.addShardToRouter(replicaSetURL);
-                    addedMembers.add(member);
+            if (member.getAttribute(Startable.SERVICE_UP) && !addingMembers.contains(member)) {
+                LOG.info("{} adding shard {}", new Object[] {MongoDBShardClusterImpl.this, member});
+                addingMembers.add(member);
+                addShardAsync(member);
+            }
+        }
+    }
+    
+    protected void addShardAsync(final Entity replicaSet) {
+        final Duration timeout = Duration.minutes(20);
+        final Stopwatch stopwatch = Stopwatch.createStarted();
+        final AtomicInteger attempts = new AtomicInteger();
+        
+        // TODO Don't use executor, use ExecutionManager; but following pattern in MongoDBReplicaSetImpl for now.
+        executor.submit(new Runnable() {
+            @Override
+            public void run() {
+                boolean reschedule;
+                MongoDBRouter router = getParent().getAttribute(MongoDBShardedDeployment.ROUTER_CLUSTER).getAttribute(MongoDBRouterCluster.ANY_RUNNING_ROUTER);
+                if (router == null) {
+                    LOG.debug("Rescheduling adding shard {} because no running router for cluster {}", replicaSet, this);
+                    reschedule = true;
                 } else {
-                    LOG.debug("{} not set for member {}); not adding shart to router {}", new Object[] {MongoDBReplicaSet.PRIMARY_ENTITY, member, router});
+                    MongoDBClientSupport client;
+                    try {
+                        client = MongoDBClientSupport.forServer(router);
+                    } catch (UnknownHostException e) {
+                        throw Exceptions.propagate(e);
+                    }
+                    
+                    MongoDBServer primary = replicaSet.getAttribute(MongoDBReplicaSet.PRIMARY_ENTITY);
+                    if (primary != null) {
+                        String addr = Strings.removeFromStart(primary.getAttribute(MongoDBServer.MONGO_SERVER_ENDPOINT), "http://");
+                        String replicaSetURL = ((MongoDBReplicaSet) replicaSet).getName() + "/" + addr;
+                        boolean added = client.addShardToRouter(replicaSetURL);
+                        if (added) {
+                            LOG.info("{} added shard {} via {}", new Object[] {MongoDBShardClusterImpl.this, replicaSetURL, router});
+                            addedMembers.add(replicaSet);
+                            reschedule = false;
+                        } else {
+                            LOG.debug("Rescheduling addition of shard {} because add failed via router {}", replicaSetURL, router);
+                            reschedule = true;
+                        }
+                    } else {
+                        LOG.debug("Rescheduling addition of shard {} because primary is null", replicaSet);
+                        reschedule = true;
+                    }
+                }
+                
+                if (reschedule) {
+                    int numAttempts = attempts.incrementAndGet();
+                    if (numAttempts > 1 && timeout.toMilliseconds() > stopwatch.elapsed(TimeUnit.MILLISECONDS)) {
+                        executor.schedule(this, 3, TimeUnit.SECONDS);
+                    } else {
+                        LOG.warn("Timeout after {} attempts ({}) adding shard {}; aborting", 
+                                new Object[] {numAttempts, Time.makeTimeStringRounded(stopwatch), replicaSet});
+                        addingMembers.remove(replicaSet);
+                    }
                 }
             }
-        }
+        });
     }
 }