You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2014/09/19 17:05:46 UTC

[1/5] git commit: Set heap for Tomcat to about 70% of RAM and for ElasticSearch to about 50% of RAM (since it shares RAM with Cassandra).

Repository: incubator-usergrid
Updated Branches:
  refs/heads/two-dot-o 196249344 -> 84433d38a


Set heap for Tomcat to about 70% of RAM and for ElasticSearch to about 50% of RAM (since it shares RAM with Cassandra).


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

Branch: refs/heads/two-dot-o
Commit: 138ead7dc184a7ba0af60f1c8f00659d51a530b3
Parents: 32455d8
Author: Dave Johnson <dm...@apigee.com>
Authored: Tue Sep 16 14:30:30 2014 -0400
Committer: Dave Johnson <dm...@apigee.com>
Committed: Tue Sep 16 14:30:30 2014 -0400

----------------------------------------------------------------------
 .../main/dist/init_instance/init_rest_server.sh | 31 +++++++++++++-------
 .../dist/init_instance/install_cassandra.sh     | 14 ++++-----
 .../dist/init_instance/install_elasticsearch.sh | 30 ++++++++++++-------
 3 files changed, 46 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/138ead7d/stack/awscluster/src/main/dist/init_instance/init_rest_server.sh
----------------------------------------------------------------------
diff --git a/stack/awscluster/src/main/dist/init_instance/init_rest_server.sh b/stack/awscluster/src/main/dist/init_instance/init_rest_server.sh
index fdc83cd..2b03477 100644
--- a/stack/awscluster/src/main/dist/init_instance/init_rest_server.sh
+++ b/stack/awscluster/src/main/dist/init_instance/init_rest_server.sh
@@ -51,38 +51,47 @@ cd /usr/share/usergrid/init_instance
 ./install_yourkit.sh
 
 # set Tomcat memory and threads based on instance type
+# use about 70% of RAM for heap
 export NOFILE=100000
 case `(curl http://169.254.169.254/latest/meta-data/instance-type)` in
 'm1.small' )
-    export TOMCAT_RAM=1250M
+    # total of 1.7g
+    export TOMCAT_RAM=1190m
     export TOMCAT_THREADS=300
 ;;
 'm1.medium' )
-    export TOMCAT_RAM=3G
+    # total of 3.75g
+    export TOMCAT_RAM=2625m
     export TOMCAT_THREADS=500
 ;;
 'm1.large' )
-    export TOMCAT_RAM=6G
+    # total of 7.5g
+    export TOMCAT_RAM=5250m
     export TOMCAT_THREADS=1000
 ;;
 'm1.xlarge' )
-    export TOMCAT_RAM=12G
+    # total of 15g
+    export TOMCAT_RAM=10500m
     export TOMCAT_THREADS=2000
 ;;
-'m3.xlarge' )
-    export TOMCAT_RAM=12G
-    export TOMCAT_THREADS=3300
-;;
 'm3.large' )
-    export TOMCAT_RAM=6G
+    # total of 7.5g
+    export TOMCAT_RAM=5250m
     export TOMCAT_THREADS=1600
 ;;
+'m3.xlarge' )
+    # total of 15g
+    export TOMCAT_RAM=10500m
+    export TOMCAT_THREADS=3300
+;;
 'c3.2xlarge' )
-    export TOMCAT_RAM=12G
+    # total of 15g
+    export TOMCAT_RAM=10500m
     export TOMCAT_THREADS=2000
 ;;
 'c3.4xlarge' )
-    export TOMCAT_RAM=24G
+    # total of 30g
+    export TOMCAT_RAM=21000m
     export TOMCAT_THREADS=4000
 esac
 

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/138ead7d/stack/awscluster/src/main/dist/init_instance/install_cassandra.sh
----------------------------------------------------------------------
diff --git a/stack/awscluster/src/main/dist/init_instance/install_cassandra.sh b/stack/awscluster/src/main/dist/init_instance/install_cassandra.sh
index d62bb92..9e74348 100644
--- a/stack/awscluster/src/main/dist/init_instance/install_cassandra.sh
+++ b/stack/awscluster/src/main/dist/init_instance/install_cassandra.sh
@@ -1,14 +1,14 @@
 #!/bin/bash
 
