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 2015/08/06 18:32:16 UTC

[02/26] incubator-brooklyn git commit: [BROOKLYN-162] Renaming of the NoSQL packages

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBTestHelper.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBTestHelper.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBTestHelper.java
new file mode 100644
index 0000000..9b890fa
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/MongoDBTestHelper.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.mongodb;
+
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.entity.nosql.mongodb.AbstractMongoDBServer;
+import org.apache.brooklyn.entity.nosql.mongodb.MongoDBServer;
+import org.bson.types.ObjectId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Throwables;
+import com.google.common.net.HostAndPort;
+import com.mongodb.BasicDBObject;
+import com.mongodb.CommandResult;
+import com.mongodb.DB;
+import com.mongodb.DBCollection;
+import com.mongodb.DBObject;
+import com.mongodb.MongoClient;
+import com.mongodb.ReadPreference;
+
+import brooklyn.location.access.BrooklynAccessUtils;
+
+public class MongoDBTestHelper {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MongoDBTestHelper.class);
+
+    private static final String TEST_DB = "brooklyn_test";
+    private static final String TEST_COLLECTION = "test_collection";
+    private static final String ADMIN_DB = "admin";
+
+    /**
+     * Inserts a new object with { key: value } at given server.
+     * @return The new document's id
+     */
+    public static String insert(AbstractMongoDBServer entity, String key, Object value) {
+        LOG.info("Inserting {}:{} at {}", new Object[]{key, value, entity});
+        MongoClient mongoClient = clientForServer(entity);
+        try {
+            DB db = mongoClient.getDB(TEST_DB);
+            DBCollection testCollection = db.getCollection(TEST_COLLECTION);
+            BasicDBObject doc = new BasicDBObject(key, value);
+            testCollection.insert(doc);
+            ObjectId id = (ObjectId) doc.get("_id");
+            return id.toString();
+        } finally {
+            mongoClient.close();
+        }
+    }
+
+    /** @return The {@link DBObject} representing the object with the given id */
+    public static DBObject getById(AbstractMongoDBServer entity, String id) {
+        LOG.info("Getting {} from {}", new Object[]{id, entity});
+        MongoClient mongoClient = clientForServer(entity);
+        // Secondary preferred means the driver will let us read from secondaries too.
+        mongoClient.setReadPreference(ReadPreference.secondaryPreferred());
+        try {
+            DB db = mongoClient.getDB(TEST_DB);
+            DBCollection testCollection = db.getCollection(TEST_COLLECTION);
+            return testCollection.findOne(new BasicDBObject("_id", new ObjectId(id)));
+        } finally {
+            mongoClient.close();
+        }
+    }
+    
+    public static List<String> getDatabaseNames(AbstractMongoDBServer entity) {
+        LOG.info("Getting database names from {}", entity);
+        MongoClient mongoClient = clientForServer(entity);
+        try {
+            return mongoClient.getDatabaseNames();
+        } finally {
+            mongoClient.close();
+        }
+    }
+    
+    public static boolean isConfigServer(AbstractMongoDBServer entity) {
+        LOG.info("Checking if {} is a config server", entity);
+        MongoClient mongoClient = clientForServer(entity);
+        try {
+            DB db = mongoClient.getDB(ADMIN_DB);
+            CommandResult commandResult = db.command("getCmdLineOpts");
+            Map<?, ?> parsedArgs = (Map<?, ?>)commandResult.get("parsed");
+            if (parsedArgs == null) return false;
+            Boolean configServer = (Boolean)parsedArgs.get("configsvr");
+            if (configServer != null) {
+                // v2.5 format
+                return Boolean.TRUE.equals(configServer);
+            } else {
+                // v2.6 format
+                String role = (String) ((Map)parsedArgs.get("sharding")).get("clusterRole");
+                return "configsvr".equals(role);
+            }
+        } finally {
+            mongoClient.close();
+        }
+    }
+
+    private static MongoClient clientForServer(AbstractMongoDBServer server) {
+        try {
+            HostAndPort hap = BrooklynAccessUtils.getBrooklynAccessibleAddress(server, server.getAttribute(MongoDBServer.PORT));
+            return new MongoClient(hap.getHostText(), hap.getPort());
+        } catch (UnknownHostException e) {
+            // Fail whatever test called this method.
+            throw Throwables.propagate(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/ReplicaSetConfigTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/ReplicaSetConfigTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/ReplicaSetConfigTest.java
new file mode 100644
index 0000000..dc5cb5c
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/ReplicaSetConfigTest.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.mongodb;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.entity.nosql.mongodb.ReplicaSetConfig;
+import org.bson.BSONObject;
+import org.bson.BasicBSONObject;
+import org.bson.types.BasicBSONList;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.net.HostAndPort;
+
+public class ReplicaSetConfigTest {
+
+    // true if object has key "votes" that is > 1
+    static Predicate<BasicBSONObject> IS_VOTING_MEMBER = new Predicate<BasicBSONObject>() {
+        @Override public boolean apply(@Nullable BasicBSONObject input) {
+            return input != null && input.containsField("votes") && input.getInt("votes") > 0;
+        }
+    };
+
+    private BasicBSONObject makeSetMember(Integer id, String host) {
+        return new BasicBSONObject(ImmutableMap.of("_id", id, "host", host));
+    }
+
+    private BasicBSONObject makeSetConfig(String id, Integer version, BasicBSONObject... members) {
+        BasicBSONList memberList = new BasicBSONList();
+        memberList.addAll(Arrays.asList(members));
+        return new BasicBSONObject(ImmutableMap.of("_id", id, "version", version, "members", memberList));
+    }
+
+    private BasicBSONObject makeSetWithNMembers(int n) {
+        ReplicaSetConfig setConfig = ReplicaSetConfig.builder("replica-set-name");
+        for (int i = 0; i < n; i++) {
+            setConfig.member("host-"+i, i, i);
+        }
+        return setConfig.build();
+    }
+
+    private Collection<HostAndPort> votingMembersOfSet(BasicBSONObject config) {
+        BasicBSONList membersObject = BasicBSONList.class.cast(config.get("members"));
+        List<BasicBSONObject> members = Lists.newArrayList();
+        for (Object object : membersObject) members.add(BasicBSONObject.class.cast(object));
+        return FluentIterable.from(members)
+                .filter(IS_VOTING_MEMBER)
+                .transform(new Function<BasicBSONObject, HostAndPort>() {
+                    @Override public HostAndPort apply(BasicBSONObject input) {
+                        return HostAndPort.fromString(input.getString("host"));
+                    }
+                })
+                .toList();
+    }
+
+    private Collection<HostAndPort> nonVotingMembersOfSet(BasicBSONObject config) {
+        BasicBSONList membersObject = BasicBSONList.class.cast(config.get("members"));
+        List<BasicBSONObject> members = Lists.newArrayList();
+        for (Object object : membersObject) members.add(BasicBSONObject.class.cast(object));
+        return FluentIterable
+                .from(members)
+                .filter(Predicates.not(IS_VOTING_MEMBER))
+                .transform(new Function<BasicBSONObject, HostAndPort>() {
+                    @Override public HostAndPort apply(BasicBSONObject input) {
+                        return HostAndPort.fromString(input.getString("host"));
+                    }
+                })
+                .toList();
+    }
+
+    @Test
+    public void testCreateFromScratch() {
+        BasicBSONObject config = ReplicaSetConfig.builder("rs")
+            .member("host-a", 12345, 1)
+            .member("host-b", 54321, 2)
+            .build();
+        assertEquals(config.get("_id"), "rs");
+        assertEquals(config.getInt("version"), 1);
+        assertTrue(config.get("members") instanceof BasicBSONList);
+        BasicBSONList members = (BasicBSONList) config.get("members");
+        assertEquals(members.size(), 2);
+    }
+
+    @Test
+    public void testCreateFromExistingConfig() {
+        // Replica set of one member
+        int version = 44;
+        BasicBSONObject config = makeSetConfig("replica-set-name", version, makeSetMember(33, "example.com:7777"));
+
+        // Use existing set to add two more members
+        BasicBSONObject newConfig = ReplicaSetConfig.fromExistingConfig(config)
+            .member("foo", 8888, 34)
+            .member("bar", 9999, 35)
+            .build();
+
+        assertEquals(newConfig.get("_id"), "replica-set-name");
+        assertEquals(newConfig.get("version"), version + 1);
+        BasicBSONList members = (BasicBSONList) newConfig.get("members");
+        assertEquals(members.size(), 3);
+
+        BSONObject original = (BSONObject) members.get(0);
+        assertEquals(original.get("_id"), 33);
+        assertEquals(original.get("host"), "example.com:7777");
+
+        BSONObject second = (BSONObject) members.get(1);
+        assertEquals(second.get("_id"), 34);
+        assertEquals(second.get("host"), "foo:8888");
+
+        BSONObject third = (BSONObject) members.get(2);
+        assertEquals(third.get("_id"), 35);
+        assertEquals(third.get("host"), "bar:9999");
+    }
+
+    @Test
+    public void testRemoveMember() {
+        int version = 44;
+        BasicBSONObject config = makeSetConfig("replica-set-name", version,
+                makeSetMember(33, "example.com:7777"),
+                makeSetMember(34, "example.com:7778"));
+
+        // Use existing set to add two more members
+        BasicBSONObject newConfig = ReplicaSetConfig.fromExistingConfig(config)
+            .remove("example.com", 7777)
+            .build();
+
+        assertEquals(newConfig.get("version"), version + 1);
+        BasicBSONList members = (BasicBSONList) newConfig.get("members");
+        assertEquals(members.size(), 1);
+        assertEquals(BSONObject.class.cast(members.get(0)).get("host"), "example.com:7778");
+
+        newConfig = ReplicaSetConfig.fromExistingConfig(newConfig)
+            .remove("example.com", 7778)
+            .build();
+
+        members = (BasicBSONList) newConfig.get("members");
+        assertTrue(members.isEmpty());
+    }
+
+    @Test
+    public void testRemoveNonExistentMemberHasNoEffect() {
+        BasicBSONObject config = makeSetConfig("replica-set-name", 1,
+                makeSetMember(33, "example.com:7777"),
+                makeSetMember(34, "example.com:7778"));
+
+        BasicBSONList members = (BasicBSONList) config.get("members");
+        assertEquals(members.size(), 2);
+
+        BasicBSONObject altered = ReplicaSetConfig.fromExistingConfig(config)
+                .remove("foo", 99)
+                .build();
+
+        members = (BasicBSONList) altered.get("members");
+        assertEquals(members.size(), 2);
+    }
+
+    @Test
+    public void testSetOfFourMembersHasThreeVoters() {
+        BasicBSONObject config = makeSetWithNMembers(4);
+        assertEquals(votingMembersOfSet(config).size(), 3, "Expected three voters in set with four members");
+        assertEquals(nonVotingMembersOfSet(config).size(), 1, "Expected one non-voter in set with four members");
+    }
+
+    @Test
+    public void testFourthServerOfFourIsGivenVoteWhenAnotherServerIsRemoved() {
+        BasicBSONObject config = makeSetWithNMembers(4);
+        HostAndPort toRemove = votingMembersOfSet(config).iterator().next();
+
+        BasicBSONObject updated = ReplicaSetConfig.fromExistingConfig(config)
+                .remove(toRemove)
+                .build();
+
+        assertEquals(votingMembersOfSet(updated).size(), 3);
+        assertTrue(nonVotingMembersOfSet(updated).isEmpty());
+
+        BasicBSONList newMembers = BasicBSONList.class.cast(updated.get("members"));
+        for (Object object : newMembers) {
+            BasicBSONObject member = BasicBSONObject.class.cast(object);
+            HostAndPort memberHostAndPort = HostAndPort.fromString(member.getString("host"));
+            assertNotEquals(memberHostAndPort, toRemove);
+        }
+    }
+
+    @Test
+    public void testMaximumNumberOfVotersIsLimited() {
+        BasicBSONObject config = makeSetWithNMembers(ReplicaSetConfig.MAXIMUM_REPLICA_SET_SIZE);
+        int voters = ReplicaSetConfig.MAXIMUM_VOTING_MEMBERS;
+        int nonVoters = ReplicaSetConfig.MAXIMUM_REPLICA_SET_SIZE - voters;
+        assertEquals(votingMembersOfSet(config).size(), voters, "Expected number of voters in max-size set to be " + voters);
+        assertEquals(nonVotingMembersOfSet(config).size(), nonVoters, "Expected number of non-voters in max-size set to be " + nonVoters);
+    }
+
+    @Test(expectedExceptions = IllegalStateException.class)
+    public void testMoreMembersThanMaximumAllowsRejected() {
+        makeSetWithNMembers(ReplicaSetConfig.MAXIMUM_REPLICA_SET_SIZE + 1);
+    }
+
+    @Test
+    public void testPrimaryGivenVoteWhenLastInMemberList() {
+        BasicBSONObject config = ReplicaSetConfig.builder("rs")
+            .member("host-a", 1, 1)
+            .member("host-b", 2, 2)
+            .member("host-c", 3, 3)
+            .member("host-d", 4, 4)
+            .primary(HostAndPort.fromParts("host-d", 4))
+            .build();
+        assertEquals(votingMembersOfSet(config).size(), 3);
+        assertEquals(nonVotingMembersOfSet(config).size(), 1);
+        assertTrue(votingMembersOfSet(config).contains(HostAndPort.fromParts("host-d", 4)));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBConfigServerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBConfigServerIntegrationTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBConfigServerIntegrationTest.java
new file mode 100644
index 0000000..fb748e4
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBConfigServerIntegrationTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.mongodb.sharding;
+
+import static org.testng.Assert.assertFalse;
+
+import org.apache.brooklyn.entity.nosql.mongodb.MongoDBServer;
+import org.apache.brooklyn.entity.nosql.mongodb.MongoDBTestHelper;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBConfigServer;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.test.Asserts;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.TestApplication;
+
+import com.google.common.collect.ImmutableList;
+
+public class MongoDBConfigServerIntegrationTest {
+    private TestApplication app;
+    private LocalhostMachineProvisioningLocation localhostProvisioningLocation;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        localhostProvisioningLocation = new LocalhostMachineProvisioningLocation();
+        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+    
+    @Test(groups = "Integration")
+    public void testCanStartAndStop() throws Exception {
+        MongoDBConfigServer entity = app.createAndManageChild(EntitySpec.create(MongoDBConfigServer.class)
+                .configure(MongoDBServer.MONGODB_CONF_TEMPLATE_URL, "classpath:///test-mongodb-configserver.conf"));
+        app.start(ImmutableList.of(localhostProvisioningLocation));
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true);
+        Asserts.assertTrue(MongoDBTestHelper.isConfigServer(entity), "Server is not a config server");
+        entity.stop();
+        assertFalse(entity.getAttribute(Startable.SERVICE_UP));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentEc2LiveTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentEc2LiveTest.java
new file mode 100644
index 0000000..9e8ff55
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentEc2LiveTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.mongodb.sharding;
+
+import groovy.time.TimeDuration;
+
+import org.apache.brooklyn.entity.nosql.mongodb.MongoDBReplicaSet;
+import org.apache.brooklyn.entity.nosql.mongodb.MongoDBServer;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBConfigServer;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBRouter;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBShardedDeployment;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.AbstractEc2LiveTest;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.Asserts;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * NOTE: These test will provision 9 machines in AWS, which can cause 'Request limit exceeded' and
+ * 'Exhausted available authentication methods' exceptions, depending upon current AWS load. You can
+ * mitigate this issue by adding the following lines to your brooklyn.properties:
+ *
+ * brooklyn.location.jclouds.machineCreateAttempts=3
+ * brooklyn.jclouds.aws-ec2.maxConcurrentMachineCreations=5
+ */
+@Test
+public class MongoDBShardedDeploymentEc2LiveTest extends AbstractEc2LiveTest {
+
+    private static final Integer ROUTER_CLUSTER_SIZE = 2;
+    private static final Integer REPLICASET_SIZE = 2;
+    private static final Integer SHARD_CLUSTER_SIZE = 3;
+    private static final TimeDuration TIMEOUT = new TimeDuration(0, 3, 0, 0);
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        final MongoDBShardedDeployment deployment = app.createAndManageChild(EntitySpec.create(MongoDBShardedDeployment.class)
+                .configure(MongoDBShardedDeployment.INITIAL_ROUTER_CLUSTER_SIZE, ROUTER_CLUSTER_SIZE)
+                .configure(MongoDBShardedDeployment.SHARD_REPLICASET_SIZE, REPLICASET_SIZE)
+                .configure(MongoDBShardedDeployment.INITIAL_SHARD_CLUSTER_SIZE, SHARD_CLUSTER_SIZE)
+                .configure(MongoDBShardedDeployment.MONGODB_REPLICA_SET_SPEC, EntitySpec.create(MongoDBReplicaSet.class)
+                        .configure(MongoDBServer.MONGODB_CONF_TEMPLATE_URL, "classpath:///test-mongodb.conf")
+                        .configure(MongoDBReplicaSet.MEMBER_SPEC, EntitySpec.create(MongoDBServer.class)))
+                .configure(MongoDBShardedDeployment.MONGODB_ROUTER_SPEC, EntitySpec.create(MongoDBRouter.class)
+                        .configure(MongoDBConfigServer.MONGODB_CONF_TEMPLATE_URL, "classpath:///test-mongodb-router.conf"))
+                .configure(MongoDBShardedDeployment.MONGODB_CONFIG_SERVER_SPEC, EntitySpec.create(MongoDBConfigServer.class)
+                        .configure(MongoDBConfigServer.MONGODB_CONF_TEMPLATE_URL, "classpath:///test-mongodb-configserver.conf")));
+
+        app.start(ImmutableList.of(loc));
+        
+        Entities.dumpInfo(app);
+
+        Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT), new Runnable() {
+            public void run() {
+                Assert.assertEquals(deployment.getRouterCluster().getCurrentSize(), ROUTER_CLUSTER_SIZE);
+                Assert.assertEquals(deployment.getShardCluster().getCurrentSize(), SHARD_CLUSTER_SIZE);
+                Assert.assertEquals(deployment.getConfigCluster().getCurrentSize(), MongoDBShardedDeployment.CONFIG_CLUSTER_SIZE.getDefaultValue());
+                for (Entity entity : deployment.getShardCluster().getMembers()) {
+                    Assert.assertEquals(((MongoDBReplicaSet) entity).getCurrentSize(), REPLICASET_SIZE);
+                }
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java
new file mode 100644
index 0000000..e4947d3
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/mongodb/sharding/MongoDBShardedDeploymentIntegrationTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.mongodb.sharding;
+
+import org.apache.brooklyn.entity.nosql.mongodb.AbstractMongoDBServer;
+import org.apache.brooklyn.entity.nosql.mongodb.MongoDBReplicaSet;
+import org.apache.brooklyn.entity.nosql.mongodb.MongoDBServer;
+import org.apache.brooklyn.entity.nosql.mongodb.MongoDBTestHelper;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBConfigServer;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBConfigServerCluster;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBRouter;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBRouterCluster;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBShardCluster;
+import org.apache.brooklyn.entity.nosql.mongodb.sharding.MongoDBShardedDeployment;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppLiveTestSupport;
+import brooklyn.entity.Entity;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.test.EntityTestUtils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.mongodb.DBObject;
+
+public class MongoDBShardedDeploymentIntegrationTest extends BrooklynAppLiveTestSupport {
+    
+    private static final Integer ROUTER_CLUSTER_SIZE = 2;
+    private static final Integer REPLICASET_SIZE = 2;
+    private static final Integer SHARD_CLUSTER_SIZE = 3;
+    
+    private LocalhostMachineProvisioningLocation localhostProvisioningLocation;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+        localhostProvisioningLocation = app.newLocalhostProvisioningLocation();
+    }
+
+    private MongoDBShardedDeployment makeAndStartDeployment() {
+        final MongoDBShardedDeployment deployment = app.createAndManageChild(EntitySpec.create(MongoDBShardedDeployment.class)
+                .configure(MongoDBShardedDeployment.INITIAL_ROUTER_CLUSTER_SIZE, ROUTER_CLUSTER_SIZE)
+                .configure(MongoDBShardedDeployment.SHARD_REPLICASET_SIZE, REPLICASET_SIZE)
+                .configure(MongoDBShardedDeployment.INITIAL_SHARD_CLUSTER_SIZE, SHARD_CLUSTER_SIZE)
+                .configure(MongoDBShardedDeployment.MONGODB_REPLICA_SET_SPEC, EntitySpec.create(MongoDBReplicaSet.class)
+                        .configure(MongoDBServer.MONGODB_CONF_TEMPLATE_URL, "classpath:///test-mongodb.conf")
+                        .configure(MongoDBReplicaSet.MEMBER_SPEC, EntitySpec.create(MongoDBServer.class)))
+                .configure(MongoDBShardedDeployment.MONGODB_ROUTER_SPEC, EntitySpec.create(MongoDBRouter.class)
+                        .configure(MongoDBConfigServer.MONGODB_CONF_TEMPLATE_URL, "classpath:///test-mongodb-router.conf"))
+                .configure(MongoDBShardedDeployment.MONGODB_CONFIG_SERVER_SPEC, EntitySpec.create(MongoDBConfigServer.class)
+                        .configure(MongoDBConfigServer.MONGODB_CONF_TEMPLATE_URL, "classpath:///test-mongodb-configserver.conf")));
+        app.start(ImmutableList.of(localhostProvisioningLocation));
+        EntityTestUtils.assertAttributeEqualsEventually(deployment, Startable.SERVICE_UP, true);
+        return deployment;
+    }
+    
+    @Test(groups = "Integration")
+    public void testCanStartAndStopDeployment() {
+        MongoDBShardedDeployment deployment = makeAndStartDeployment();
+        deployment.stop();
+        EntityTestUtils.assertAttributeEqualsEventually(deployment, Startable.SERVICE_UP, false);
+    }
+    
+    @Test(groups = "Integration")
+    public void testDeployedStructure() {
+        MongoDBShardedDeployment deployment = makeAndStartDeployment();
+        MongoDBConfigServerCluster configServers = deployment.getConfigCluster();
+        MongoDBRouterCluster routers = deployment.getRouterCluster();
+        MongoDBShardCluster shards = deployment.getShardCluster();
+        Assert.assertNotNull(configServers);
+        Assert.assertNotNull(routers);
+        Assert.assertNotNull(shards);
+        Assert.assertEquals(configServers.getCurrentSize(), MongoDBShardedDeployment.CONFIG_CLUSTER_SIZE.getDefaultValue());
+        Assert.assertEquals(routers.getCurrentSize(), ROUTER_CLUSTER_SIZE);
+        Assert.assertEquals(shards.getCurrentSize(), SHARD_CLUSTER_SIZE);
+        for (Entity entity : deployment.getShardCluster().getMembers()) {
+            Assert.assertEquals(((MongoDBReplicaSet)entity).getCurrentSize(), REPLICASET_SIZE);
+        }
+        for (Entity entity : configServers.getMembers()) {
+            checkEntityTypeAndServiceUp(entity, MongoDBConfigServer.class);
+        }
+        for (Entity entity : routers.getMembers()) {
+            checkEntityTypeAndServiceUp(entity, MongoDBRouter.class);
+        }
+        for (Entity entity : shards.getMembers()) {
+            checkEntityTypeAndServiceUp(entity, MongoDBReplicaSet.class);
+        }
+    }
+    
+    @Test(groups = "Integration")
+    private void testReadAndWriteDifferentRouters() {
+        MongoDBShardedDeployment deployment = makeAndStartDeployment();
+        EntityTestUtils.assertAttributeEqualsEventually(deployment, Startable.SERVICE_UP, true);
+        MongoDBRouter router1 = (MongoDBRouter) Iterables.get(deployment.getRouterCluster().getMembers(), 0);
+        MongoDBRouter router2 = (MongoDBRouter) Iterables.get(deployment.getRouterCluster().getMembers(), 1);
+        EntityTestUtils.assertAttributeEqualsEventually(router1, Startable.SERVICE_UP, true);
+        EntityTestUtils.assertAttributeEqualsEventually(router2, Startable.SERVICE_UP, true);
+        
+        String documentId = MongoDBTestHelper.insert(router1, "meaning-of-life", 42);
+        DBObject docOut = MongoDBTestHelper.getById(router2, documentId);
+        Assert.assertEquals(docOut.get("meaning-of-life"), 42);
+        
+        for (Entity entity : Iterables.filter(app.getManagementContext().getEntityManager().getEntitiesInApplication(app), AbstractMongoDBServer.class)) {
+            EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true);
+        }
+    }
+    
+    private void checkEntityTypeAndServiceUp(Entity entity, Class<? extends Entity> expectedClass) {
+        Assert.assertNotNull(entity);
+        Assert.assertTrue(expectedClass.isAssignableFrom(entity.getClass()), "expected: " + expectedClass 
+                + " on interfaces, found: " + entity.getClass().getInterfaces());
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/JedisSupport.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/JedisSupport.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/JedisSupport.java
new file mode 100644
index 0000000..81abb42
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/JedisSupport.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.redis;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.entity.nosql.redis.RedisStore;
+
+import redis.clients.jedis.Jedis;
+
+import com.google.common.base.Strings;
+
+/**
+ * {@link RedisStore} testing using Jedis API.
+ */
+public class JedisSupport {
+    private static final String TEST_DATA = Strings.repeat("0123456789", 16);
+
+    private RedisStore redis;
+
+    public JedisSupport(RedisStore redis) {
+        this.redis = redis;
+    }
+
+    /**
+     * Exercise the {@link RedisStore} using the Jedis API.
+     */
+    public void redisTest() throws Exception {
+        writeData("brooklyn", TEST_DATA);
+        String result = readData("brooklyn");
+        assertEquals(result, TEST_DATA);
+    }
+    
+    public void writeData(String key, String val) throws Exception {
+        Jedis client = getRedisClient(redis);
+        try {
+            client.set(key, val);
+        } finally {
+            client.disconnect();
+        }
+    }
+
+    public String readData(String key) throws Exception {
+        Jedis client = getRedisClient(redis);
+        try {
+            return client.get(key);
+        } finally {
+            client.disconnect();
+        }
+    }
+
+    private Jedis getRedisClient(RedisStore redis) {
+        int port = redis.getAttribute(RedisStore.REDIS_PORT);
+        String host = redis.getAttribute(RedisStore.HOSTNAME);
+        Jedis client = new Jedis(host, port);
+        client.connect();
+        assertTrue(client.isConnected());
+        return client;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisClusterIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisClusterIntegrationTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisClusterIntegrationTest.java
new file mode 100644
index 0000000..6331662
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisClusterIntegrationTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.redis;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import org.apache.brooklyn.entity.nosql.redis.RedisCluster;
+import org.apache.brooklyn.entity.nosql.redis.RedisSlave;
+import org.apache.brooklyn.entity.nosql.redis.RedisStore;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.Location;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.test.Asserts;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.TestApplication;
+
+import com.google.common.collect.ImmutableList;
+
+public class RedisClusterIntegrationTest {
+
+    private TestApplication app;
+    private Location loc;
+    private RedisCluster cluster;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setup() {
+        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+        loc = new LocalhostMachineProvisioningLocation();
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void shutdown() {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    @Test(groups = { "Integration" })
+    public void testRedisClusterReplicates() throws Exception {
+        final String key = "mykey";
+        final String val = "1234567890";
+        
+        cluster = app.createAndManageChild(EntitySpec.create(RedisCluster.class)
+                .configure(DynamicCluster.INITIAL_SIZE, 3));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Startable.SERVICE_UP, true);
+
+        RedisStore master = cluster.getMaster();
+        List<RedisSlave> slaves = ImmutableList.<RedisSlave>copyOf((Collection)cluster.getSlaves().getMembers());
+        
+        assertEquals(slaves.size(), 3);
+        
+        JedisSupport viaMaster = new JedisSupport(master);
+        viaMaster.writeData(key, val);
+        assertEquals(viaMaster.readData(key), val);
+
+        for (RedisSlave slave : slaves) {
+            final JedisSupport viaSlave = new JedisSupport(slave);
+            Asserts.succeedsEventually(new Callable<Void>() {
+                @Override public Void call() throws Exception {
+                    assertEquals(viaSlave.readData(key), val);
+                    return null;
+                }});
+        }
+
+        // Check that stopping slave will not stop anything else
+        // (it used to stop master because wasn't supplying port!)
+        slaves.get(0).stop();
+        EntityTestUtils.assertAttributeEqualsEventually(slaves.get(0), Startable.SERVICE_UP, false);
+        
+        assertEquals(master.getAttribute(Startable.SERVICE_UP), Boolean.TRUE);
+        for (RedisSlave slave : slaves.subList(1, slaves.size())) {
+            assertEquals(slave.getAttribute(Startable.SERVICE_UP), Boolean.TRUE);
+        }
+        
+        // Check that stopping cluster will stop everything
+        cluster.stop();
+
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Startable.SERVICE_UP, false);
+        assertEquals(master.getAttribute(Startable.SERVICE_UP), Boolean.FALSE);
+        for (RedisSlave slave : slaves) {
+            assertEquals(slave.getAttribute(Startable.SERVICE_UP), Boolean.FALSE);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisEc2LiveTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisEc2LiveTest.java
new file mode 100644
index 0000000..3d0b421
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisEc2LiveTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.redis;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.entity.nosql.redis.RedisStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.AbstractEc2LiveTest;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.EntityTestUtils;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+
+public class RedisEc2LiveTest extends AbstractEc2LiveTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(RedisEc2LiveTest.class);
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        RedisStore redis = app.createAndManageChild(EntitySpec.create(RedisStore.class));
+        app.start(ImmutableList.of(loc));
+        EntityTestUtils.assertAttributeEqualsEventually(redis, RedisStore.SERVICE_UP, true);
+
+        JedisSupport support = new JedisSupport(redis);
+        support.redisTest();
+        // Confirm sensors are valid
+        EntityTestUtils.assertPredicateEventuallyTrue(redis, new Predicate<RedisStore>() {
+            @Override public boolean apply(@Nullable RedisStore input) {
+                return input != null &&
+                        input.getAttribute(RedisStore.UPTIME) > 0 &&
+                        input.getAttribute(RedisStore.TOTAL_COMMANDS_PROCESSED) >= 0 &&
+                        input.getAttribute(RedisStore.TOTAL_CONNECTIONS_RECEIVED) >= 0 &&
+                        input.getAttribute(RedisStore.EXPIRED_KEYS) >= 0 &&
+                        input.getAttribute(RedisStore.EVICTED_KEYS) >= 0 &&
+                        input.getAttribute(RedisStore.KEYSPACE_HITS) >= 0 &&
+                        input.getAttribute(RedisStore.KEYSPACE_MISSES) >= 0;
+            }
+        });
+
+    }
+
+    @Test(enabled=false)
+    public void testDummy() {} // Convince testng IDE integration that this really does have test methods  
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisIntegrationTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisIntegrationTest.java
new file mode 100644
index 0000000..8af953f
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/redis/RedisIntegrationTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.redis;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.entity.nosql.redis.RedisStore;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.Location;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Test the operation of the {@link RedisStore} class.
+ */
+public class RedisIntegrationTest {
+
+    private TestApplication app;
+    private Location loc;
+    private RedisStore redis;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setup() {
+        app = TestApplication.Factory.newManagedInstanceForTests();
+        loc = app.newLocalhostProvisioningLocation();
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void shutdown() {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+    /**
+     * Test that the server starts up and sets SERVICE_UP correctly.
+     */
+    @Test(groups = { "Integration" })
+    public void canStartupAndShutdown() throws Exception {
+        redis = app.createAndManageChild(EntitySpec.create(RedisStore.class));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(redis, Startable.SERVICE_UP, true);
+
+        redis.stop();
+
+        EntityTestUtils.assertAttributeEqualsEventually(redis, Startable.SERVICE_UP, false);
+    }
+
+    /**
+     * Test that a client can connect to the service.
+     */
+    @Test(groups = { "Integration" })
+    public void testRedisConnection() throws Exception {
+        redis = app.createAndManageChild(EntitySpec.create(RedisStore.class));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(redis, Startable.SERVICE_UP, true);
+
+        JedisSupport support = new JedisSupport(redis);
+        support.redisTest();
+    }
+
+    /**
+     * Test we get sensors from an instance on a non-default port
+     */
+    @Test(groups = { "Integration" })
+    public void testNonStandardPort() throws Exception {
+        redis = app.createAndManageChild(EntitySpec.create(RedisStore.class)
+                .configure(RedisStore.REDIS_PORT, PortRanges.fromString("10000+")));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(redis, Startable.SERVICE_UP, true);
+        JedisSupport support = new JedisSupport(redis);
+        support.redisTest();
+
+        // Increase timeout because test was failing on jenkins sometimes. The log shows only one 
+        // call to `info server` (for obtaining uptime) which took 26 seconds; then 4 seconds later 
+        // this assert failed (with it checking every 500ms). The response did correctly contain
+        // `uptime_in_seconds:27`.
+        EntityTestUtils.assertPredicateEventuallyTrue(ImmutableMap.of("timeout", Duration.FIVE_MINUTES), redis, new Predicate<RedisStore>() {
+            @Override public boolean apply(@Nullable RedisStore input) {
+                return input != null &&
+                        input.getAttribute(RedisStore.UPTIME) > 0 &&
+                        input.getAttribute(RedisStore.TOTAL_COMMANDS_PROCESSED) >= 0 &&
+                        input.getAttribute(RedisStore.TOTAL_CONNECTIONS_RECEIVED) >= 0 &&
+                        input.getAttribute(RedisStore.EXPIRED_KEYS) >= 0 &&
+                        input.getAttribute(RedisStore.EVICTED_KEYS) >= 0 &&
+                        input.getAttribute(RedisStore.KEYSPACE_HITS) >= 0 &&
+                        input.getAttribute(RedisStore.KEYSPACE_MISSES) >= 0;
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakClusterEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakClusterEc2LiveTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakClusterEc2LiveTest.java
new file mode 100644
index 0000000..883299f
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakClusterEc2LiveTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.riak;
+
+import org.apache.brooklyn.entity.nosql.riak.RiakCluster;
+import org.apache.brooklyn.entity.nosql.riak.RiakNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.AbstractEc2LiveTest;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.EntityTestUtils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class RiakClusterEc2LiveTest extends AbstractEc2LiveTest {
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(RiakNodeEc2LiveTest.class);
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        RiakCluster cluster = app.createAndManageChild(EntitySpec.create(RiakCluster.class)
+                .configure(RiakCluster.INITIAL_SIZE, 3)
+                .configure(RiakCluster.MEMBER_SPEC, EntitySpec.create(RiakNode.class)));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, RiakNode.SERVICE_UP, true);
+
+        RiakNode first = (RiakNode) Iterables.get(cluster.getMembers(), 0);
+        RiakNode second = (RiakNode) Iterables.get(cluster.getMembers(), 1);
+
+        assertNodesUpAndInCluster(first, second);
+        
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_UP, true);
+    }
+    
+    private void assertNodesUpAndInCluster(final RiakNode... nodes) {
+        for (final RiakNode node : nodes) {
+            EntityTestUtils.assertAttributeEqualsEventually(node, RiakNode.SERVICE_UP, true);
+            EntityTestUtils.assertAttributeEqualsEventually(node, RiakNode.RIAK_NODE_HAS_JOINED_CLUSTER, true);
+        }
+    }
+
+    @Test(enabled = false)
+    public void testDummy() {
+    } // Convince TestNG IDE integration that this really does have test methods
+
+
+    @Override
+    public void test_Ubuntu_12_0() throws Exception {
+        //Override to add the custom securityGroup for opening Riak ports.
+        // Image: {id=us-east-1/ami-d0f89fb9, providerId=ami-d0f89fb9, name=ubuntu/images/ebs/ubuntu-precise-12.04-amd64-server-20130411.1, location={scope=REGION, id=us-east-1, description=us-east-1, parent=aws-ec2, iso3166Codes=[US-VA]}, os={family=ubuntu, arch=paravirtual, version=12.04, description=099720109477/ubuntu/images/ebs/ubuntu-precise-12.04-amd64-server-20130411.1, is64Bit=true}, description=099720109477/ubuntu/images/ebs/ubuntu-precise-12.04-amd64-server-20130411.1, version=20130411.1, status=AVAILABLE[available], loginUser=ubuntu, userMetadata={owner=099720109477, rootDeviceType=ebs, virtualizationType=paravirtual, hypervisor=xen}}
+        runTest(ImmutableMap.of("imageId", "us-east-1/ami-d0f89fb9", "loginUser", "ubuntu", "hardwareId", SMALL_HARDWARE_ID, "securityGroups", "RiakSecurityGroup"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeEc2LiveTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeEc2LiveTest.java
new file mode 100644
index 0000000..1d69103
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeEc2LiveTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.riak;
+
+import org.apache.brooklyn.entity.nosql.riak.RiakNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import brooklyn.entity.AbstractEc2LiveTest;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.EntityTestUtils;
+
+public class RiakNodeEc2LiveTest extends AbstractEc2LiveTest {
+
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(RiakNodeEc2LiveTest.class);
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        RiakNode entity = app.createAndManageChild(EntitySpec.create(RiakNode.class));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, RiakNode.SERVICE_UP, true);
+
+    }
+
+    @Test(enabled = false)
+    public void testDummy() {
+    } // Convince TestNG IDE integration that this really does have test methods
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeGoogleComputeLiveTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeGoogleComputeLiveTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeGoogleComputeLiveTest.java
new file mode 100644
index 0000000..a5877c4
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeGoogleComputeLiveTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.riak;
+
+import org.apache.brooklyn.entity.nosql.riak.RiakCluster;
+import org.apache.brooklyn.entity.nosql.riak.RiakNode;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import brooklyn.entity.AbstractGoogleComputeLiveTest;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.EntityTestUtils;
+
+public class RiakNodeGoogleComputeLiveTest extends AbstractGoogleComputeLiveTest {
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        RiakCluster cluster = app.createAndManageChild(EntitySpec.create(RiakCluster.class)
+                .configure(RiakCluster.INITIAL_SIZE, 2)
+                .configure(RiakCluster.MEMBER_SPEC, EntitySpec.create(RiakNode.class)));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, RiakCluster.SERVICE_UP, true);
+
+        RiakNode first = (RiakNode) Iterables.get(cluster.getMembers(), 0);
+        RiakNode second = (RiakNode) Iterables.get(cluster.getMembers(), 1);
+
+        EntityTestUtils.assertAttributeEqualsEventually(first, RiakNode.SERVICE_UP, true);
+        EntityTestUtils.assertAttributeEqualsEventually(second, RiakNode.SERVICE_UP, true);
+
+        EntityTestUtils.assertAttributeEqualsEventually(first, RiakNode.RIAK_NODE_HAS_JOINED_CLUSTER, true);
+        EntityTestUtils.assertAttributeEqualsEventually(second, RiakNode.RIAK_NODE_HAS_JOINED_CLUSTER, true);
+
+    }
+
+    @Test(groups = {"Live"})
+    @Override
+    public void test_DefaultImage() throws Exception {
+        super.test_DefaultImage();
+    }
+
+    @Test(enabled = false)
+    public void testDummy() {
+    } // Convince testng IDE integration that this really does have test methods
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeIntegrationTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeIntegrationTest.java
new file mode 100644
index 0000000..50c946f
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeIntegrationTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.riak;
+
+import static org.testng.Assert.assertFalse;
+
+import org.apache.brooklyn.entity.nosql.riak.RiakNode;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.TestApplication;
+
+public class RiakNodeIntegrationTest {
+
+    private TestApplication app;
+    private LocalhostMachineProvisioningLocation localhostProvisioningLocation;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception {
+        localhostProvisioningLocation = new LocalhostMachineProvisioningLocation();
+        app = TestApplication.Factory.newManagedInstanceForTests();
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (app != null) Entities.destroyAll(app.getManagementContext());
+    }
+
+
+    @Test(groups = "Integration")
+    public void testCanStartAndStop() throws Exception {
+        RiakNode entity = app.createAndManageChild(EntitySpec.create(RiakNode.class)
+                .configure(RiakNode.SUGGESTED_VERSION, "2.1.1"));
+        app.start(ImmutableList.of(localhostProvisioningLocation));
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, Startable.SERVICE_UP, true);
+        entity.stop();
+        assertFalse(entity.getAttribute(Startable.SERVICE_UP));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeSoftlayerLiveTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeSoftlayerLiveTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeSoftlayerLiveTest.java
new file mode 100644
index 0000000..069a920
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/riak/RiakNodeSoftlayerLiveTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.riak;
+
+import org.apache.brooklyn.entity.nosql.riak.RiakNode;
+import org.testng.annotations.BeforeMethod;
+
+import com.google.common.collect.ImmutableList;
+
+import brooklyn.entity.AbstractSoftlayerLiveTest;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.EntityTestUtils;
+
+public class RiakNodeSoftlayerLiveTest extends AbstractSoftlayerLiveTest {
+
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        RiakNode entity = app.createAndManageChild(EntitySpec.create(RiakNode.class)
+                .configure(RiakNode.SUGGESTED_VERSION, "2.1.1"));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(entity, RiakNode.SERVICE_UP, true);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/AbstractSolrServerTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/AbstractSolrServerTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/AbstractSolrServerTest.java
new file mode 100644
index 0000000..381a78d
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/AbstractSolrServerTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.solr;
+
+import org.apache.brooklyn.entity.nosql.solr.SolrServer;
+import org.testng.annotations.BeforeMethod;
+
+import brooklyn.entity.BrooklynAppLiveTestSupport;
+import brooklyn.location.Location;
+
+/**
+ * Solr test framework for integration and live tests.
+ */
+public class AbstractSolrServerTest extends BrooklynAppLiveTestSupport {
+
+    protected Location testLocation;
+    protected SolrServer solr;
+
+    @BeforeMethod(alwaysRun = true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        testLocation = app.newLocalhostProvisioningLocation();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrJSupport.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrJSupport.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrJSupport.java
new file mode 100644
index 0000000..d192c05
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrJSupport.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.solr;
+
+import java.util.Map;
+
+import org.apache.brooklyn.entity.nosql.solr.SolrServer;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.client.solrj.impl.HttpSolrServer;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrInputDocument;
+
+import brooklyn.entity.basic.Attributes;
+
+/**
+ * Solr testing using SolrJ API.
+ */
+public class SolrJSupport {
+
+    private final HttpSolrServer server;
+    
+    public SolrJSupport(SolrServer node, String core) {
+        this(node.getAttribute(Attributes.HOSTNAME), node.getSolrPort(), core);
+    }
+    
+    public SolrJSupport(String hostname, int solrPort, String core) {
+        server = new HttpSolrServer(String.format("http://%s:%d/solr/%s", hostname, solrPort, core));
+        server.setMaxRetries(1);
+        server.setConnectionTimeout(5000);
+        server.setSoTimeout(5000);
+    }
+
+    public void commit() throws Exception {
+        server.commit();
+    }
+
+    public void addDocument(Map<String, Object> fields) throws Exception {
+        SolrInputDocument doc = new SolrInputDocument();
+        for (String field : fields.keySet()) {
+            doc.setField(field, fields.get(field));
+        }
+        server.add(doc, 100);
+    }
+
+    public Iterable<SolrDocument> getDocuments() throws Exception {
+        SolrQuery solrQuery = new SolrQuery();
+        solrQuery.setQuery("*:*");
+        
+        return server.query(solrQuery).getResults();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerEc2LiveTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerEc2LiveTest.java
new file mode 100644
index 0000000..ef2f166
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerEc2LiveTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.solr;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.entity.nosql.solr.SolrServer;
+import org.apache.solr.common.SolrDocument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.AbstractEc2LiveTest;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.location.Location;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class SolrServerEc2LiveTest extends AbstractEc2LiveTest {
+
+    private static final Logger log = LoggerFactory.getLogger(SolrServerEc2LiveTest.class);
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        log.info("Testing Solr on {}", loc);
+
+        SolrServer solr = app.createAndManageChild(EntitySpec.create(SolrServer.class)
+                .configure(SolrServer.SOLR_CORE_CONFIG, ImmutableMap.of("example", "classpath://solr/example.tgz")));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(solr, Startable.SERVICE_UP, true);
+
+        SolrJSupport client = new SolrJSupport(solr, "example");
+
+        Iterable<SolrDocument> results = client.getDocuments();
+        assertTrue(Iterables.isEmpty(results));
+
+        client.addDocument(MutableMap.<String, Object>of("id", "1", "description", "first"));
+        client.addDocument(MutableMap.<String, Object>of("id", "2", "description", "second"));
+        client.addDocument(MutableMap.<String, Object>of("id", "3", "description", "third"));
+        client.commit();
+
+        results = client.getDocuments();
+        assertEquals(Iterables.size(results), 3);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerIntegrationTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerIntegrationTest.java
new file mode 100644
index 0000000..23ca974
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerIntegrationTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.solr;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.brooklyn.entity.nosql.solr.SolrServer;
+import org.apache.solr.common.SolrDocument;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+/**
+ * Solr integration tests.
+ *
+ * Test the operation of the {@link SolrServer} class.
+ */
+public class SolrServerIntegrationTest extends AbstractSolrServerTest {
+
+    /**
+     * Test that a node starts and sets SERVICE_UP correctly.
+     */
+    @Test(groups = "Integration")
+    public void canStartupAndShutdown() {
+        solr = app.createAndManageChild(EntitySpec.create(SolrServer.class));
+        app.start(ImmutableList.of(testLocation));
+
+        EntityTestUtils.assertAttributeEqualsEventually(solr, Startable.SERVICE_UP, true);
+        Entities.dumpInfo(app);
+
+        solr.stop();
+
+        EntityTestUtils.assertAttributeEqualsEventually(solr, Startable.SERVICE_UP, false);
+    }
+
+    /**
+     * Test that a core can be created and used with SolrJ client.
+     */
+    @Test(groups = "Integration")
+    public void testConnection() throws Exception {
+        solr = app.createAndManageChild(EntitySpec.create(SolrServer.class)
+                .configure(SolrServer.SOLR_CORE_CONFIG, ImmutableMap.of("example", "classpath://solr/example.tgz")));
+        app.start(ImmutableList.of(testLocation));
+
+        EntityTestUtils.assertAttributeEqualsEventually(solr, Startable.SERVICE_UP, true);
+
+        SolrJSupport client = new SolrJSupport(solr, "example");
+
+        Iterable<SolrDocument> results = client.getDocuments();
+        assertTrue(Iterables.isEmpty(results));
+
+        client.addDocument(MutableMap.<String, Object>of("id", "1", "description", "first"));
+        client.addDocument(MutableMap.<String, Object>of("id", "2", "description", "second"));
+        client.addDocument(MutableMap.<String, Object>of("id", "3", "description", "third"));
+        client.commit();
+
+        results = client.getDocuments();
+        assertEquals(Iterables.size(results), 3);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerLiveTest.java
----------------------------------------------------------------------
diff --git a/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerLiveTest.java b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerLiveTest.java
new file mode 100644
index 0000000..d9fc27f
--- /dev/null
+++ b/software/nosql/src/test/java/org/apache/brooklyn/entity/nosql/solr/SolrServerLiveTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.solr;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.apache.brooklyn.entity.nosql.solr.SolrServer;
+import org.apache.solr.common.SolrDocument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.text.Strings;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+/**
+ * Solr live tests.
+ *
+ * Test the operation of the {@link SolrServer} class using the jclouds {@code rackspace-cloudservers-uk}
+ * and {@code aws-ec2} providers, with different OS images. The tests use the {@link SolrJSupport} class
+ * to exercise the node, and will need to have {@code brooklyn.jclouds.provider.identity} and {@code .credential}
+ * set, usually in the {@code .brooklyn/brooklyn.properties} file.
+ */
+public class SolrServerLiveTest extends AbstractSolrServerTest {
+
+    private static final Logger log = LoggerFactory.getLogger(SolrServerLiveTest.class);
+
+    @DataProvider(name = "virtualMachineData")
+    public Object[][] provideVirtualMachineData() {
+        return new Object[][] { // ImageId, Provider, Region, Description (for logging)
+            new Object[] { "eu-west-1/ami-0307d674", "aws-ec2", "eu-west-1", "Ubuntu Server 14.04 LTS (HVM), SSD Volume Type" },
+            new Object[] { "LON/f9b690bf-88eb-43c2-99cf-391f2558732e", "rackspace-cloudservers-uk", "", "Ubuntu 12.04 LTS (Precise Pangolin)" }, 
+            new Object[] { "LON/a84b1592-6817-42da-a57c-3c13f3cfc1da", "rackspace-cloudservers-uk", "", "CentOS 6.5 (PVHVM)" }, 
+        };
+    }
+
+    @Test(groups = "Live", dataProvider = "virtualMachineData")
+    protected void testOperatingSystemProvider(String imageId, String provider, String region, String description) throws Exception {
+        log.info("Testing Solr on {}{} using {} ({})", new Object[] { provider, Strings.isNonEmpty(region) ? ":" + region : "", description, imageId });
+
+        Map<String, String> properties = MutableMap.of("imageId", imageId);
+        testLocation = app.getManagementContext().getLocationRegistry()
+                .resolve(provider + (Strings.isNonEmpty(region) ? ":" + region : ""), properties);
+        solr = app.createAndManageChild(EntitySpec.create(SolrServer.class)
+                .configure(SolrServer.SOLR_CORE_CONFIG, ImmutableMap.of("example", "classpath://solr/example.tgz")));
+        app.start(ImmutableList.of(testLocation));
+
+        EntityTestUtils.assertAttributeEqualsEventually(solr, Startable.SERVICE_UP, true);
+
+        SolrJSupport client = new SolrJSupport(solr, "example");
+
+        Iterable<SolrDocument> results = client.getDocuments();
+        assertTrue(Iterables.isEmpty(results));
+
+        client.addDocument(MutableMap.<String, Object>of("id", "1", "description", "first"));
+        client.addDocument(MutableMap.<String, Object>of("id", "2", "description", "second"));
+        client.addDocument(MutableMap.<String, Object>of("id", "3", "description", "third"));
+        client.commit();
+
+        results = client.getDocuments();
+        assertEquals(Iterables.size(results), 3);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/usage/cli/src/main/resources/brooklyn/default.catalog.bom
----------------------------------------------------------------------
diff --git a/usage/cli/src/main/resources/brooklyn/default.catalog.bom b/usage/cli/src/main/resources/brooklyn/default.catalog.bom
index 9cc4afd..839662a 100644
--- a/usage/cli/src/main/resources/brooklyn/default.catalog.bom
+++ b/usage/cli/src/main/resources/brooklyn/default.catalog.bom
@@ -183,7 +183,7 @@ brooklyn.catalog:
                             $brooklyn:entity("riak-cluster").attributeWhenReady("main.uri"))
                             
       # use the off-the-shelf Riak cluster
-      - type:           brooklyn.entity.nosql.riak.RiakCluster
+      - type:           org.apache.brooklyn.entity.nosql.riak.RiakCluster
         id:             riak-cluster
         initialSize:    3
         # and add a policy to scale based on ops per minute

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/usage/launcher/src/test/resources/cassandra-blueprint.yaml
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/resources/cassandra-blueprint.yaml b/usage/launcher/src/test/resources/cassandra-blueprint.yaml
index 3af6e7b..1b08802 100644
--- a/usage/launcher/src/test/resources/cassandra-blueprint.yaml
+++ b/usage/launcher/src/test/resources/cassandra-blueprint.yaml
@@ -18,7 +18,7 @@
 #
 name: cassandra-cluster-app
 services:
-- type: brooklyn.entity.nosql.cassandra.CassandraCluster
+- type: org.apache.brooklyn.entity.nosql.cassandra.CassandraCluster
   name: Cassandra Cluster
   brooklyn.config:
     cluster.initial.size: 5

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/usage/launcher/src/test/resources/couchbase-cluster-singleNode.yaml
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/resources/couchbase-cluster-singleNode.yaml b/usage/launcher/src/test/resources/couchbase-cluster-singleNode.yaml
index f40c779..f1f426c 100644
--- a/usage/launcher/src/test/resources/couchbase-cluster-singleNode.yaml
+++ b/usage/launcher/src/test/resources/couchbase-cluster-singleNode.yaml
@@ -21,7 +21,7 @@ name: Couchbase One Bucket
 location: softlayer:wdc01
 
 services:
-- type: brooklyn.entity.nosql.couchbase.CouchbaseCluster
+- type: org.apache.brooklyn.entity.nosql.couchbase.CouchbaseCluster
   initialSize: 1
   intialQuorumSize: 1
   adminUsername: Administrator

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/usage/launcher/src/test/resources/couchbase-cluster.yaml
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/resources/couchbase-cluster.yaml b/usage/launcher/src/test/resources/couchbase-cluster.yaml
index b85884b..48fbc90 100644
--- a/usage/launcher/src/test/resources/couchbase-cluster.yaml
+++ b/usage/launcher/src/test/resources/couchbase-cluster.yaml
@@ -21,7 +21,7 @@ name: Couchbase One Bucket
 location: softlayer:wdc01
 
 services:
-- type: brooklyn.entity.nosql.couchbase.CouchbaseCluster
+- type: org.apache.brooklyn.entity.nosql.couchbase.CouchbaseCluster
   initialSize: 3
   intialQuorumSize: 2
   adminUsername: Administrator

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/d5cf5285/usage/launcher/src/test/resources/couchbase-node.yaml
----------------------------------------------------------------------
diff --git a/usage/launcher/src/test/resources/couchbase-node.yaml b/usage/launcher/src/test/resources/couchbase-node.yaml
index 63a6348..01db0a8 100644
--- a/usage/launcher/src/test/resources/couchbase-node.yaml
+++ b/usage/launcher/src/test/resources/couchbase-node.yaml
@@ -21,6 +21,6 @@ name: Couchbase Node
 location: softlayer:wdc01
 
 services:
-- type: brooklyn.entity.nosql.couchbase.CouchbaseNode
+- type: org.apache.brooklyn.entity.nosql.couchbase.CouchbaseNode
   adminUsername: Administrator
   adminPassword: Password