You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by za...@apache.org on 2013/11/09 01:36:01 UTC

[1/2] JCLOUDS-215 - Adds Webhook and Execution support to autoscale

Updated Branches:
  refs/heads/master 1c871e12e -> e052acb29


http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApiLiveTest.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApiLiveTest.java b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApiLiveTest.java
new file mode 100644
index 0000000..a6bed11
--- /dev/null
+++ b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApiLiveTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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.jclouds.rackspace.autoscale.v1.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.openstack.v2_0.domain.Link;
+import org.jclouds.rackspace.autoscale.v1.domain.Group;
+import org.jclouds.rackspace.autoscale.v1.domain.GroupConfiguration;
+import org.jclouds.rackspace.autoscale.v1.domain.LaunchConfiguration;
+import org.jclouds.rackspace.autoscale.v1.domain.LaunchConfiguration.LaunchConfigurationType;
+import org.jclouds.rackspace.autoscale.v1.domain.LoadBalancer;
+import org.jclouds.rackspace.autoscale.v1.domain.Personality;
+import org.jclouds.rackspace.autoscale.v1.domain.ScalingPolicy;
+import org.jclouds.rackspace.autoscale.v1.domain.ScalingPolicy.ScalingPolicyTargetType;
+import org.jclouds.rackspace.autoscale.v1.domain.ScalingPolicy.ScalingPolicyType;
+import org.jclouds.rackspace.autoscale.v1.domain.ScalingPolicyResponse;
+import org.jclouds.rackspace.autoscale.v1.domain.Webhook;
+import org.jclouds.rackspace.autoscale.v1.domain.WebhookResponse;
+import org.jclouds.rackspace.autoscale.v1.internal.BaseAutoscaleApiLiveTest;
+import org.jclouds.rackspace.autoscale.v1.utils.AutoscaleUtils;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.Uninterruptibles;
+
+/**
+ * Webhook live test
+ * 
+ * @author Zack Shoylev
+ */
+@Test(groups = "live", testName = "WebhookApiLiveTest", singleThreaded = true)
+public class WebhookApiLiveTest extends BaseAutoscaleApiLiveTest {
+
+   private static Map<String, List<Group>> created = Maps.newHashMap();
+
+   @Override
+   @BeforeClass(groups = { "integration", "live" })
+   public void setup() {
+      super.setup();
+      for (String zone : api.getConfiguredZones()) {
+         List<Group> createdGroupList = Lists.newArrayList();
+         created.put(zone, createdGroupList);
+         GroupApi groupApi = api.getGroupApiForZone(zone);
+
+         GroupConfiguration groupConfiguration = GroupConfiguration.builder().maxEntities(10).cooldown(3)
+               .name("testscalinggroup198547").minEntities(0)
+               .metadata(ImmutableMap.of("gc_meta_key_2", "gc_meta_value_2", "gc_meta_key_1", "gc_meta_value_1"))
+               .build();
+
+         LaunchConfiguration launchConfiguration = LaunchConfiguration
+               .builder()
+               .loadBalancers(ImmutableList.of(LoadBalancer.builder().port(8080).id(9099).build()))
+               .serverName("autoscale_server")
+               .serverImageRef("c52a0ca6-c1f2-4cd1-b7d6-afbcd1ebda22")
+               .serverFlavorRef("2")
+               .serverDiskConfig("AUTO")
+               .serverMetadata(
+                     ImmutableMap
+                     .of("build_config", "core", "meta_key_1", "meta_value_1", "meta_key_2", "meta_value_2"))
+                     .networks(
+                           ImmutableList.of("11111111-1111-1111-1111-111111111111", "00000000-0000-0000-0000-000000000000"))
+                           .personalities(
+                                 ImmutableList.of(Personality.builder().path("/root/.csivh")
+                                       .contents("VGhpcyBpcyBhIHRlc3QgZmlsZS4=").build()))
+                                       .type(LaunchConfigurationType.LAUNCH_SERVER).build();
+
+         List<ScalingPolicy> scalingPolicies = Lists.newArrayList();
+
+         ScalingPolicy scalingPolicy = ScalingPolicy.builder().cooldown(3).type(ScalingPolicyType.WEBHOOK)
+               .name("scale up by 1").targetType(ScalingPolicyTargetType.INCREMENTAL).target("1").build();
+         scalingPolicies.add(scalingPolicy);
+
+         Group g = groupApi.create(groupConfiguration, launchConfiguration, scalingPolicies);
+         createdGroupList.add(g);
+
+         WebhookApi webhookApi = api.getWebhookApiForGroupAndPolicyInZone(g.getId(), g.getScalingPolicies().iterator().next().getId(), zone);
+         assertFalse( webhookApi.create("test_webhook", ImmutableMap.<String, Object>of()).isEmpty() );
+
+         assertNotNull(g);
+         assertNotNull(g.getId());
+         assertEquals(g.getLinks().size(), 1);
+         assertEquals(g.getLinks().get(0).getHref().toString(),
+               "https://" + zone.toLowerCase() + ".autoscale.api.rackspacecloud.com/v1.0/" + api.getCurrentTenantId().get().getId() + "/groups/" + g.getId() + "/");
+         assertEquals(g.getLinks().get(0).getRelation(), Link.Relation.SELF);
+
+         assertNotNull(g.getScalingPolicies().get(0).getId());
+         assertEquals(g.getScalingPolicies().get(0).getLinks().size(), 1);
+         assertEquals(
+               g.getScalingPolicies().get(0).getLinks().get(0).getHref().toString(),
+               "https://" + zone.toLowerCase() + ".autoscale.api.rackspacecloud.com/v1.0/" + api.getCurrentTenantId().get().getId() + "/groups/" + g.getId() + "/policies/" + g.getScalingPolicies().get(0).getId() +"/");
+         assertEquals(g.getScalingPolicies().get(0).getLinks().get(0).getRelation(), Link.Relation.SELF);
+         assertEquals(g.getScalingPolicies().get(0).getCooldown(), 3);
+         assertEquals(g.getScalingPolicies().get(0).getTarget(), "1");
+         assertEquals(g.getScalingPolicies().get(0).getTargetType(), ScalingPolicyTargetType.INCREMENTAL);
+         assertEquals(g.getScalingPolicies().get(0).getType(), ScalingPolicyType.WEBHOOK);
+         assertEquals(g.getScalingPolicies().get(0).getName(), "scale up by 1");
+
+         assertEquals(g.getLaunchConfiguration().getLoadBalancers().size(), 1);
+         assertEquals(g.getLaunchConfiguration().getLoadBalancers().get(0).getId(), 9099);
+         assertEquals(g.getLaunchConfiguration().getLoadBalancers().get(0).getPort(), 8080);
+         assertEquals(g.getLaunchConfiguration().getServerName(), "autoscale_server");
+         assertNotNull(g.getLaunchConfiguration().getServerImageRef());
+         assertEquals(g.getLaunchConfiguration().getServerFlavorRef(), "2");
+         assertEquals(g.getLaunchConfiguration().getServerDiskConfig(), "AUTO");
+         assertEquals(g.getLaunchConfiguration().getPersonalities().size(), 1);
+         assertEquals(g.getLaunchConfiguration().getPersonalities().get(0).getPath(), "/root/.csivh");
+         assertEquals(g.getLaunchConfiguration().getPersonalities().get(0).getContents(),
+               "VGhpcyBpcyBhIHRlc3QgZmlsZS4=");
+         assertEquals(g.getLaunchConfiguration().getNetworks().size(), 2);
+         assertEquals(g.getLaunchConfiguration().getNetworks().get(0), "11111111-1111-1111-1111-111111111111");
+         assertEquals(g.getLaunchConfiguration().getNetworks().get(1), "00000000-0000-0000-0000-000000000000");
+         assertEquals(g.getLaunchConfiguration().getServerMetadata().size(), 3);
+         assertTrue(g.getLaunchConfiguration().getServerMetadata().containsKey("build_config"));
+         assertTrue(g.getLaunchConfiguration().getServerMetadata().containsValue("core"));
+         assertEquals(g.getLaunchConfiguration().getType(), LaunchConfigurationType.LAUNCH_SERVER);
+
+         assertEquals(g.getGroupConfiguration().getMaxEntities(), 10);
+         assertEquals(g.getGroupConfiguration().getCooldown(), 3);
+         assertEquals(g.getGroupConfiguration().getName(), "testscalinggroup198547");
+         assertEquals(g.getGroupConfiguration().getMinEntities(), 0);
+         assertEquals(g.getGroupConfiguration().getMetadata().size(), 2);
+         assertTrue(g.getGroupConfiguration().getMetadata().containsKey("gc_meta_key_2"));
+         assertTrue(g.getGroupConfiguration().getMetadata().containsValue("gc_meta_value_2"));
+      }
+   }
+
+   @Test
+   public void testCreateWebhook() {
+      for (String zone : api.getConfiguredZones()) {
+         Group g = created.get(zone).get(0);
+         WebhookApi webhookApi = api.getWebhookApiForGroupAndPolicyInZone(g.getId(), g.getScalingPolicies().iterator().next().getId(), zone);
+         WebhookResponse webhook = webhookApi.create("test1", ImmutableMap.<String, Object>of("notes", "test metadata")).first().get();
+
+         assertEquals(webhook.getName(), "test1");
+         assertEquals(webhook.getMetadata().get("notes"), "test metadata");
+      }
+   }
+
+   @Test
+   public void testCreateWebhooks() {
+      for (String zone : api.getConfiguredZones()) {
+         Group g = created.get(zone).get(0);
+         WebhookApi webhookApi = api.getWebhookApiForGroupAndPolicyInZone(g.getId(), g.getScalingPolicies().iterator().next().getId(), zone);
+         FluentIterable<WebhookResponse> webhookList = webhookApi.create(
+               ImmutableList.of(
+                     Webhook.builder().name("test5").metadata(null).build(),
+                     Webhook.builder().name("test6").metadata(ImmutableMap.<String, Object>of("notes2", "different test")).build()
+                     ));
+
+         assertEquals(webhookList.get(0).getName(), "test5");
+         assertNull(webhookList.get(0).getMetadata().get("notes"));
+         assertEquals(webhookList.get(1).getName(), "test6");
+         assertEquals(webhookList.get(1).getMetadata().get("notes2"), "different test");
+      }
+   }
+
+   @Test
+   public void testUpdateWebhook() {
+      for (String zone : api.getConfiguredZones()) {
+         Group g = created.get(zone).get(0);
+         WebhookApi webhookApi = api.getWebhookApiForGroupAndPolicyInZone(g.getId(), g.getScalingPolicies().iterator().next().getId(), zone);
+         String webhookId = webhookApi.list().first().get().getId();
+         assertTrue( webhookApi.update(webhookId, "updated_name", ImmutableMap.<String, Object>of()) );
+
+         WebhookResponse webhook= webhookApi.get(webhookId);
+         assertEquals(webhook.getName(), "updated_name");
+         assertTrue( webhook.getMetadata().isEmpty() );
+      }
+   }
+
+   @Test
+   public void testGetWebhook() {
+      for (String zone : api.getConfiguredZones()) {
+         Group g = created.get(zone).get(0);
+         WebhookApi webhookApi = api.getWebhookApiForGroupAndPolicyInZone(g.getId(), g.getScalingPolicies().iterator().next().getId(), zone);
+         WebhookResponse webhookList = webhookApi.list().first().get();
+         WebhookResponse webhookGet = webhookApi.get(webhookList.getId());
+         assertNotNull(webhookList);
+         assertNotNull(webhookGet);
+         assertEquals(webhookList, webhookGet);
+      }
+   }
+
+   @Test
+   public void testListWebhook() {
+      for (String zone : api.getConfiguredZones()) {
+         Group g = created.get(zone).get(0);
+         WebhookApi webhookApi = api.getWebhookApiForGroupAndPolicyInZone(g.getId(), g.getScalingPolicies().iterator().next().getId(), zone);
+         assertFalse( webhookApi.list().isEmpty() );
+      }
+   }
+
+   @Test
+   public void testDeleteWebhook() {
+      for (String zone : api.getConfiguredZones()) {
+         Group g = created.get(zone).get(0);
+         WebhookApi webhookApi = api.getWebhookApiForGroupAndPolicyInZone(g.getId(), g.getScalingPolicies().iterator().next().getId(), zone);
+         WebhookResponse webhook = webhookApi.create("test1", ImmutableMap.<String, Object>of("notes", "test metadata")).first().get();
+
+         assertEquals(webhook.getName(), "test1");
+         assertEquals(webhook.getMetadata().get("notes"), "test metadata");
+         
+         assertTrue( webhookApi.delete(webhook.getId()) );
+         assertNull( webhookApi.get(webhook.getId()) );
+      }
+   }
+
+   @Test
+   public void testExecuteWebhook() throws IOException {
+      for (String zone : api.getConfiguredZones()) {
+         Group g = created.get(zone).get(0);
+         WebhookApi webhookApi = api.getWebhookApiForGroupAndPolicyInZone(g.getId(), g.getScalingPolicies().iterator().next().getId(), zone);
+         WebhookResponse webhook = webhookApi.create("test_execute", ImmutableMap.<String, Object>of("notes", "test metadata")).first().get();
+         
+         assertTrue( AutoscaleUtils.execute(webhook.getAnonymousExecutionURI().get()) , " for " + webhook + " in " + zone);
+      }
+   }
+
+   @Test
+   public void testExecuteWebhookFail() throws IOException, URISyntaxException {
+      for (String zone : api.getConfiguredZones()) {
+         Group g = created.get(zone).get(0);
+         WebhookApi webhookApi = api.getWebhookApiForGroupAndPolicyInZone(g.getId(), g.getScalingPolicies().iterator().next().getId(), zone);
+         WebhookResponse webhook = webhookApi.create("test_execute_fail", ImmutableMap.<String, Object>of("notes", "test metadata")).first().get();
+         
+         URI uri = new URI(webhook.getAnonymousExecutionURI().toString() + "123");
+         assertFalse( AutoscaleUtils.execute(uri) );
+      }
+   }
+
+   @Override
+   @AfterClass(groups = { "integration", "live" })
+   public void tearDown() {
+      for (String zone : api.getConfiguredZones()) {
+         GroupApi groupApi = api.getGroupApiForZone(zone);
+         for (Group group : created.get(zone)) {
+            PolicyApi policyApi = api.getPolicyApiForGroupInZone(group.getId(), zone);
+            if(policyApi == null)continue;
+            for(ScalingPolicyResponse sgr : policyApi.list()) {
+               if(!policyApi.delete(sgr.getId())) {
+                  System.out.println("Could not delete an autoscale policy after tests!");
+               }
+            }
+
+            List<ScalingPolicy> scalingPolicies = Lists.newArrayList();
+
+            ScalingPolicy scalingPolicy = ScalingPolicy.builder()
+                  .cooldown(2)
+                  .type(ScalingPolicyType.WEBHOOK)
+                  .name("0 machines")
+                  .targetType(ScalingPolicyTargetType.DESIRED_CAPACITY)
+                  .target("0")
+                  .build();
+            scalingPolicies.add(scalingPolicy);
+
+            FluentIterable<ScalingPolicyResponse> scalingPolicyResponse = policyApi.create(scalingPolicies);
+            String policyId = scalingPolicyResponse.iterator().next().getId();
+
+            Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
+
+            policyApi.execute(policyId);
+
+            Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
+            policyApi.delete(policyId);
+
+            if (!groupApi.delete(group.getId()))
+               System.out.println("Could not delete an autoscale group after tests!");
+         }
+      }
+      super.tearDown();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/resources/autoscale_webhook_create_request.json
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/resources/autoscale_webhook_create_request.json b/rackspace-autoscale/src/test/resources/autoscale_webhook_create_request.json
new file mode 100644
index 0000000..b7ed42d
--- /dev/null
+++ b/rackspace-autoscale/src/test/resources/autoscale_webhook_create_request.json
@@ -0,0 +1,8 @@
+[
+    {
+        "metadata": {
+            "notes": "PagerDuty will fire this webhook"
+        },
+        "name": "PagerDuty"
+    }
+]

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/resources/autoscale_webhook_create_response.json
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/resources/autoscale_webhook_create_response.json b/rackspace-autoscale/src/test/resources/autoscale_webhook_create_response.json
new file mode 100644
index 0000000..7c15764
--- /dev/null
+++ b/rackspace-autoscale/src/test/resources/autoscale_webhook_create_response.json
@@ -0,0 +1,21 @@
+{
+    "webhooks": [
+        {
+            "id": "152054a3-e0ab-445b-941d-9f8e360c9eed",
+            "links": [
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/676873/groups/605e13f6-1452-4588-b5da-ac6bb468c5bf/policies/eb0fe1bf-3428-4f34-afd9-a5ac36f60511/webhooks/152054a3-e0ab-445b-941d-9f8e360c9eed/",
+                    "rel": "self"
+                },
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/execute/1/0077882e9626d83ef30e1ca379c8654d86cd34df3cd49ac8da72174668315fe8/",
+                    "rel": "capability"
+                }
+            ],
+            "metadata": {
+                "notes": "PagerDuty will fire this webhook"
+            },
+            "name": "PagerDuty"
+        }
+    ]
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/resources/autoscale_webhook_get_response.json
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/resources/autoscale_webhook_get_response.json b/rackspace-autoscale/src/test/resources/autoscale_webhook_get_response.json
index fa1d859..307c396 100644
--- a/rackspace-autoscale/src/test/resources/autoscale_webhook_get_response.json
+++ b/rackspace-autoscale/src/test/resources/autoscale_webhook_get_response.json
@@ -1,17 +1,17 @@
 {
     "webhook": {
-        "id":"{webhookId}",
-        "name": "webhook name",
+        "id":"5555",
+        "name": "alice",
         "metadata": {},
         "links": [
-            {
-                "href": ".../{groupId1}/policies/{policyId1}/webhooks/{webhookId}/",
-                "rel": "self"
-            },
-            {
-                "href": ".../execute/1/{capabilityHash2}",
-                "rel": "capability"
-            }
-        ]
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/676873/groups/605e13f6-1452-4588-b5da-ac6bb468c5bf/policies/eb0fe1bf-3428-4f34-afd9-a5ac36f60511/webhooks/152054a3-e0ab-445b-941d-9f8e360c9eed/",
+                    "rel": "self"
+                },
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/execute/1/0077882e9626d83ef30e1ca379c8654d86cd34df3cd49ac8da72174668315fe8/",
+                    "rel": "capability"
+                }
+            ]
     }
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/resources/autoscale_webhook_list_response.json
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/resources/autoscale_webhook_list_response.json b/rackspace-autoscale/src/test/resources/autoscale_webhook_list_response.json
new file mode 100644
index 0000000..1158fe0
--- /dev/null
+++ b/rackspace-autoscale/src/test/resources/autoscale_webhook_list_response.json
@@ -0,0 +1,37 @@
+{
+    "webhooks": [
+        {
+            "id": "152054a3-e0ab-445b-941d-9f8e360c9eed",
+            "links": [
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/676873/groups/605e13f6-1452-4588-b5da-ac6bb468c5bf/policies/eb0fe1bf-3428-4f34-afd9-a5ac36f60511/webhooks/152054a3-e0ab-445b-941d-9f8e360c9eed/",
+                    "rel": "self"
+                },
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/execute/1/0077882e9626d83ef30e1ca379c8654d86cd34df3cd49ac8da72174668315fe8/",
+                    "rel": "capability"
+                }
+            ],
+            "metadata": {
+                "notes": "PagerDuty will fire this webhook"
+            },
+            "name": "PagerDuty"
+        },
+        {
+            "id": "23037efb-53a9-4ae5-bc33-e89a56b501b6",
+            "links": [
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/676873/groups/605e13f6-1452-4588-b5da-ac6bb468c5bf/policies/eb0fe1bf-3428-4f34-afd9-a5ac36f60511/webhooks/23037efb-53a9-4ae5-bc33-e89a56b501b6/",
+                    "rel": "self"
+                },
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/execute/1/4f767340574433927a26dc747253dad643d5d13ec7b66b764dcbf719b32302b9/",
+                    "rel": "capability"
+                }
+            ],
+            "metadata": {},
+            "name": "Nagios"
+        }
+    ],
+    "webhooks_links": []
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/resources/autoscale_webhooks_create_request.json
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/resources/autoscale_webhooks_create_request.json b/rackspace-autoscale/src/test/resources/autoscale_webhooks_create_request.json
new file mode 100644
index 0000000..eb3f257
--- /dev/null
+++ b/rackspace-autoscale/src/test/resources/autoscale_webhooks_create_request.json
@@ -0,0 +1,11 @@
+[
+    {
+        "metadata": {
+            "notes": "PagerDuty will fire this webhook"
+        },
+        "name": "PagerDuty"
+    },
+    {
+        "name": "Nagios"
+    }
+]

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/resources/autoscale_webhooks_create_response.json
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/resources/autoscale_webhooks_create_response.json b/rackspace-autoscale/src/test/resources/autoscale_webhooks_create_response.json
new file mode 100644
index 0000000..70aeb23
--- /dev/null
+++ b/rackspace-autoscale/src/test/resources/autoscale_webhooks_create_response.json
@@ -0,0 +1,36 @@
+{
+    "webhooks": [
+        {
+            "id": "152054a3-e0ab-445b-941d-9f8e360c9eed",
+            "links": [
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/676873/groups/605e13f6-1452-4588-b5da-ac6bb468c5bf/policies/eb0fe1bf-3428-4f34-afd9-a5ac36f60511/webhooks/152054a3-e0ab-445b-941d-9f8e360c9eed/",
+                    "rel": "self"
+                },
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/execute/1/0077882e9626d83ef30e1ca379c8654d86cd34df3cd49ac8da72174668315fe8/",
+                    "rel": "capability"
+                }
+            ],
+            "metadata": {
+                "notes": "PagerDuty will fire this webhook"
+            },
+            "name": "PagerDuty"
+        },
+        {
+            "id": "23037efb-53a9-4ae5-bc33-e89a56b501b6",
+            "links": [
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/676873/groups/605e13f6-1452-4588-b5da-ac6bb468c5bf/policies/eb0fe1bf-3428-4f34-afd9-a5ac36f60511/webhooks/23037efb-53a9-4ae5-bc33-e89a56b501b6/",
+                    "rel": "self"
+                },
+                {
+                    "href": "https://dfw.autoscale.api.rackspacecloud.com/v1.0/execute/1/4f767340574433927a26dc747253dad643d5d13ec7b66b764dcbf719b32302b9/",
+                    "rel": "capability"
+                }
+            ],
+            "metadata": {},
+            "name": "Nagios"
+        }
+    ]
+}