-# 
+#
 #  Licensed to the Apache Software Foundation (ASF) under one or more
 #   contributor license agreements.  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.
@@ -22,12 +22,13 @@
 # Install and stop Cassandra
 pushd /etc/apt/sources.list.d
 
-cat >> cassandra.sources.list << EOF
+sudo cat >> cassandra.sources.list << EOF
 deb http://www.apache.org/dist/cassandra/debian 12x main
+#deb http://debian.datastax.com/community stable main
 EOF
+
 apt-get update
-apt-get -y --force-yes install libcap2
-apt-get --force-yes -y install cassandra
+apt-get -y --force-yes install libcap2 cassandra=1.2.11
 /etc/init.d/cassandra stop
 
 mkdir -p /mnt/data/cassandra
@@ -40,7 +41,6 @@ groovy wait_for_instances.groovy cassandra ${CASSANDRA_NUM_SERVERS}
 
 cd /usr/share/usergrid/scripts
 groovy configure_cassandra.groovy > /etc/cassandra/cassandra.yaml
-
 /etc/init.d/cassandra start
 
 # Install opscenter

http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/138ead7d/stack/awscluster/src/main/dist/init_instance/install_elasticsearch.sh
----------------------------------------------------------------------
diff --git a/stack/awscluster/src/main/dist/init_instance/install_elasticsearch.sh b/stack/awscluster/src/main/dist/init_instance/install_elasticsearch.sh
index d398591..540aeca 100644
--- a/stack/awscluster/src/main/dist/init_instance/install_elasticsearch.sh
+++ b/stack/awscluster/src/main/dist/init_instance/install_elasticsearch.sh
@@ -42,31 +42,39 @@ cd /usr/share/usergrid/scripts
 #groovy registry_register.groovy elasticsearch
 #groovy wait_for_instances.groovy elasticsearch ${CASSANDRA_NUM_SERVERS}
 
-# use about one quarter of RAM for heap
+# leave room for Cassandra: use about one half of RAM for heap
 case `(curl http://169.254.169.254/latest/meta-data/instance-type)` in
 'm1.small' )
-    export ES_HEAP_SIZE=512M
+    # total of 1.7g
+    export ES_HEAP_SIZE=850m
 ;;
 'm1.medium' )
-    export ES_HEAP_SIZE=1G
+    # total of 3.75g
+    export ES_HEAP_SIZE=1700m
 ;;
 'm1.large' )
-    export ES_HEAP_SIZE=2G
+    # total of 7.5g
+    export ES_HEAP_SIZE=3500m
 ;;
 'm1.xlarge' )
-    export ES_HEAP_SIZE=5G
-;;
-'m3.xlarge' )
-    export ES_HEAP_SIZE=5G
+    # total of 15g
+    export ES_HEAP_SIZE=7500m
 ;;
 'm3.large' )
-    export ES_HEAP_SIZE=2G
+    # total of 7.5g
+    export ES_HEAP_SIZE=3500m
+;;
+'m3.xlarge' )
+    # total of 15g 
+    export ES_HEAP_SIZE=7500m
 ;;
 'c3.2xlarge' )
-    export ES_HEAP_SIZE=4G
+    # total of 15g
+    export ES_HEAP_SIZE=7500m
 ;;
 'c3.4xlarge' )
-    export ES_HEAP_SIZE=12G
+    # total of 30g
+    export ES_HEAP_SIZE=15g
 esac
 
 cat >> /etc/default/elasticsearch << EOF


[5/5] git commit: Merge branch 'two-dot-o' of https://git-wip-us.apache.org/repos/asf/incubator-usergrid into two-dot-o

Posted by sn...@apache.org.
Merge branch 'two-dot-o' of https://git-wip-us.apache.org/repos/asf/incubator-usergrid into two-dot-o


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