[2/2] git commit: JCLOUDS-215 - Adds Webhook and Execution support to autoscale

Posted by za...@apache.org.
JCLOUDS-215 - Adds Webhook and Execution support to autoscale


Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/commit/e052acb2
Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/tree/e052acb2
Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/diff/e052acb2

Branch: refs/heads/master
Commit: e052acb29699334be0262cc0cf918dcf1b4f67f7
Parents: 1c871e1
Author: Zack Shoylev <za...@rackspace.com>
Authored: Fri Nov 1 15:34:25 2013 -0500
Committer: Zack Shoylev <za...@rackspace.com>
Committed: Fri Nov 8 15:14:08 2013 -0600

----------------------------------------------------------------------
 .../us/v1/AutoscaleUSProviderMetadata.java      |   9 +-
 .../features/AutoscaleUSGroupApiLiveTest.java   |   4 +-
 .../AutoscaleUSScalingPolicyApiLiveTest.java    |   4 +-
 .../features/AutoscaleUSWebhookApiLiveTest.java |  32 ++
 rackspace-autoscale/pom.xml                     |  12 +-
 .../rackspace/autoscale/v1/AutoscaleApi.java    |  10 +
 .../autoscale/v1/binders/BindWebhookToJson.java |  60 ++++
 .../v1/binders/BindWebhookUpdateToJson.java     |  58 ++++
 .../v1/binders/BindWebhooksToJson.java          |  65 ++++
 .../rackspace/autoscale/v1/domain/Webhook.java  | 134 ++++++++
 .../autoscale/v1/domain/WebhookResponse.java    | 106 +++++++
 .../autoscale/v1/features/GroupApi.java         |  15 +-
 .../autoscale/v1/features/PolicyApi.java        |  10 +-
 .../autoscale/v1/features/WebhookApi.java       | 157 ++++++++++
 .../autoscale/v1/utils/AutoscaleUtils.java      |  52 ++++
 .../v1/features/GroupApiExpectTest.java         |   4 +-
 .../autoscale/v1/features/GroupApiLiveTest.java |  13 +-
 .../v1/features/ScalingPolicyApiLiveTest.java   |  36 +--
 .../v1/features/WebhookApiExpectTest.java       | 187 ++++++++++++
 .../v1/features/WebhookApiLiveTest.java         | 304 +++++++++++++++++++
 .../autoscale_webhook_create_request.json       |   8 +
 .../autoscale_webhook_create_response.json      |  21 ++
 .../autoscale_webhook_get_response.json         |  22 +-
 .../autoscale_webhook_list_response.json        |  37 +++
 .../autoscale_webhooks_create_request.json      |  11 +
 .../autoscale_webhooks_create_response.json     |  36 +++
 26 files changed, 1337 insertions(+), 70 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale-us/src/main/java/org/jclouds/rackspace/autoscale/us/v1/AutoscaleUSProviderMetadata.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale-us/src/main/java/org/jclouds/rackspace/autoscale/us/v1/AutoscaleUSProviderMetadata.java b/rackspace-autoscale-us/src/main/java/org/jclouds/rackspace/autoscale/us/v1/AutoscaleUSProviderMetadata.java
index c7844ea..27036f8 100644
--- a/rackspace-autoscale-us/src/main/java/org/jclouds/rackspace/autoscale/us/v1/AutoscaleUSProviderMetadata.java
+++ b/rackspace-autoscale-us/src/main/java/org/jclouds/rackspace/autoscale/us/v1/AutoscaleUSProviderMetadata.java
@@ -62,14 +62,16 @@ public class AutoscaleUSProviderMetadata extends BaseProviderMetadata {
       super(builder);
    }
 
+   // NOTE: SYD is disabled for now. Enable when service is enabled in SYD
    public static Properties defaultProperties() {
       Properties properties = new Properties();
       properties.setProperty(CREDENTIAL_TYPE, CloudIdentityCredentialTypes.API_KEY_CREDENTIALS);
       properties.setProperty(SERVICE_TYPE, "rax:autoscale"); 
-      properties.setProperty(PROPERTY_ZONES, "ORD,DFW,SYD");
+      //properties.setProperty(PROPERTY_ZONES, "ORD,DFW,SYD");
+      properties.setProperty(PROPERTY_ZONES, "ORD,DFW");
       properties.setProperty(PROPERTY_ZONE + ".ORD." + ISO3166_CODES, "US-IL");
       properties.setProperty(PROPERTY_ZONE + ".DFW." + ISO3166_CODES, "US-TX");
-      properties.setProperty(PROPERTY_ZONE + ".SYD." + ISO3166_CODES, "AU-NSW");
+      //properties.setProperty(PROPERTY_ZONE + ".SYD." + ISO3166_CODES, "AU-NSW");
       return properties;
    }
 
@@ -94,7 +96,8 @@ public class AutoscaleUSProviderMetadata extends BaseProviderMetadata {
                      .homepage(URI.create("http://www.rackspace.com/cloud/public/autoscale/"))
                      .console(URI.create("https://mycloud.rackspace.com"))
                      .linkedServices("rackspace-cloudservers-us", "cloudfiles-us")
-                     .iso3166Codes("US-IL", "US-TX", "AU-NSW")
+                     //.iso3166Codes("US-IL", "US-TX", "AU-NSW")
+                     .iso3166Codes("US-IL", "US-TX")
                      .endpoint("https://identity.api.rackspacecloud.com/v2.0/")
                      .defaultProperties(AutoscaleUSProviderMetadata.defaultProperties());
       }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSGroupApiLiveTest.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSGroupApiLiveTest.java b/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSGroupApiLiveTest.java
index 3c3b7f0..017a84a 100644
--- a/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSGroupApiLiveTest.java
+++ b/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSGroupApiLiveTest.java
@@ -20,11 +20,11 @@ import org.jclouds.rackspace.autoscale.v1.features.GroupApiLiveTest;
 import org.testng.annotations.Test;
 
 /**
- * Tests GroupApi Guice wiring and parsing
+ * Tests GroupApi - live
  *
  * @author Zack Shoylev
  */