Branch: refs/heads/two-dot-o
Commit: 84433d38a5166e235a58a8dcb026df0be13706ba
Parents: e07202f 1962493
Author: Dave Johnson <dm...@apigee.com>
Authored: Fri Sep 19 11:05:22 2014 -0400
Committer: Dave Johnson <dm...@apigee.com>
Committed: Fri Sep 19 11:05:22 2014 -0400

----------------------------------------------------------------------
 .../apache/usergrid/metrics/MetricsFactory.java |  16 +-
 .../notifications/ApplicationQueueManager.java  | 285 ++++++++++---------
 .../services/notifications/JobScheduler.java    |  10 -
 .../notifications/NotificationsService.java     |  21 +-
 .../services/notifications/QueueJob.java        |   4 +-
 .../notifications/SingleQueueTaskManager.java   |  17 +-
 .../services/notifications/TaskManager.java     | 199 -------------
 .../services/notifications/gcm/GCMAdapter.java  |  61 +---
 8 files changed, 196 insertions(+), 417 deletions(-)
----------------------------------------------------------------------



[3/5] git commit: Test to show that cursor is not required for result sets that do not require it.

Posted by sn...@apache.org.
Test to show that cursor is not required for result sets that do not require it.


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

Branch: refs/heads/two-dot-o
Commit: dfc0d96b67f301e8784f11c148287d81c6415704
Parents: 6ae1a66
Author: Dave Johnson <dm...@apigee.com>
Authored: Thu Sep 18 10:27:53 2014 -0400
Committer: Dave Johnson <dm...@apigee.com>
Committed: Thu Sep 18 10:27:53 2014 -0400

----------------------------------------------------------------------
 .../collection/PagingResourceIT.java            | 52 ++++++++++++++++++--
 1 file changed, 48 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/dfc0d96b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/PagingResourceIT.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/PagingResourceIT.java b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/PagingResourceIT.java
index c7a7a84..7008f3e 100644
--- a/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/PagingResourceIT.java
+++ b/stack/rest/src/test/java/org/apache/usergrid/rest/applications/collection/PagingResourceIT.java
@@ -24,6 +24,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import javax.ws.rs.core.MediaType;
 import org.apache.usergrid.cassandra.Concurrent;
 import org.apache.usergrid.java.client.entities.Entity;
 import org.apache.usergrid.java.client.response.ApiResponse;
@@ -34,6 +35,7 @@ import org.apache.usergrid.rest.test.resource.EntityResource;
 import static org.apache.usergrid.utils.MapUtils.hashMap;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import org.junit.Ignore;