-@Test(groups = "unit", testName = "AutoscaleUSGroupApiLiveTest", singleThreaded = true)
+@Test(groups = "live", testName = "AutoscaleUSGroupApiLiveTest", singleThreaded = true)
 public class AutoscaleUSGroupApiLiveTest extends GroupApiLiveTest {
    public AutoscaleUSGroupApiLiveTest() {
       provider = "rackspace-autoscale-us";

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSScalingPolicyApiLiveTest.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSScalingPolicyApiLiveTest.java b/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSScalingPolicyApiLiveTest.java
index 12c9d26..6c737fc 100644
--- a/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSScalingPolicyApiLiveTest.java
+++ b/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSScalingPolicyApiLiveTest.java
@@ -20,11 +20,11 @@ import org.jclouds.rackspace.autoscale.v1.features.ScalingPolicyApiLiveTest;
 import org.testng.annotations.Test;
 
 /**
- * Tests GroupApi Guice wiring and parsing
+ * Tests Scaling Policy Api - live
  *
  * @author Zack Shoylev
  */
-@Test(groups = "unit", testName = "AutoscaleUSGroupApiLiveTest", singleThreaded = true)
+@Test(groups = "live", testName = "AutoscaleUSScalingPolicyApiLiveTest", singleThreaded = true)
 public class AutoscaleUSScalingPolicyApiLiveTest extends ScalingPolicyApiLiveTest {
    public AutoscaleUSScalingPolicyApiLiveTest() {
       provider = "rackspace-autoscale-us";

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSWebhookApiLiveTest.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSWebhookApiLiveTest.java b/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSWebhookApiLiveTest.java
new file mode 100644
index 0000000..2a76398
--- /dev/null
+++ b/rackspace-autoscale-us/src/test/java/org/jclouds/rackspace/autoscale/us/v1/features/AutoscaleUSWebhookApiLiveTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.jclouds.rackspace.autoscale.us.v1.features;
+
+import org.jclouds.rackspace.autoscale.v1.features.WebhookApiLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ * Tests WebhookApi - live
+ *
+ * @author Zack Shoylev
+ */
+@Test(groups = "live", testName = "AutoscaleUSWebhookApiLiveTest", singleThreaded = true)
+public class AutoscaleUSWebhookApiLiveTest extends WebhookApiLiveTest {
+   public AutoscaleUSWebhookApiLiveTest() {
+      provider = "rackspace-autoscale-us";
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/pom.xml
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/pom.xml b/rackspace-autoscale/pom.xml
index 8a7d2b1..c3537dc 100644
--- a/rackspace-autoscale/pom.xml
+++ b/rackspace-autoscale/pom.xml
@@ -72,20 +72,20 @@
       <version>${project.parent.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.apache.jclouds.api</groupId>
-      <artifactId>openstack-keystone</artifactId>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-core</artifactId>
       <version>${project.parent.version}</version>
-      <type>test-jar</type>
-      <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jclouds</groupId>
       <artifactId>jclouds-core</artifactId>
       <version>${project.parent.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.apache.jclouds</groupId>
-      <artifactId>jclouds-core</artifactId>
+      <groupId>org.apache.jclouds.api</groupId>
+      <artifactId>openstack-keystone</artifactId>
       <version>${project.parent.version}</version>
       <type>test-jar</type>
       <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/AutoscaleApi.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/AutoscaleApi.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/AutoscaleApi.java
index 1ae148d..50c4cc6 100644
--- a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/AutoscaleApi.java
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/AutoscaleApi.java
@@ -28,6 +28,7 @@ import org.jclouds.location.functions.ZoneToEndpoint;
 import org.jclouds.openstack.keystone.v2_0.domain.Tenant;
 import org.jclouds.rackspace.autoscale.v1.features.GroupApi;
 import org.jclouds.rackspace.autoscale.v1.features.PolicyApi;
+import org.jclouds.rackspace.autoscale.v1.features.WebhookApi;
 import org.jclouds.rest.annotations.Delegate;
 import org.jclouds.rest.annotations.EndpointParam;
 
@@ -63,6 +64,15 @@ public interface AutoscaleApi extends Closeable{
    @Delegate
    @Path("/groups/{groupId}")
    PolicyApi getPolicyApiForGroupInZone(@PathParam("groupId") String groupId, 
+         @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);   
+
+   /**
+    * Provides access to webhook management features.
+    */
+   @Delegate
+   @Path("/groups/{groupId}/policies/{policyId}")
+   WebhookApi getWebhookApiForGroupAndPolicyInZone(@PathParam("groupId") String groupId, 
+         @PathParam("policyId") String policyId, 
          @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
 
    /**

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhookToJson.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhookToJson.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhookToJson.java
new file mode 100644
index 0000000..3db6f64
--- /dev/null
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhookToJson.java
@@ -0,0 +1,60 @@
+/*
+ * 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.jclouds.rackspace.autoscale.v1.binders;
+
+import java.util.Map;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+
+/**
+ * Bind parameters to a webhook
+ * @author Zack Shoylev
+ */
+public class BindWebhookToJson implements MapBinder {
+
+   private final BindToJsonPayload jsonBinder;
+
+   @Inject
+   private BindWebhookToJson(BindToJsonPayload jsonBinder) {
+      this.jsonBinder = jsonBinder;
+   }
+
+   // This to be refactored with the other BindWebhook bindings
+   @SuppressWarnings("unchecked")
+   @Override    
+   public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+      ImmutableMap.Builder<String, Object> webhook = ImmutableMap.builder();
+      webhook.put("name", postParams.get("name"));
+      ImmutableMap<String, Object> metadata = postParams.get("metadata")!=null ? 
+            ImmutableMap.copyOf((Map<String, Object>) postParams.get("metadata")) : ImmutableMap.<String, Object>of(); 
+      if (!metadata.isEmpty()) {
+         webhook.put("metadata", postParams.get("metadata"));
+      }
+      return jsonBinder.bindToRequest(request, ImmutableList.of(webhook.build()));
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object toBind) {
+      throw new IllegalStateException("Create webhook is a POST operation");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhookUpdateToJson.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhookUpdateToJson.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhookUpdateToJson.java
new file mode 100644
index 0000000..ff33c3a
--- /dev/null
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhookUpdateToJson.java
@@ -0,0 +1,58 @@
+/*
+ * 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.jclouds.rackspace.autoscale.v1.binders;
+
+import java.util.Map;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+
+/**
+ * Bind parameters to a webhook
+ * @author Zack Shoylev
+ */
+public class BindWebhookUpdateToJson implements MapBinder {
+
+   private final BindToJsonPayload jsonBinder;
+
+   @Inject
+   private BindWebhookUpdateToJson(BindToJsonPayload jsonBinder) {
+      this.jsonBinder = jsonBinder;
+   }
+
+   // This binding will potentially get refactored, as right now it is very close, but not completely the same as the create call.
+   // Refactoring will depend on whether this call will change any further.
+   @SuppressWarnings("unchecked")
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+      ImmutableMap.Builder<String, Object> webhook = ImmutableMap.builder();
+      webhook.put("name", postParams.get("name"));
+      ImmutableMap<String, Object> metadata = postParams.get("metadata")!=null ? 
+            ImmutableMap.copyOf((Map<String, Object>) postParams.get("metadata")) : ImmutableMap.<String, Object>of();
+      webhook.put("metadata", metadata);
+      return jsonBinder.bindToRequest(request, webhook.build());
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object toBind) {
+      throw new IllegalStateException("Create webhook is a POST operation");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhooksToJson.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhooksToJson.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhooksToJson.java
new file mode 100644
index 0000000..2185b67
--- /dev/null
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/binders/BindWebhooksToJson.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.jclouds.rackspace.autoscale.v1.binders;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rackspace.autoscale.v1.domain.Webhook;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+
+/**
+ * Bind parameters to a webhook
+ * @author Zack Shoylev
+ */
+public class BindWebhooksToJson implements MapBinder {
+
+   private final BindToJsonPayload jsonBinder;
+
+   @Inject
+   private BindWebhooksToJson(BindToJsonPayload jsonBinder) {
+      this.jsonBinder = jsonBinder;
+   }
+
+   @SuppressWarnings("unchecked")
+   @Override
+   // This binding will potentially get refactored, as right now it is very close, but not completely the same as the create call.
+   // Refactoring will depend on whether this call will change any further.
+   public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+      ImmutableList.Builder<Map<String, Object>> webhookListBuilder = ImmutableList.builder();
+      for(Webhook webhook : (List<Webhook>)postParams.get("webhooks") ) {
+         ImmutableMap.Builder<String, Object> webhookMap = ImmutableMap.builder();
+         webhookMap.put("name", webhook.getName());
+         if(!webhook.getMetadata().isEmpty()) {
+            webhookMap.put("metadata", webhook.getMetadata());
+         }
+         webhookListBuilder.add((Map<String, Object>)webhookMap.build());
+      }
+      return jsonBinder.bindToRequest(request, webhookListBuilder.build());
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object toBind) {
+      throw new IllegalStateException("Create webhook is a POST operation");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/domain/Webhook.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/domain/Webhook.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/domain/Webhook.java
new file mode 100644
index 0000000..76bff6f
--- /dev/null
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/domain/Webhook.java
@@ -0,0 +1,134 @@
+/*
+ * 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.jclouds.rackspace.autoscale.v1.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+import java.util.Map;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * An Autoscale Webhook for a specific group and policy.
+ * 
+ * @see Group
+ * @see ScalingPolicy
+ * @author Zack Shoylev
+ */
+public class Webhook {
+   private final String name;
+   private final ImmutableMap<String, Object> metadata;
+
+   @ConstructorProperties({ "name", "metadata" })
+   protected Webhook(String name, @Nullable Map<String, Object> metadata) {
+      this.name = checkNotNull(name, "name should not be null");
+      if (metadata == null) {
+         this.metadata = ImmutableMap.of();
+      } else {
+         this.metadata = ImmutableMap.copyOf(metadata);
+      }
+   }
+
+   /**
+    * @return the name of this Webhook.
+    * @see Webhook.Builder#name(String)
+    */
+   public String getName() {
+      return this.name;
+   }
+
+   /**
+    * @return the metadata for this Webhook.
+    * @see Webhook.Builder#metadata(Map)
+    */
+   public ImmutableMap<String, Object> getMetadata() {
+      return this.metadata;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(name, metadata);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj)
+         return true;
+      if (obj == null || getClass() != obj.getClass())
+         return false;
+      Webhook that = Webhook.class.cast(obj);
+      return Objects.equal(this.name, that.name) && Objects.equal(this.metadata, that.metadata);
+   }
+
+   protected ToStringHelper string() {
+      return Objects.toStringHelper(this).add("name", name).add("metadata", metadata);
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return new Builder().fromWebhook(this);
+   }
+
+   public static class Builder {
+      protected String name;
+      protected Map<String, Object> metadata;
+
+      /**
+       * @param name The name of this Webhook.
+       * @return The builder object.
+       * @see Webhook#getName()
+       */
+      public Builder name(String name) {
+         this.name = name;
+         return this;
+      }
+
+      /**
+       * @param metadata The metadata of this Webhook.
+       * @return The builder object.
+       * @see Webhook#getMetadata()
+       */
+      public Builder metadata(Map<String, Object> metadata) {
+         this.metadata = metadata;
+         return this;
+      }
+
+      /**
+       * @return A new Webhook object.
+       */
+      public Webhook build() {
+         return new Webhook(name, metadata);
+      }
+
+      public Builder fromWebhook(Webhook in) {
+         return this.name(in.getName()).metadata(in.getMetadata());
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/domain/WebhookResponse.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/domain/WebhookResponse.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/domain/WebhookResponse.java
new file mode 100644
index 0000000..8abe691
--- /dev/null
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/domain/WebhookResponse.java
@@ -0,0 +1,106 @@
+/*
+ * 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.jclouds.rackspace.autoscale.v1.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.openstack.v2_0.domain.Link;
+import org.jclouds.openstack.v2_0.domain.Link.Relation;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Autoscale WebhookResponse. Extends Webhook with id and links.
+ * 
+ * @see Webhook#getWebhooks()
+ * @author Zack Shoylev
+ */
+public class WebhookResponse extends Webhook{
+   private final ImmutableList<Link> links;
+   private final String id;
+
+   @ConstructorProperties({
+      "name", "metadata", "links", "id"
+   })
+   public WebhookResponse(String name, @Nullable Map<String, Object> metadata, List<Link> links, String id) {
+      super(name, metadata);
+      this.id = checkNotNull(id, "id required");
+      this.links = ImmutableList.copyOf(checkNotNull(links, "links required"));
+   }
+
+   /**
+    * @return the unique id of this ScalingPolicy.
+    * @see ScalingPolicyResponse.Builder#id(String)
+    */
+   public String getId() {
+      return this.id;
+   }   
+
+   /**
+    * The capability Link for the webhook can be called with a POST request to execute the webhook anonymously.
+    * @return the links to this ScalingPolicy.
+    * @see ScalingPolicyResponse.Builder#links(String)
+    */
+   public ImmutableList<Link> getLinks() {
+      return this.links;
+   }
+   
+   public Optional<URI> getAnonymousExecutionURI() {
+      // TODO: Add Relation.CAPABILITY to openstack Link
+      for (Link l : this.links) {
+         if (l.getRelation() == Relation.UNRECOGNIZED) {
+            return Optional.of(l.getHref());
+         }
+      }
+      return Optional.absent();
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(super.hashCode(), links, id);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null || getClass() != obj.getClass()) return false;
+      WebhookResponse that = WebhookResponse.class.cast(obj);
+      return Objects.equal(this.id, that.id) && 
+            Objects.equal(this.links, that.links) &&
+            super.equals(obj);
+   }
+
+   protected ToStringHelper string() {
+      return super.string()
+            .add("links", links)
+            .add("id", id);
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }   
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/GroupApi.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/GroupApi.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/GroupApi.java
index abad72f..c78f658 100644
--- a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/GroupApi.java
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/GroupApi.java
@@ -16,6 +16,7 @@
  */
 package org.jclouds.rackspace.autoscale.v1.features;
 
+import java.io.Closeable;
 import java.util.List;
 
 import javax.inject.Named;
@@ -56,7 +57,8 @@ import com.google.common.collect.FluentIterable;
  * @author Zack Shoylev
  */
 @RequestFilters(AuthenticateRequest.class)
-public interface GroupApi {
+@Consumes(MediaType.APPLICATION_JSON)
+public interface GroupApi extends Closeable {
 
    /**
     * Create a scaling group.
@@ -72,7 +74,6 @@ public interface GroupApi {
    @Named("Group:create")
    @POST
    @Path("/groups")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(NullOnNotFoundOr404.class)
    @MapBinder(BindCreateGroupToJson.class)
    @ResponseParser(ParseGroupResponse.class)
@@ -90,7 +91,6 @@ public interface GroupApi {
    @Named("Groups:pause/{groupId}")
    @POST
    @Path("/groups/{groupId}/pause")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(FalseOnNotFoundOr404.class)
    boolean pause(@PathParam("groupId") String groupId);
 
@@ -104,7 +104,6 @@ public interface GroupApi {
    @Named("Groups:resume/{groupId}")
    @POST
    @Path("/groups/{groupId}/resume")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(FalseOnNotFoundOr404.class)
    boolean resume(@PathParam("groupId") String groupId);
 
@@ -117,7 +116,6 @@ public interface GroupApi {
    @Named("Groups:delete/{id}")
    @DELETE
    @Path("/groups/{id}")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(FalseOnNotFoundOr404.class)
    boolean delete(@PathParam("id") String groupId);
 
@@ -129,7 +127,6 @@ public interface GroupApi {
    @Named("Group:get/{id}")
    @GET
    @Path("/groups/{id}")
-   @Consumes(MediaType.APPLICATION_JSON)
    @ResponseParser(ParseGroupResponse.class)
    @Fallback(NullOnNotFoundOr404.class)
    Group get(@PathParam("id") String id);
@@ -144,7 +141,6 @@ public interface GroupApi {
    @GET
    @Path("/groups/{id}/state")
    @SelectJson("group")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(NullOnNotFoundOr404.class)
    GroupState getState(@PathParam("id") String id);
 
@@ -157,7 +153,6 @@ public interface GroupApi {
    @GET
    @Path("/groups")
    @SelectJson("groups")
-   @Consumes(MediaType.APPLICATION_JSON)
    FluentIterable<GroupState> listGroupStates();
    
    /**
@@ -169,7 +164,6 @@ public interface GroupApi {
    @GET
    @Path("/groups/{groupId}/config")
    @SelectJson("groupConfiguration")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(NullOnNotFoundOr404.class)
    GroupConfiguration getGroupConfiguration(@PathParam("groupId") String id);
    
@@ -181,7 +175,6 @@ public interface GroupApi {
    @Named("Group:updateConfiguration")
    @PUT
    @Path("/groups/{groupId}/config")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(FalseOnNotFoundOr404.class)
    @MapBinder(BindToGroupConfigurationRequestPayload.class)
    boolean updateGroupConfiguration(@PathParam("groupId") String id,
@@ -195,7 +188,6 @@ public interface GroupApi {
    @Named("Group:launchConfiguration")
    @GET
    @Path("/groups/{groupId}/launch")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(NullOnNotFoundOr404.class)
    @ResponseParser(ParseGroupLaunchConfigurationResponse.class)
    LaunchConfiguration getLaunchConfiguration(@PathParam("groupId") String id);
@@ -208,7 +200,6 @@ public interface GroupApi {
    @Named("Group:updateLaunchConfiguration")
    @PUT
    @Path("/groups/{groupId}/launch")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(FalseOnNotFoundOr404.class)
    @MapBinder(BindLaunchConfigurationToJson.class)
    boolean updateLaunchConfiguration(@PathParam("groupId") String id, 

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/PolicyApi.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/PolicyApi.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/PolicyApi.java
index fdfc0e2..2e3f2b1 100644
--- a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/PolicyApi.java
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/PolicyApi.java
@@ -17,6 +17,7 @@
 package org.jclouds.rackspace.autoscale.v1.features;
 
 
+import java.io.Closeable;
 import java.util.List;
 
 import javax.inject.Named;
@@ -53,7 +54,8 @@ import com.google.common.collect.FluentIterable;
  * @author Zack Shoylev
  */
 @RequestFilters(AuthenticateRequest.class)
-public interface PolicyApi {
+@Consumes(MediaType.APPLICATION_JSON)
+public interface PolicyApi extends Closeable {
    /**
     * Create a scaling policy.
     * @param scalingPolicies The list of scaling policies.
@@ -64,7 +66,6 @@ public interface PolicyApi {
    @Named("Policy:create")
    @POST
    @Path("/policies")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
    @MapBinder(BindScalingPoliciesToJson.class)
    @ResponseParser(ParseScalingPoliciesResponse.class)
@@ -79,7 +80,6 @@ public interface PolicyApi {
    @GET
    @Path("/policies")
    @ResponseParser(ParseScalingPoliciesResponse.class)
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
    FluentIterable<ScalingPolicyResponse> list();
 
@@ -92,7 +92,6 @@ public interface PolicyApi {
    @GET
    @Path("/policies/{scalingPolicyId}")
    @ResponseParser(ParseScalingPolicyResponse.class)
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(NullOnNotFoundOr404.class)
    ScalingPolicyResponse get(@PathParam("scalingPolicyId") String scalingPolicyId);
 
@@ -104,7 +103,6 @@ public interface PolicyApi {
    @Named("Policy:update")
    @PUT
    @Path("/policies/{scalingPolicyId}")
-   @Consumes(MediaType.APPLICATION_JSON)
    @MapBinder(BindScalingPolicyToJson.class)
    @Fallback(FalseOnNotFoundOr404.class)
    boolean update(@PathParam("scalingPolicyId") String scalingPolicyId, @PayloadParam("scalingPolicy") ScalingPolicy scalingPolicy);
@@ -117,7 +115,6 @@ public interface PolicyApi {
    @Named("Policy:delete")
    @DELETE
    @Path("/policies/{scalingPolicyId}")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(FalseOnNotFoundOr404.class)
    boolean delete(@PathParam("scalingPolicyId") String scalingPolicyId);
 
@@ -129,7 +126,6 @@ public interface PolicyApi {
    @Named("Policy:execute")
    @POST
    @Path("/policies/{scalingPolicyId}/execute")
-   @Consumes(MediaType.APPLICATION_JSON)
    @Fallback(FalseOnNotFoundOr404.class)
    boolean execute(@PathParam("scalingPolicyId") String scalingPolicyId);
 }

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApi.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApi.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApi.java
new file mode 100644
index 0000000..c699177
--- /dev/null
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApi.java
@@ -0,0 +1,157 @@
+/*
+ * 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.jclouds.rackspace.autoscale.v1.features;
+
+import java.io.Closeable;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptyFluentIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.rackspace.autoscale.v1.binders.BindWebhookToJson;
+import org.jclouds.rackspace.autoscale.v1.binders.BindWebhookUpdateToJson;
+import org.jclouds.rackspace.autoscale.v1.binders.BindWebhooksToJson;
+import org.jclouds.rackspace.autoscale.v1.domain.Group;
+import org.jclouds.rackspace.autoscale.v1.domain.ScalingPolicy;
+import org.jclouds.rackspace.autoscale.v1.domain.Webhook;
+import org.jclouds.rackspace.autoscale.v1.domain.WebhookResponse;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.common.collect.FluentIterable;
+
+/**
+ * The API for controlling autoscale webhooks.
+ * @author Zack Shoylev
+ */
+@RequestFilters(AuthenticateRequest.class)
+@Consumes(MediaType.APPLICATION_JSON)
+public interface WebhookApi extends Closeable {
+   /**
+    * Create a webhook.
+    * @param name The webhook name. Required.
+    * @param metadata A map of associated metadata. Use String keys. Required. 
+    * @return WebhookResponse The webhook created by this call.
+    * @see Webhook
+    * @see WebhookResponse
+    * @see Group
+    * @see ScalingPolicy
+    */
+   @Named("Webhook:create")
+   @POST
+   @Path("/webhooks")
+   @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
+   @MapBinder(BindWebhookToJson.class)
+   @SelectJson("webhooks")
+   FluentIterable<WebhookResponse> create(@PayloadParam("name") String name, @PayloadParam("metadata") Map<String, Object> metadata);
+
+   /**
+    * Create webhooks.
+    * @param webhooks A list of webhooks.
+    * @return WebhookResponse The webhook created by this call.
+    * @see Webhook
+    * @see WebhookResponse
+    * @see Group
+    * @see ScalingPolicy
+    */
+   @Named("Webhook:create")
+   @POST
+   @Path("/webhooks")
+   @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
+   @MapBinder(BindWebhooksToJson.class)
+   @SelectJson("webhooks")
+   FluentIterable<WebhookResponse> create(@PayloadParam("webhooks") List<Webhook> webhooks);
+   
+   /**
+    * List webhooks.
+    * @return A list of webhooks
+    * @see Webhook
+    * @see WebhookResponse
+    * @see Group
+    * @see ScalingPolicy
+    */
+   @Named("Webhook:list")
+   @GET
+   @Path("/webhooks")
+   @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
+   @SelectJson("webhooks")
+   FluentIterable<WebhookResponse> list();
+
+   /**
+    * Get a webhook.
+    * @param String id The id of the webhook.
+    * @return The webhook
+    * @see Webhook
+    * @see WebhookResponse
+    * @see Group
+    * @see ScalingPolicy
+    */
+   @Named("Webhook:get")
+   @GET
+   @Path("/webhooks/{webhookId}")   
+   @Fallback(NullOnNotFoundOr404.class)
+   @SelectJson("webhook")
+   WebhookResponse get(@PathParam("webhookId") String id);
+
+   /**
+    * Update a webhook.
+    * @param id The webhook id
+    * @param name The webhook name
+    * @param metadata A map of associated metadata. Use String keys.
+    * @return true when successful.
+    * @see Webhook
+    * @see WebhookResponse
+    * @see Group
+    * @see ScalingPolicy
+    */
+   @Named("Webhook:update")
+   @PUT
+   @Path("/webhooks/{webhookId}")   
+   @Fallback(FalseOnNotFoundOr404.class)
+   @MapBinder(BindWebhookUpdateToJson.class)
+   boolean update(@PathParam("webhookId") String id, @PayloadParam("name") String name, @PayloadParam("metadata") Map<String, Object> metadata);
+
+   /**
+    * Delete a webhook.
+    * @param String id The id of the webhook.
+    * @return true if successful.
+    * @see Webhook
+    * @see WebhookResponse
+    * @see Group
+    * @see ScalingPolicy
+    */
+   @Named("Webhook:delete")
+   @DELETE
+   @Path("/webhooks/{webhookId}")   
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean delete(@PathParam("webhookId") String id);
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/utils/AutoscaleUtils.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/utils/AutoscaleUtils.java b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/utils/AutoscaleUtils.java
new file mode 100644
index 0000000..b8466fe
--- /dev/null
+++ b/rackspace-autoscale/src/main/java/org/jclouds/rackspace/autoscale/v1/utils/AutoscaleUtils.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.jclouds.rackspace.autoscale.v1.utils;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+
+import org.jclouds.rackspace.autoscale.v1.domain.WebhookResponse;
+
+/**
+ * @author Zack Shoylev
+ * 
+ * This is more of an example code of how to execute the anonymous webhook call without jclouds.
+ * A POST call to the Webhook capability Link executes the scaling policy that webhook belongs to.
+ * Calling the capability Link ensures this is done without authentication and anonymously (the webhook information is hashed).
+ */
+public class AutoscaleUtils {
+   public static boolean execute(URI webhookUri) throws IOException {
+      URL url = webhookUri.toURL();
+      HttpURLConnection connection = (HttpURLConnection) url.openConnection();           
+      connection.setDoOutput(true);
+      connection.setDoInput(true);
+      connection.setInstanceFollowRedirects(false);
+      connection.setRequestMethod("POST");
+      connection.setUseCaches(false);
+      connection.connect();
+      int code = connection.getResponseCode();
+      connection.disconnect();
+      
+      return code == 202;
+   }
+   
+   public static boolean execute(WebhookResponse webhook) throws IOException {
+      return execute(webhook.getAnonymousExecutionURI().get());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiExpectTest.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiExpectTest.java b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiExpectTest.java
index 8e5ad0a..a8ebbda 100644
--- a/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiExpectTest.java
+++ b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiExpectTest.java
@@ -381,7 +381,7 @@ public class GroupApiExpectTest extends BaseAutoscaleApiExpectTest {
             .getGroupApiForZone("DFW");
 
       GroupConfiguration gc = api.getGroupConfiguration("1234567890");
-      assertEquals(gc, null);
+      assertNull(gc);
    }
 
    public void testGetLaunchConfiguration() {
@@ -410,7 +410,7 @@ public class GroupApiExpectTest extends BaseAutoscaleApiExpectTest {
             .getGroupApiForZone("DFW");
 
       LaunchConfiguration lc = api.getLaunchConfiguration("1234567890");
-      assertEquals(lc, null);
+      assertNull(lc);
    }
 
    public void testUpdateGroupConfiguration() {

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiLiveTest.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiLiveTest.java b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiLiveTest.java
index 7f095e1..929e7f6 100644
--- a/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiLiveTest.java
+++ b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/GroupApiLiveTest.java
@@ -52,7 +52,7 @@ import com.google.common.collect.Maps;
  * 
  * @author Zack Shoylev
  */
-@Test(groups = "unit", testName = "GroupApiLiveTest", singleThreaded = true)
+@Test(groups = "live", testName = "GroupApiLiveTest", singleThreaded = true)
 public class GroupApiLiveTest extends BaseAutoscaleApiLiveTest {
 
    private static Map<String, List<Group>> created = Maps.newHashMap();
@@ -76,7 +76,6 @@ public class GroupApiLiveTest extends BaseAutoscaleApiLiveTest {
                .loadBalancers(ImmutableList.of(LoadBalancer.builder().port(8080).id(9099).build()))
                .serverName("autoscale_server")
                .serverImageRef("57b8a366-ab2c-454b-939f-215303a4431f")
-               //.serverImageRef("0d589460-f177-4b0f-81c1-8ab8903ac7d8")
                .serverFlavorRef("2")
                .serverDiskConfig("AUTO")
                .serverMetadata(
@@ -85,13 +84,13 @@ public class GroupApiLiveTest extends BaseAutoscaleApiLiveTest {
                      .networks(
                            ImmutableList.of("11111111-1111-1111-1111-111111111111", "00000000-0000-0000-0000-000000000000"))
                            .personalities(
-                                 ImmutableList.of(Personality.builder().path("/root/.csivh")
+                                 ImmutableList.of(Personality.builder().path("testfile")
                                        .contents("VGhpcyBpcyBhIHRlc3QgZmlsZS4=").build()))
                                        .type(LaunchConfigurationType.LAUNCH_SERVER).build();
 
          List<ScalingPolicy> scalingPolicies = Lists.newArrayList();
 
-         ScalingPolicy scalingPolicy = ScalingPolicy.builder().cooldown(0).type(ScalingPolicyType.WEBHOOK)
+         ScalingPolicy scalingPolicy = ScalingPolicy.builder().cooldown(1).type(ScalingPolicyType.WEBHOOK)
                .name("scale up by 1").targetType(ScalingPolicyTargetType.INCREMENTAL).target("1").build();
          scalingPolicies.add(scalingPolicy);
 
@@ -111,7 +110,7 @@ public class GroupApiLiveTest extends BaseAutoscaleApiLiveTest {
                g.getScalingPolicies().get(0).getLinks().get(0).getHref().toString(),
                "https://" + zone.toLowerCase() + ".autoscale.api.rackspacecloud.com/v1.0/" + api.getCurrentTenantId().get().getId() + "/groups/" + g.getId() + "/policies/" + g.getScalingPolicies().get(0).getId() +"/");
          assertEquals(g.getScalingPolicies().get(0).getLinks().get(0).getRelation(), Link.Relation.SELF);
-         assertEquals(g.getScalingPolicies().get(0).getCooldown(), 0);
+         assertEquals(g.getScalingPolicies().get(0).getCooldown(), 1);
          assertEquals(g.getScalingPolicies().get(0).getTarget(), "1");
          assertEquals(g.getScalingPolicies().get(0).getTargetType(), ScalingPolicyTargetType.INCREMENTAL);
          assertEquals(g.getScalingPolicies().get(0).getType(), ScalingPolicyType.WEBHOOK);
@@ -125,7 +124,7 @@ public class GroupApiLiveTest extends BaseAutoscaleApiLiveTest {
          assertEquals(g.getLaunchConfiguration().getServerFlavorRef(), "2");
          assertEquals(g.getLaunchConfiguration().getServerDiskConfig(), "AUTO");
          assertEquals(g.getLaunchConfiguration().getPersonalities().size(), 1);
-         assertEquals(g.getLaunchConfiguration().getPersonalities().get(0).getPath(), "/root/.csivh");
+         assertEquals(g.getLaunchConfiguration().getPersonalities().get(0).getPath(), "testfile");
          assertEquals(g.getLaunchConfiguration().getPersonalities().get(0).getContents(),
                "VGhpcyBpcyBhIHRlc3QgZmlsZS4=");
          assertEquals(g.getLaunchConfiguration().getNetworks().size(), 2);
@@ -247,7 +246,7 @@ public class GroupApiLiveTest extends BaseAutoscaleApiLiveTest {
                      .networks(
                            ImmutableList.of("11111111-1111-1111-1111-111111111111", "00000000-0000-0000-0000-000000000000"))
                            .personalities(
-                                 ImmutableList.of(Personality.builder().path("/root/.csivh")
+                                 ImmutableList.of(Personality.builder().path("testfile2")
                                        .contents("VGhpcyBpcyBhIHRlc3QgZmlsZS4=").build()))
                                        .type(LaunchConfigurationType.LAUNCH_SERVER).build();
 

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/ScalingPolicyApiLiveTest.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/ScalingPolicyApiLiveTest.java b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/ScalingPolicyApiLiveTest.java
index ab2e257..4fa7465 100644
--- a/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/ScalingPolicyApiLiveTest.java
+++ b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/ScalingPolicyApiLiveTest.java
@@ -52,7 +52,7 @@ import com.google.common.util.concurrent.Uninterruptibles;
  * 
  * @author Zack Shoylev
  */
-@Test(groups = "unit", testName = "ScalingPolicyApiLiveTest", singleThreaded = true)
+@Test(groups = "live", testName = "ScalingPolicyApiLiveTest", singleThreaded = true)
 public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
 
    private static Map<String, List<Group>> created = Maps.newHashMap();
@@ -66,7 +66,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
          created.put(zone, createdGroupList);
          GroupApi groupApi = api.getGroupApiForZone(zone);
 
-         GroupConfiguration groupConfiguration = GroupConfiguration.builder().maxEntities(10).cooldown(360)
+         GroupConfiguration groupConfiguration = GroupConfiguration.builder().maxEntities(10).cooldown(3)
                .name("testscalinggroup198547").minEntities(0)
                .metadata(ImmutableMap.of("gc_meta_key_2", "gc_meta_value_2", "gc_meta_key_1", "gc_meta_value_1"))
                .build();
@@ -84,13 +84,13 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
                      .networks(
                            ImmutableList.of("11111111-1111-1111-1111-111111111111", "00000000-0000-0000-0000-000000000000"))
                            .personalities(
-                                 ImmutableList.of(Personality.builder().path("/root/.csivh")
+                                 ImmutableList.of(Personality.builder().path("testfile")
                                        .contents("VGhpcyBpcyBhIHRlc3QgZmlsZS4=").build()))
                                        .type(LaunchConfigurationType.LAUNCH_SERVER).build();
 
          List<ScalingPolicy> scalingPolicies = Lists.newArrayList();
 
-         ScalingPolicy scalingPolicy = ScalingPolicy.builder().cooldown(0).type(ScalingPolicyType.WEBHOOK)
+         ScalingPolicy scalingPolicy = ScalingPolicy.builder().cooldown(3).type(ScalingPolicyType.WEBHOOK)
                .name("scale up by 1").targetType(ScalingPolicyTargetType.INCREMENTAL).target("1").build();
          scalingPolicies.add(scalingPolicy);
 
@@ -110,7 +110,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
                g.getScalingPolicies().get(0).getLinks().get(0).getHref().toString(),
                "https://" + zone.toLowerCase() + ".autoscale.api.rackspacecloud.com/v1.0/" + api.getCurrentTenantId().get().getId() + "/groups/" + g.getId() + "/policies/" + g.getScalingPolicies().get(0).getId() +"/");
          assertEquals(g.getScalingPolicies().get(0).getLinks().get(0).getRelation(), Link.Relation.SELF);
-         assertEquals(g.getScalingPolicies().get(0).getCooldown(), 0);
+         assertEquals(g.getScalingPolicies().get(0).getCooldown(), 3);
          assertEquals(g.getScalingPolicies().get(0).getTarget(), "1");
          assertEquals(g.getScalingPolicies().get(0).getTargetType(), ScalingPolicyTargetType.INCREMENTAL);
          assertEquals(g.getScalingPolicies().get(0).getType(), ScalingPolicyType.WEBHOOK);
@@ -124,7 +124,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
          assertEquals(g.getLaunchConfiguration().getServerFlavorRef(), "2");
          assertEquals(g.getLaunchConfiguration().getServerDiskConfig(), "AUTO");
          assertEquals(g.getLaunchConfiguration().getPersonalities().size(), 1);
-         assertEquals(g.getLaunchConfiguration().getPersonalities().get(0).getPath(), "/root/.csivh");
+         assertEquals(g.getLaunchConfiguration().getPersonalities().get(0).getPath(), "testfile");
          assertEquals(g.getLaunchConfiguration().getPersonalities().get(0).getContents(),
                "VGhpcyBpcyBhIHRlc3QgZmlsZS4=");
          assertEquals(g.getLaunchConfiguration().getNetworks().size(), 2);
@@ -136,7 +136,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
          assertEquals(g.getLaunchConfiguration().getType(), LaunchConfigurationType.LAUNCH_SERVER);
 
          assertEquals(g.getGroupConfiguration().getMaxEntities(), 10);
-         assertEquals(g.getGroupConfiguration().getCooldown(), 360);
+         assertEquals(g.getGroupConfiguration().getCooldown(), 3);
          assertEquals(g.getGroupConfiguration().getName(), "testscalinggroup198547");
          assertEquals(g.getGroupConfiguration().getMinEntities(), 0);
          assertEquals(g.getGroupConfiguration().getMetadata().size(), 2);
@@ -154,7 +154,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
          List<ScalingPolicy> scalingPolicies = Lists.newArrayList();
 
          ScalingPolicy scalingPolicy = ScalingPolicy.builder()
-               .cooldown(1800)
+               .cooldown(3)
                .type(ScalingPolicyType.WEBHOOK)
                .name("scale up by one server")
                .targetType(ScalingPolicyTargetType.INCREMENTAL)
@@ -164,7 +164,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
 
          FluentIterable<ScalingPolicyResponse> scalingPolicyResponse = policyApi.create(scalingPolicies);
          assertNotNull(scalingPolicyResponse.iterator().next().getId());
-         assertEquals(scalingPolicyResponse.iterator().next().getCooldown(), 1800);
+         assertEquals(scalingPolicyResponse.iterator().next().getCooldown(), 3);
          assertEquals(scalingPolicyResponse.iterator().next().getTarget(), "1");
       }
    }
@@ -208,7 +208,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
          List<ScalingPolicy> scalingPolicies = Lists.newArrayList();
 
          ScalingPolicy scalingPolicy = ScalingPolicy.builder()
-               .cooldown(1800)
+               .cooldown(3)
                .type(ScalingPolicyType.WEBHOOK)
                .name("scale up by one server")
                .targetType(ScalingPolicyTargetType.INCREMENTAL)
@@ -217,7 +217,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
          scalingPolicies.add(scalingPolicy);
 
          ScalingPolicy updated = ScalingPolicy.builder()
-               .cooldown(2000)
+               .cooldown(3)
                .type(ScalingPolicyType.WEBHOOK)
                .name("scale up by 2 PERCENT server")
                .targetType(ScalingPolicyTargetType.PERCENT_CHANGE)
@@ -235,7 +235,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
          ScalingPolicyResponse updatedResponse = policyApi.get(policyId);
 
          assertNotNull(updatedResponse.getId());
-         assertEquals(updatedResponse.getCooldown(), 2000);
+         assertEquals(updatedResponse.getCooldown(), 3);
          assertEquals(updatedResponse.getTarget(), "2");
          assertEquals(updatedResponse.getTargetType(), ScalingPolicyTargetType.PERCENT_CHANGE);
          assertEquals(updatedResponse.getType(), ScalingPolicyType.WEBHOOK);
@@ -252,7 +252,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
          List<ScalingPolicy> scalingPolicies = Lists.newArrayList();
 
          ScalingPolicy scalingPolicy = ScalingPolicy.builder()
-               .cooldown(1800)
+               .cooldown(3)
                .type(ScalingPolicyType.WEBHOOK)
                .name("scale up by one server")
                .targetType(ScalingPolicyTargetType.INCREMENTAL)
@@ -278,7 +278,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
          List<ScalingPolicy> scalingPolicies = Lists.newArrayList();
 
          ScalingPolicy scalingPolicy = ScalingPolicy.builder()
-               .cooldown(1800)
+               .cooldown(3)
                .type(ScalingPolicyType.WEBHOOK)
                .name("scale up by 0 server")
                .targetType(ScalingPolicyTargetType.INCREMENTAL)
@@ -312,7 +312,7 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
             List<ScalingPolicy> scalingPolicies = Lists.newArrayList();
 
             ScalingPolicy scalingPolicy = ScalingPolicy.builder()
-                  .cooldown(1)
+                  .cooldown(3)
                   .type(ScalingPolicyType.WEBHOOK)
                   .name("0 machines")
                   .targetType(ScalingPolicyTargetType.DESIRED_CAPACITY)
@@ -321,13 +321,13 @@ public class ScalingPolicyApiLiveTest extends BaseAutoscaleApiLiveTest {
             scalingPolicies.add(scalingPolicy);
 
             FluentIterable<ScalingPolicyResponse> scalingPolicyResponse = policyApi.create(scalingPolicies);
-            String policyId = scalingPolicyResponse.iterator().next().getId();
+            String policyId = scalingPolicyResponse.first().get().getId();
 
-            Uninterruptibles.sleepUninterruptibly(3, TimeUnit.SECONDS);
+            Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
 
             policyApi.execute(policyId);
 
-            Uninterruptibles.sleepUninterruptibly(3, TimeUnit.SECONDS);
+            Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
 
             if (!groupApi.delete(group.getId()))
                System.out.println("Could not delete an autoscale group after tests!");

http://git-wip-us.apache.org/repos/asf/jclouds-labs-openstack/blob/e052acb2/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApiExpectTest.java
----------------------------------------------------------------------
diff --git a/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApiExpectTest.java b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApiExpectTest.java
new file mode 100644
index 0000000..41555db
--- /dev/null
+++ b/rackspace-autoscale/src/test/java/org/jclouds/rackspace/autoscale/v1/features/WebhookApiExpectTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.jclouds.rackspace.autoscale.v1.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.http.HttpResponse;
+import org.jclouds.rackspace.autoscale.v1.domain.Webhook;
+import org.jclouds.rackspace.autoscale.v1.domain.WebhookResponse;
+import org.jclouds.rackspace.autoscale.v1.internal.BaseAutoscaleApiExpectTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Tests WebhookApi Guice wiring and parsing
+ *
+ * @author Zack Shoylev
+ */
+@Test(groups = "unit", testName = "WebhookApiExpectTest")
+public class WebhookApiExpectTest extends BaseAutoscaleApiExpectTest {
+
+   public void testCreateWebhook() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().method("POST").endpoint(endpoint).payload(payloadFromResourceWithContentType("/autoscale_webhook_create_request.json", MediaType.APPLICATION_JSON)).build(),
+            HttpResponse.builder().statusCode(201).payload(payloadFromResource("/autoscale_webhook_create_response.json")).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      FluentIterable<WebhookResponse> webhooks = api.create("PagerDuty", ImmutableMap.<String, Object>of("notes", "PagerDuty will fire this webhook"));
+      assertEquals(webhooks.first().get().getName(), "PagerDuty");
+      assertEquals(webhooks.first().get().getMetadata().get("notes"), "PagerDuty will fire this webhook");
+   }
+
+   public void testCreateWebhookFail() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().method("POST").endpoint(endpoint).payload(payloadFromResourceWithContentType("/autoscale_webhook_create_request.json", MediaType.APPLICATION_JSON)).build(),
+            HttpResponse.builder().statusCode(404).payload(payloadFromResource("/autoscale_webhook_create_response.json")).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      FluentIterable<WebhookResponse> webhooks = api.create("PagerDuty", ImmutableMap.<String, Object>of("notes", "PagerDuty will fire this webhook"));
+      assertTrue(webhooks.isEmpty());
+   }
+
+   public void testCreateWebhooks() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().method("POST").endpoint(endpoint).payload(payloadFromResourceWithContentType("/autoscale_webhooks_create_request.json", MediaType.APPLICATION_JSON)).build(),
+            HttpResponse.builder().statusCode(201).payload(payloadFromResource("/autoscale_webhooks_create_response.json")).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      FluentIterable<WebhookResponse> webhooks = api.create(ImmutableList.of(
+               Webhook.builder().name("PagerDuty").metadata(ImmutableMap.<String, Object>of("notes", "PagerDuty will fire this webhook")).build(),
+               Webhook.builder().name("Nagios").metadata(ImmutableMap.<String, Object>of()).build()
+            ));
+      assertEquals(webhooks.size(), 2);
+      assertEquals(webhooks.first().get().getName(), "PagerDuty");
+      assertEquals(webhooks.first().get().getMetadata().get("notes"), "PagerDuty will fire this webhook");
+   }
+
+   public void testCreateWebhooksFail() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().method("POST").endpoint(endpoint).payload(payloadFromResourceWithContentType("/autoscale_webhooks_create_request.json", MediaType.APPLICATION_JSON)).build(),
+            HttpResponse.builder().statusCode(404).payload(payloadFromResource("/autoscale_webhooks_create_response.json")).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      FluentIterable<WebhookResponse> webhooks = api.create(ImmutableList.of(
+               Webhook.builder().name("PagerDuty").metadata(ImmutableMap.<String, Object>of("notes", "PagerDuty will fire this webhook")).build(),
+               Webhook.builder().name("Nagios").metadata(ImmutableMap.<String, Object>of()).build()
+            ));
+      assertTrue(webhooks.isEmpty());
+   }
+
+   public void testListWebhooks() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().method("GET").endpoint(endpoint).build(),
+            HttpResponse.builder().statusCode(201).payload(payloadFromResource("/autoscale_webhook_list_response.json")).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      FluentIterable<WebhookResponse> webhooks = api.list();
+      assertEquals(webhooks.size(), 2);
+      assertEquals(webhooks.first().get().getName(), "PagerDuty");
+      assertEquals(webhooks.first().get().getMetadata().get("notes"), "PagerDuty will fire this webhook");
+   }
+
+   public void testListWebhooksFail() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().method("GET").endpoint(endpoint).build(),
+            HttpResponse.builder().statusCode(404).payload(payloadFromResource("/autoscale_webhook_list_response.json")).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      FluentIterable<WebhookResponse> webhooks = api.list();
+      assertEquals(webhooks.size(), 0);
+   }
+
+   public void testUpdateWebhook() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks/5555");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().method("PUT").endpoint(endpoint).payload(payloadFromResourceWithContentType("/autoscale_webhook_update_request.json", MediaType.APPLICATION_JSON)).build(),
+            HttpResponse.builder().statusCode(201).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      boolean success = api.update("5555", "alice", ImmutableMap.<String, Object>of("notes", "this is for Alice"));
+      assertTrue(success);
+   }
+
+   public void testUpdateWebhookFail() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks/5555");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().method("PUT").endpoint(endpoint).payload(payloadFromResourceWithContentType("/autoscale_webhook_update_request.json", MediaType.APPLICATION_JSON)).build(),
+            HttpResponse.builder().statusCode(404).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      boolean success = api.update("5555", "alice", ImmutableMap.<String, Object>of("notes", "this is for Alice"));
+      assertFalse(success);
+   }
+
+   public void testGetWebhook() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks/5555");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().endpoint(endpoint).build(),
+            HttpResponse.builder().statusCode(201).payload(payloadFromResource("/autoscale_webhook_get_response.json")).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      WebhookResponse webhook = api.get("5555");
+      assertEquals(webhook.getName(), "alice");
+      assertEquals(webhook.getLinks().size(), 2);
+   }
+
+   public void testGetWebhookFail() {
+      URI endpoint = URI.create("https://dfw.autoscale.api.rackspacecloud.com/v1.0/888888/groups/1234567890/policies/321456/webhooks/5555");
+      WebhookApi api = requestsSendResponses(
+            keystoneAuthWithUsernameAndPasswordAndTenantName,
+            responseWithKeystoneAccess,
+            authenticatedGET().endpoint(endpoint).build(),
+            HttpResponse.builder().statusCode(404).payload(payloadFromResource("/autoscale_webhook_get_response.json")).build()
+            ).getWebhookApiForGroupAndPolicyInZone("1234567890", "321456", "DFW");
+
+      WebhookResponse webhook = api.get("5555");
+      assertNull(webhook);
+   }
+}