@@ -52,6 +54,13 @@ public class PagingResourceIT extends AbstractRestIT {
     @Rule
     public TestContextSetup context = new TestContextSetup( this );
 
+    private static ObjectMapper mapper = new ObjectMapper();
+
+    private static final ApiResponse parse( JsonNode response ) throws Exception {
+        String jsonResponseString = mapper.writeValueAsString( response );
+        return mapper.readValue( jsonResponseString, ApiResponse.class );
+    }
+
 
     @Test
     public void collectionPaging() throws Exception {
@@ -229,11 +238,46 @@ public class PagingResourceIT extends AbstractRestIT {
     }
 
 
-    private static ObjectMapper mapper = new ObjectMapper();
+    @Test
+    public void testCursor() throws Exception {
+
+        // test that we do get cursor when we need one
+        // create 50 widgets
+        int widgetsSize = 50;
+        CustomCollection widgets = context.application().collection("widgets");
+        for (int i = 0; i < widgetsSize; i++) {
+            Map<String, String> entity = hashMap("name", String.valueOf(i));
+            widgets.create(entity);
+        }
+        refreshIndex(context.getOrgName(), context.getAppName());
+
+        // fetch all widgets 
+        JsonNode widgetsNode = mapper.readTree(
+            resource().path("/" + context.getOrgName() + "/" + context.getAppName() + "/widgets")
+                .queryParam("access_token", context.getActiveUser().getToken())
+                .accept(MediaType.APPLICATION_JSON)
+                .get(String.class));
+        assertEquals(10, widgetsNode.get("count").asInt()); // get back default page size of 10
+        assertNotNull(widgetsNode.get("cursor")); // with a cursor
+
+        // test that we DO NOT get cursor when we should not get cursor
+        // create 5 trinkets
+        int trinketsSize = 5;
+        CustomCollection trinkets = context.application().collection("trinkets");
+        for (int i = 0; i < trinketsSize; i++) {
+            Map<String, String> entity = hashMap("name", String.valueOf(i));
+            trinkets.create(entity);
+        }
+        refreshIndex(context.getOrgName(), context.getAppName());
 
+        // fetch all trinkets 
+        JsonNode trinketsNode = mapper.readTree(
+            resource().path("/" + context.getOrgName() + "/" + context.getAppName() + "/trinkets")
+                .queryParam("access_token", context.getActiveUser().getToken())
+                .accept(MediaType.APPLICATION_JSON)
+                .get(String.class));
+        assertEquals(trinketsSize, trinketsNode.get("count").asInt()); // get back all 
+        assertNull(trinketsNode.get("cursor")); // and no cursor
 
-    private static final ApiResponse parse( JsonNode response ) throws Exception {
-        String jsonResponseString = mapper.writeValueAsString( response );
-        return mapper.readValue( jsonResponseString, ApiResponse.class );
     }
 }


[2/5] git commit: Test partial update via PUT with both /users/ and /users/ forms of URL

Posted by sn...@apache.org.
Test partial update via PUT with both /users/<username> and /users/<uuid> forms of URL


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

Branch: refs/heads/two-dot-o
Commit: 6ae1a668f6b18d42ccf9109ddae747cf0b97efda
Parents: 138ead7
Author: Dave Johnson <dm...@apigee.com>
Authored: Thu Sep 18 10:27:25 2014 -0400
Committer: Dave Johnson <dm...@apigee.com>
Committed: Thu Sep 18 10:27:25 2014 -0400

----------------------------------------------------------------------
 .../apache/usergrid/rest/PartialUpdateTest.java | 47 ++++++++++++++++++--
 1 file changed, 43 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/6ae1a668/stack/rest/src/test/java/org/apache/usergrid/rest/PartialUpdateTest.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/PartialUpdateTest.java b/stack/rest/src/test/java/org/apache/usergrid/rest/PartialUpdateTest.java
index 366df14..7e6e364 100644
--- a/stack/rest/src/test/java/org/apache/usergrid/rest/PartialUpdateTest.java
+++ b/stack/rest/src/test/java/org/apache/usergrid/rest/PartialUpdateTest.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import javax.ws.rs.core.MediaType;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 import org.junit.Rule;
@@ -61,18 +62,18 @@ public class PartialUpdateTest extends AbstractRestIT {
         assertNotNull( userNode );
         String uuid = userNode.withArray("entities").get(0).get("uuid").asText();
         assertNotNull( uuid );
-
         refreshIndex( "test-organization", "test-app" );
 
-        // update user bart passing only an update to his employer
+        // Update bart's employer without specifying any required fields 
+        // (with uuid specified in URL)
 
         Map<String, Object> updateProperties = new LinkedHashMap<String, Object>() {{
             put( "employer", "Initech" );
         }};
 
         try {
-            JsonNode updatedNode = mapper.readTree( 
-                resource().path( "/test-organization/test-app/user/" + uuid )
+            mapper.readTree( 
+                resource().path( "/test-organization/test-app/user/" + uuid ) 
                     .queryParam( "access_token", adminAccessToken )
                     .accept( MediaType.APPLICATION_JSON )
                     .type( MediaType.APPLICATION_JSON )
@@ -81,5 +82,43 @@ public class PartialUpdateTest extends AbstractRestIT {
         } catch ( UniformInterfaceException uie ) {
             fail("Update failed due to: " + uie.getResponse().getEntity(String.class));
         }
+        refreshIndex( "test-organization", "test-app" );
+
+        userNode = mapper.readTree( 
+            resource().path( "/test-organization/test-app/users/" + uuid )
+                .queryParam( "access_token", adminAccessToken )
+                .accept( MediaType.APPLICATION_JSON )
+                .get( String.class ));
+        assertNotNull( userNode );
+        assertEquals( "Initech", userNode.withArray("entities").get(0).get("employer").asText());
+
+
+        // Update bart's employer without specifying any required fields 
+        // (this time with username specified in URL)
+
+        updateProperties = new LinkedHashMap<String, Object>() {{
+            put( "employer", "ACME Corporation" );
+        }};
+
+        try {
+            mapper.readTree( 
+                resource().path( "/test-organization/test-app/users/bart")
+                    .queryParam( "access_token", adminAccessToken )
+                    .accept( MediaType.APPLICATION_JSON )
+                    .type( MediaType.APPLICATION_JSON )
+                    .put( String.class, updateProperties ));
+
+        } catch ( UniformInterfaceException uie ) {
+            fail("Update failed due to: " + uie.getResponse().getEntity(String.class));
+        }
+        refreshIndex( "test-organization", "test-app" );
+
+        userNode = mapper.readTree( 
+            resource().path( "/test-organization/test-app/users/bart" )
+                .queryParam( "access_token", adminAccessToken )
+                .accept( MediaType.APPLICATION_JSON )
+                .get( String.class ));
+        assertNotNull( userNode );
+        assertEquals( "ACME Corporation", userNode.withArray("entities").get(0).get("employer").asText());
     }
 }


[4/5] git commit: Beginnings of a REST level notifications test.

Posted by sn...@apache.org.
Beginnings of a REST level notifications test.


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

Branch: refs/heads/two-dot-o
Commit: e07202f014a9b9d39d8b2e272efcb745ba93d5ce
Parents: dfc0d96
Author: Dave Johnson <dm...@apigee.com>
Authored: Fri Sep 19 11:04:42 2014 -0400
Committer: Dave Johnson <dm...@apigee.com>
Committed: Fri Sep 19 11:04:42 2014 -0400

----------------------------------------------------------------------
 .../apache/usergrid/rest/NotificationsIT.java   | 170 +++++++++++++++++++
 1 file changed, 170 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-usergrid/blob/e07202f0/stack/rest/src/test/java/org/apache/usergrid/rest/NotificationsIT.java
----------------------------------------------------------------------
diff --git a/stack/rest/src/test/java/org/apache/usergrid/rest/NotificationsIT.java b/stack/rest/src/test/java/org/apache/usergrid/rest/NotificationsIT.java
new file mode 100644
index 0000000..f729b93
--- /dev/null
+++ b/stack/rest/src/test/java/org/apache/usergrid/rest/NotificationsIT.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2014 The Apache Software Foundation.
+ *
+ * Licensed 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.usergrid.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.lang3.time.StopWatch;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test Notification end-points.
+ */
+public class NotificationsIT extends AbstractRestIT {
+    private static final Logger logger = LoggerFactory.getLogger( NotificationsIT.class );
+  
+    @Test
+    public void testBasicOperation() throws Exception {
+
+        int numDevices = 10;
+        int numNotifications = 50; // to send to each device
+
+        String token = userToken( "ed@anuff.com", "sesame" );
+        String org = "test-organization";
+        String app = "test-app";
+        String orgapp = org + "/" + app;
+
+        // create notifier
+        Map<String, Object> notifier = new HashMap<String, Object>() {{
+            put("name", "mynotifier");
+            put("provider", "noop");
+        }};
+        JsonNode notifierNode = mapper.readTree(resource().path( orgapp + "/notifier")
+            .queryParam( "access_token", token )
+            .accept( MediaType.APPLICATION_JSON ).type( MediaType.APPLICATION_JSON )
+            .post(String.class, notifier ) );
+
+        logger.debug("Notifier is: " + notifierNode.toString());
+        assertEquals( "noop", notifierNode.withArray("entities").get(0).get("provider").asText()); 
+        
+        refreshIndex( org, app );
+
+        // create devices
+        StopWatch sw = new StopWatch();
+        sw.start();
+        int devicesCount = 0;
+        List<String> deviceIds = new ArrayList();
+        for (int i=0; i<numDevices; i++) {
+          
+            final int deviceNum = i;
+            Map<String, Object> device = new HashMap<String, Object>() {{
+                put("name", "device" + deviceNum);
+                put("deviceModel", "iPhone6+");
+                put("deviceOSVersion", "iOS 8");
+                put("devicePlatform", "Apple");
+                put("deviceOSVersion", "8");
+                put("mynotifier.notifier.id", "pushtoken" + deviceNum);
+            }};
+
+            JsonNode deviceNode = mapper.readTree(resource().path( orgapp + "/devices")
+                .queryParam( "access_token", token )
+                .accept( MediaType.APPLICATION_JSON ).type( MediaType.APPLICATION_JSON )
+                .post(String.class, device ) );
+
+            logger.debug("Device is: " + deviceNode.toString());
+            assertEquals( "device"+i, deviceNode.withArray("entities").get(0).get("name").asText()); 
+
+            deviceIds.add(deviceNode.withArray("entities").get(0).get("uuid").asText());
+            devicesCount++;
+        }
+        sw.stop();
+        logger.info("Created {} devices in {}ms", devicesCount, sw.getTime());
+
+        refreshIndex( org, app );
+
+        // send notifications 
+        sw.reset();
+        sw.start();
+        int notificationCount = 0;
+        for (int i=0; i<numNotifications; i++) {
+
+            // send a notificaton to each device
+            for (int j=0; j<deviceIds.size(); j++) {
+
+                final String deviceId = deviceIds.get(j);
+                Map<String, Object> notification = new HashMap<String, Object>() {{
+                    put("payloads", new HashMap<String, Object>() {{
+                        put("mynotifier", "Hello device " + deviceId);
+                    }});
+                }};
+
+                JsonNode notificationNode = mapper.readTree(resource().path( orgapp + "/notifications")
+                    .queryParam( "access_token", token )
+                    .accept( MediaType.APPLICATION_JSON ).type( MediaType.APPLICATION_JSON )
+                    .post( String.class, notification ) );
+
+                logger.debug("Notification is: " + notificationNode.toString());
+            }
+            notificationCount++;
+        }
+        sw.stop();
+        logger.info("Created {} notifications in {}ms", notificationCount, sw.getTime());
+        logger.info("Post Notification throughput = {} TPS", ((float)notificationCount) / (sw.getTime()/1000));
+
+        refreshIndex( org, app );
+
+        logger.info("Waiting for all notifications to be sent");
+        sw.reset();
+        sw.start();
+        boolean allSent = false;
+        while (!allSent) {
+
+            Thread.sleep(1000); 
+
+            JsonNode finishedNode = mapper.readTree( resource().path(orgapp + "/notifications")
+                .queryParam("ql", "select * where state='FINISHED'")
+                .queryParam("access_token", token)
+                .accept(MediaType.APPLICATION_JSON)
+                .get(String.class));
+
+            int finished = finishedNode.get("count").asInt();
+            if ( finishedNode.get("cursor") != null ) {
+                String cursor = finishedNode.get("cursor").asText();
+                while ( cursor != null ) {
+
+                    JsonNode moreNode = mapper.readTree( resource().path(orgapp + "/notifications")
+                        .queryParam("ql", "select * where state='FINISHED'")
+                        .queryParam("access_token", token)
+                        .queryParam("cursor", cursor)
+                        .accept(MediaType.APPLICATION_JSON)
+                        .get(String.class));
+
+                    finished += moreNode.get("count").asInt();
+
+                    if ( moreNode.get("cursor") != null ) {
+                        cursor = moreNode.get("cursor").asText();
+                    } else {
+                        cursor = null;
+                    }
+                } 
+            }
+
+            if ( finished == (numDevices * numNotifications) ) {
+                allSent = true;
+            }
+        }
+        notificationCount = numDevices * numNotifications; 
+        logger.info("Finished {} notifications in {}ms", notificationCount, sw.getTime());
+        logger.info("Finished Notification throughput = {} TPS", ((float)notificationCount) / (sw.getTime()/1000));
+    }
+}