You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ab...@apache.org on 2013/06/26 23:59:08 UTC

[2/4] JCLOUDS-101 - Add SecurityGroupExtension for compute

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseComputeServiceTypicalSecurityGroupTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseComputeServiceTypicalSecurityGroupTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseComputeServiceTypicalSecurityGroupTest.java
index 5da793f..9b056b9 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseComputeServiceTypicalSecurityGroupTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseComputeServiceTypicalSecurityGroupTest.java
@@ -23,8 +23,8 @@ import javax.ws.rs.core.MediaType;
 
 import org.jclouds.json.BaseItemParserTest;
 import org.jclouds.json.config.GsonModule;
+import org.jclouds.net.domain.IpProtocol;
 import org.jclouds.openstack.nova.v2_0.config.NovaParserModule;
-import org.jclouds.openstack.nova.v2_0.domain.IpProtocol;
 import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
 import org.jclouds.openstack.nova.v2_0.domain.SecurityGroupRule;
 import org.jclouds.openstack.nova.v2_0.domain.TenantIdAndName;

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupListTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupListTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupListTest.java
index 9f477ee..4e46ede 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupListTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupListTest.java
@@ -23,8 +23,8 @@ import javax.ws.rs.core.MediaType;
 
 import org.jclouds.json.BaseSetParserTest;
 import org.jclouds.json.config.GsonModule;
+import org.jclouds.net.domain.IpProtocol;
 import org.jclouds.openstack.nova.v2_0.config.NovaParserModule;
-import org.jclouds.openstack.nova.v2_0.domain.IpProtocol;
 import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
 import org.jclouds.openstack.nova.v2_0.domain.SecurityGroupRule;
 import org.jclouds.rest.annotations.SelectJson;

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupTest.java
index a5c83f4..784a0bd 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseSecurityGroupTest.java
@@ -23,8 +23,8 @@ import javax.ws.rs.core.MediaType;
 
 import org.jclouds.json.BaseItemParserTest;
 import org.jclouds.json.config.GsonModule;
+import org.jclouds.net.domain.IpProtocol;
 import org.jclouds.openstack.nova.v2_0.config.NovaParserModule;
-import org.jclouds.openstack.nova.v2_0.domain.IpProtocol;
 import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
 import org.jclouds.openstack.nova.v2_0.domain.SecurityGroupRule;
 import org.jclouds.openstack.nova.v2_0.domain.TenantIdAndName;

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/compute/TerremarkVCloudComputeService.java
----------------------------------------------------------------------
diff --git a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/compute/TerremarkVCloudComputeService.java b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/compute/TerremarkVCloudComputeService.java
index 1431d54..b37e85e 100644
--- a/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/compute/TerremarkVCloudComputeService.java
+++ b/common/trmk/src/main/java/org/jclouds/trmk/vcloud_0_8/compute/TerremarkVCloudComputeService.java
@@ -37,6 +37,7 @@ import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.internal.BaseComputeService;
 import org.jclouds.compute.internal.PersistNodeCredentials;
 import org.jclouds.compute.options.TemplateOptions;
@@ -84,12 +85,12 @@ public class TerremarkVCloudComputeService extends BaseComputeService {
             RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
             PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
             @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, CleanupOrphanKeys cleanupOrphanKeys,
-            Optional<ImageExtension> imageExtension) {
+            Optional<ImageExtension> imageExtension, Optional<SecurityGroupExtension> securityGroupExtension) {
       super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy,
                getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
                resumeNodeStrategy, suspendNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
                nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
-               persistNodeCredentials, timeouts, userExecutor, imageExtension);
+               persistNodeCredentials, timeouts, userExecutor, imageExtension, securityGroupExtension);
       this.cleanupOrphanKeys = cleanupOrphanKeys;
    }
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/ComputeService.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/ComputeService.java b/compute/src/main/java/org/jclouds/compute/ComputeService.java
index 610090a..a7c2519 100644
--- a/compute/src/main/java/org/jclouds/compute/ComputeService.java
+++ b/compute/src/main/java/org/jclouds/compute/ComputeService.java
@@ -27,6 +27,7 @@ import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.internal.BaseComputeService;
 import org.jclouds.compute.options.RunScriptOptions;
 import org.jclouds.compute.options.TemplateOptions;
@@ -383,5 +384,13 @@ public interface ComputeService {
    @Beta
    Optional<ImageExtension> getImageExtension();
 
+   /**
+    * Returns the {@link SecurityGroupExtension} for this provider if it implements it.
+    * 
+    * @return an optional of the {@link SecurityGroupExtension} or {@link Optional#absent()} if not
+    *         implemented
+    */
+   @Beta
+   Optional<SecurityGroupExtension> getSecurityGroupExtension();
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java b/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java
index 7581e86..7307196 100644
--- a/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java
+++ b/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java
@@ -44,6 +44,7 @@ import org.jclouds.compute.domain.OsFamily;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.functions.CreateSshClientOncePortIsListeningOnNode;
 import org.jclouds.compute.functions.DefaultCredentialsFromImageOrOverridingCredentials;
 import org.jclouds.compute.functions.TemplateOptionsToStatement;
@@ -306,5 +307,11 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
       return Optional.absent();
    }
    
+   @Provides
+   @Singleton
+   protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i){
+      return Optional.absent();
+   }
+   
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/domain/ComputeType.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/domain/ComputeType.java b/compute/src/main/java/org/jclouds/compute/domain/ComputeType.java
index 26df759..8e2c75c 100644
--- a/compute/src/main/java/org/jclouds/compute/domain/ComputeType.java
+++ b/compute/src/main/java/org/jclouds/compute/domain/ComputeType.java
@@ -22,6 +22,6 @@ package org.jclouds.compute.domain;
  */
 public enum ComputeType {
 
-   NODE, IMAGE, HARDWARE;
+   NODE, IMAGE, HARDWARE, SECURITYGROUP;
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/domain/SecurityGroup.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/domain/SecurityGroup.java b/compute/src/main/java/org/jclouds/compute/domain/SecurityGroup.java
new file mode 100644
index 0000000..fecba5a
--- /dev/null
+++ b/compute/src/main/java/org/jclouds/compute/domain/SecurityGroup.java
@@ -0,0 +1,80 @@
+/*
+ * 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.compute.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.compute.domain.ComputeType;
+import org.jclouds.compute.domain.internal.ComputeMetadataImpl;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.ResourceMetadata;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.net.domain.IpPermission;
+
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Describes a security group containing a set of @{link IpPermission}s
+ * 
+ * @author Andrew Bayer
+ */
+public class SecurityGroup extends ComputeMetadataImpl {
+
+   private final Set<IpPermission> ipPermissions;
+   private final String ownerId;
+   
+   public SecurityGroup(String providerId, String name, String id, @Nullable Location location, URI uri,
+                        Map<String, String> userMetadata, Set<String> tags,
+                        Iterable<IpPermission> ipPermissions,
+                        @Nullable String ownerId) { 
+      super(ComputeType.SECURITYGROUP, providerId, name, id, location, uri, userMetadata, tags);
+      this.ipPermissions = ImmutableSet.copyOf(checkNotNull(ipPermissions, "ipPermissions"));
+      this.ownerId = ownerId;
+   }
+
+   /**
+    * 
+    * @return The set of @{link IpPermission}s for this security group
+    */
+   public Set<IpPermission> getIpPermissions() {
+      return ipPermissions;
+   }
+
+   /**
+    *
+    * @return the owner ID. Can be null.
+    */
+   public String getOwnerId() {
+      return ownerId;
+   }
+   
+   @Override
+   protected ToStringHelper string() {
+      ToStringHelper helper = computeToStringPrefix();
+      if (ipPermissions.size() > 0)
+         helper.add("ipPermissions", ipPermissions);
+      return addComputeToStringSuffix(helper);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/domain/SecurityGroupBuilder.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/domain/SecurityGroupBuilder.java b/compute/src/main/java/org/jclouds/compute/domain/SecurityGroupBuilder.java
new file mode 100644
index 0000000..002116f
--- /dev/null
+++ b/compute/src/main/java/org/jclouds/compute/domain/SecurityGroupBuilder.java
@@ -0,0 +1,122 @@
+/*
+ * 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.compute.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LoginCredentials;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.net.domain.IpPermission;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+/**
+ * @author Andrew Bayer
+ */
+public class SecurityGroupBuilder extends ComputeMetadataBuilder {
+   private ImmutableSet.Builder<IpPermission> ipPermissions = ImmutableSet.<IpPermission> builder();
+   private String ownerId;
+   
+   public SecurityGroupBuilder() {
+      super(ComputeType.SECURITYGROUP);
+   }
+
+   public SecurityGroupBuilder ipPermissions() {
+      this.ipPermissions = ImmutableSet.<IpPermission> builder();
+      return this;
+   }
+
+   public SecurityGroupBuilder ipPermissions(Iterable<IpPermission> ipPermissions) {
+      this.ipPermissions.addAll(checkNotNull(ipPermissions, "ipPermissions"));
+      return this;
+   }
+
+   public SecurityGroupBuilder ipPermission(IpPermission ipPermission) {
+      this.ipPermissions.add(checkNotNull(ipPermission, "ipPermission"));
+      return this;
+   }
+
+   public SecurityGroupBuilder ownerId(String ownerId) {
+      this.ownerId = ownerId;
+      return this;
+   }
+
+   @Override
+   public SecurityGroupBuilder id(String id) {
+      return SecurityGroupBuilder.class.cast(super.id(id));
+   }
+
+   @Override
+   public SecurityGroupBuilder tags(Iterable<String> tags) {
+      return SecurityGroupBuilder.class.cast(super.tags(tags));
+   }
+
+   @Override
+   public SecurityGroupBuilder ids(String id) {
+      return SecurityGroupBuilder.class.cast(super.ids(id));
+   }
+
+   @Override
+   public SecurityGroupBuilder providerId(String providerId) {
+      return SecurityGroupBuilder.class.cast(super.providerId(providerId));
+   }
+
+   @Override
+   public SecurityGroupBuilder name(String name) {
+      return SecurityGroupBuilder.class.cast(super.name(name));
+   }
+
+   @Override
+   public SecurityGroupBuilder location(Location location) {
+      return SecurityGroupBuilder.class.cast(super.location(location));
+   }
+
+   @Override
+   public SecurityGroupBuilder uri(URI uri) {
+      return SecurityGroupBuilder.class.cast(super.uri(uri));
+   }
+
+   @Override
+   public SecurityGroupBuilder userMetadata(Map<String, String> userMetadata) {
+      return SecurityGroupBuilder.class.cast(super.userMetadata(userMetadata));
+   }
+
+   @Override
+   public SecurityGroup build() {
+      return new SecurityGroup(providerId, name, id, location, uri, userMetadata, tags,
+                               ipPermissions.build(), ownerId);
+   }
+
+   public static SecurityGroupBuilder fromSecurityGroup(SecurityGroup group) {
+      return new SecurityGroupBuilder().providerId(group.getProviderId())
+         .name(group.getName())
+         .id(group.getId())
+         .location(group.getLocation())
+         .uri(group.getUri())
+         .userMetadata(group.getUserMetadata())
+         .tags(group.getTags())
+         .ipPermissions(group.getIpPermissions())
+         .ownerId(group.getOwnerId());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/extensions/SecurityGroupExtension.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/extensions/SecurityGroupExtension.java b/compute/src/main/java/org/jclouds/compute/extensions/SecurityGroupExtension.java
new file mode 100644
index 0000000..f07e72d
--- /dev/null
+++ b/compute/src/main/java/org/jclouds/compute/extensions/SecurityGroupExtension.java
@@ -0,0 +1,180 @@
+/*
+ * 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.compute.extensions;
+
+import java.util.Set;
+
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.domain.Location;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * An extension to compute service to allow for the manipulation of {@link SecurityGroup}s. Implementation
+ * is optional by providers.
+ * 
+ * @author Andrew Bayer
+ */
+public interface SecurityGroupExtension {
+
+   /**
+    * List security groups.
+    *
+    * @return The set of @{link SecurityGroup}s we have access to.
+    */
+   Set<SecurityGroup> listSecurityGroups();
+
+   /**
+    * List security groups in a given @{link Location}.
+    *
+    * @return The set of @{link SecurityGroup}s we have access to in the given location.
+    */
+   Set<SecurityGroup> listSecurityGroupsInLocation(Location location);
+   
+   /**
+    * List security groups for a given instance given the instance's ID.
+    *
+    * @return The set of @{link SecurityGroup}s for the given instance..
+    */
+   Set<SecurityGroup> listSecurityGroupsForNode(String id);
+
+   /**
+    * Get a security group by id.
+    *
+    * @return The @{link SecurityGroup}, if it exists.
+    */
+   SecurityGroup getSecurityGroupById(String id);
+
+   /**
+    * Create a new @{link SecurityGroup} from the parameters given.
+    *
+    * @param name
+    *           The name of the security group
+    * @param location
+    *           The @{link Location} of the security group
+    *
+    * @return The SecurityGroup that has been created.
+    */
+   SecurityGroup createSecurityGroup(String name, Location location);
+
+   /**
+    * Remove an existing @{link SecurityGroup}, and its permissions.
+    *
+    * @param id
+    *           The id of the SecurityGroup to delete.
+    *
+    * @return true if we were able to remove the group, false otherwise.
+    */
+   boolean removeSecurityGroup(String id);
+
+   /**
+    * Add a @{link IpPermission} to an existing @{link SecurityGroup}. Applies the permission to the
+    *   security group on the provider.
+    *
+    * @param rule
+    *           The IpPermission to add.
+    * @param group
+    *           The SecurityGroup to add the permission to.
+    *
+    * @return The SecurityGroup with the new permission added, after the permission has been applied on the provider.
+    */
+   SecurityGroup addIpPermission(IpPermission ipPermission, SecurityGroup group);
+
+   /**
+    * Remove a @{link IpPermission} from an existing @{link SecurityGroup}. Removes the permission from the
+    *   security group on the provider.
+    *
+    * @param rule
+    *           The IpPermission to remove.
+    * @param group
+    *           The SecurityGroup to remove the permission from.
+    *
+    * @return The SecurityGroup with the permission removed, after the permission has been removed on the provider.
+    */
+   SecurityGroup removeIpPermission(IpPermission ipPermission, SecurityGroup group);
+
+   /**
+    * Add a @{link IpPermission} to an existing @{link SecurityGroup}, based on the parameters given.
+    *   Applies the permission to the security group on the provider.
+    *
+    * @param protocol
+    *           The @{link IpProtocol} for the permission.
+    * @param startPort
+    *           The first port in the range to be opened, or -1 for ICMP.
+    * @param endPort
+    *           The last port in the range to be opened, or -1 for ICMP.
+    * @param tenantIdGroupNamePairs
+    *           source of traffic allowed is on basis of another group in a tenant, as opposed to by cidr
+    * @param ipRanges
+    *           An Iterable of Strings representing the IP range(s) the permission should allow.
+    * @param groupIds
+    *           An Iterable of @{link SecurityGroup} IDs this permission should allow.
+    * @param group
+    *           The SecurityGroup to add the permission to.
+    *
+    * @return The SecurityGroup with the new permission added, after the permission has been applied on the provider.
+    */
+   SecurityGroup addIpPermission(IpProtocol protocol, int startPort, int endPort,
+                                 Multimap<String, String> tenantIdGroupNamePairs,
+                                 Iterable<String> ipRanges,
+                                 Iterable<String> groupIds, SecurityGroup group);
+
+   /**
+    * Remove a @{link IpPermission} from an existing @{link SecurityGroup}, based on the parameters given.
+    *   Removes the permission from the security group on the provider.
+    *
+    * @param protocol
+    *           The @{link IpProtocol} for the permission.
+    * @param startPort
+    *           The first port in the range to be opened, or -1 for ICMP.
+    * @param endPort
+    *           The last port in the range to be opened, or -1 for ICMP.
+    * @param tenantIdGroupNamePairs
+    *           source of traffic allowed is on basis of another group in a tenant, as opposed to by cidr
+    * @param ipRanges
+    *           An Iterable of Strings representing the IP range(s) the permission should allow.
+    * @param groupIds
+    *           An Iterable of @{link SecurityGroup} IDs this permission should allow.
+    * @param group
+    *           The SecurityGroup to remove the permission from.
+    *
+    * @return The SecurityGroup with the permission removed, after the permission has been removed from the provider.
+    */
+   SecurityGroup removeIpPermission(IpProtocol protocol, int startPort, int endPort,
+                                    Multimap<String, String> tenantIdGroupNamePairs,
+                                    Iterable<String> ipRanges,
+                                    Iterable<String> groupIds, SecurityGroup group);
+
+   /**
+    * Returns true if this SecurityGroupExtension supports tenant ID + group name pairs.
+    */
+   boolean supportsTenantIdGroupNamePairs();
+
+   /**
+    * Returns true if this SecurityGroupExtension supports group IDs.
+    */
+   boolean supportsGroupIds();
+
+   /**
+    * Returns true if this SecurityGroupExtension supports port ranges for group authorization.
+    */
+   boolean supportsPortRangesForGroups();
+   
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java b/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java
index fd177ac..75ea787 100644
--- a/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java
+++ b/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java
@@ -66,6 +66,7 @@ import org.jclouds.compute.domain.NodeMetadataBuilder;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.options.RunScriptOptions;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.compute.reference.ComputeServiceConstants;
@@ -141,6 +142,7 @@ public class BaseComputeService implements ComputeService {
    private final RunScriptOnNode.Factory runScriptOnNodeFactory;
    private final ListeningExecutorService userExecutor;
    private final Optional<ImageExtension> imageExtension;
+   private final Optional<SecurityGroupExtension> securityGroupExtension;
 
    @Inject
    protected BaseComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
@@ -158,7 +160,7 @@ public class BaseComputeService implements ComputeService {
             InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, InitAdminAccess initAdminAccess,
             RunScriptOnNode.Factory runScriptOnNodeFactory, PersistNodeCredentials persistNodeCredentials,
             Timeouts timeouts, @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
-            Optional<ImageExtension> imageExtension) {
+            Optional<ImageExtension> imageExtension, Optional<SecurityGroupExtension> securityGroupExtension) {
       this.context = checkNotNull(context, "context");
       this.credentialStore = checkNotNull(credentialStore, "credentialStore");
       this.images = checkNotNull(images, "images");
@@ -184,6 +186,7 @@ public class BaseComputeService implements ComputeService {
       this.persistNodeCredentials = checkNotNull(persistNodeCredentials, "persistNodeCredentials");
       this.userExecutor = checkNotNull(userExecutor, "userExecutor");
       this.imageExtension = checkNotNull(imageExtension, "imageExtension");
+      this.securityGroupExtension = checkNotNull(securityGroupExtension, "securityGroupExtension");
    }
 
    /**
@@ -714,4 +717,11 @@ public class BaseComputeService implements ComputeService {
       return imageExtension;
    }
 
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Optional<SecurityGroupExtension> getSecurityGroupExtension() {
+      return securityGroupExtension;
+   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/options/TemplateOptions.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/options/TemplateOptions.java b/compute/src/main/java/org/jclouds/compute/options/TemplateOptions.java
index 270633a..d17a6cd 100644
--- a/compute/src/main/java/org/jclouds/compute/options/TemplateOptions.java
+++ b/compute/src/main/java/org/jclouds/compute/options/TemplateOptions.java
@@ -70,6 +70,8 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable {
          to.inboundPorts(this.getInboundPorts());
       if (this.getRunScript() != null)
          to.runScript(this.getRunScript());
+      if (this.getGroups().size() > 0)
+         to.securityGroups(this.getGroups());
       if (this.getPrivateKey() != null)
          to.installPrivateKey(this.getPrivateKey());
       if (this.getPublicKey() != null)
@@ -295,6 +297,21 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable {
       }
 
       @Override
+      public Set<String> getGroups() {
+         return delegate.getGroups();
+      }
+
+      @Override
+      public TemplateOptions securityGroups(Iterable<String> securityGroups) {
+         throw new IllegalArgumentException("tags are immutable");
+      }
+
+      @Override
+      public TemplateOptions securityGroups(String... securityGroups) {
+         throw new IllegalArgumentException("tags are immutable");
+      }
+
+      @Override
       public TemplateOptions userMetadata(Map<String, String> userMetadata) {
          throw new IllegalArgumentException("userMetadata is immutable");
       }
@@ -321,6 +338,8 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable {
 
    protected Set<String> tags = ImmutableSet.of();
 
+   protected Set<String> securityGroups = ImmutableSet.of();
+
    protected String privateKey;
 
    protected String publicKey;
@@ -339,13 +358,13 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable {
       return super.equals(that) && equal(this.inboundPorts, that.inboundPorts) && equal(this.script, that.script)
             && equal(this.publicKey, that.publicKey) && equal(this.privateKey, that.privateKey)
             && equal(this.blockUntilRunning, that.blockUntilRunning) && equal(this.tags, that.tags)
-            && equal(this.userMetadata, that.userMetadata);
+            && equal(this.securityGroups, that.securityGroups) && equal(this.userMetadata, that.userMetadata);
    }
 
    @Override
    public int hashCode() {
       return Objects.hashCode(super.hashCode(), inboundPorts, script, publicKey, privateKey, blockUntilRunning, tags,
-            userMetadata);
+                              securityGroups, userMetadata);
    }
 
    @Override
@@ -363,6 +382,8 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable {
          toString.add("blockUntilRunning", blockUntilRunning);
       if (tags.size() != 0)
          toString.add("tags", tags);
+      if (securityGroups.size() != 0)
+         toString.add("securityGroups", securityGroups);
       if (userMetadata.size() != 0)
          toString.add("userMetadata", userMetadata);
       return toString;
@@ -380,6 +401,10 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable {
       return tags;
    }
 
+   public Set<String> getGroups() {
+      return securityGroups;
+   }
+
    public String getPrivateKey() {
       return privateKey;
    }
@@ -451,6 +476,21 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable {
    }
 
    /**
+    * assigns the created nodes to these security groups
+    */
+   public TemplateOptions securityGroups(Iterable<String> securityGroups) {
+      this.securityGroups = ImmutableSet.copyOf(checkNotNull(securityGroups, "securityGroups"));
+      return this;
+   }
+
+   /**
+    * @see TemplateOptions#securityGroups(Iterable<String>)
+    */
+   public TemplateOptions securityGroups(String... securityGroups) {
+      return securityGroups(ImmutableSet.copyOf(securityGroups));
+   }
+
+   /**
     * Opens the set of ports to public access.
     */
    public TemplateOptions inboundPorts(int... ports) {
@@ -522,6 +562,22 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable {
       }
 
       /**
+       * @see TemplateOptions#securityGroups
+       */
+      public static TemplateOptions securityGroups(Iterable<String> securityGroups) {
+         TemplateOptions options = new TemplateOptions();
+         return options.securityGroups(securityGroups);
+      }
+
+      /**
+       * @see TemplateOptions#securityGroups
+       */
+      public static TemplateOptions securityGroups(String... securityGroups) {
+         TemplateOptions options = new TemplateOptions();
+         return options.securityGroups(securityGroups);
+      }
+
+      /**
        * @see TemplateOptions#blockUntilRunning(boolean)
        */
       public static TemplateOptions blockUntilRunning(boolean blockUntilRunning) {

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceAdapter.java b/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceAdapter.java
index 925dc1a..22e7dcf 100644
--- a/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceAdapter.java
+++ b/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceAdapter.java
@@ -41,13 +41,18 @@ import org.jclouds.compute.domain.NodeMetadata.Status;
 import org.jclouds.compute.domain.NodeMetadataBuilder;
 import org.jclouds.compute.domain.OperatingSystem;
 import org.jclouds.compute.domain.OsFamily;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.SecurityGroupBuilder;
 import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.predicates.ImagePredicates;
 import org.jclouds.domain.Location;
 import org.jclouds.domain.LoginCredentials;
 import org.jclouds.location.suppliers.all.JustProvider;
 import org.jclouds.rest.ResourceNotFoundException;
 
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
@@ -55,6 +60,7 @@ import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.ListeningExecutorService;
 
@@ -66,20 +72,25 @@ import com.google.common.util.concurrent.ListeningExecutorService;
 public class StubComputeServiceAdapter implements JCloudsNativeComputeServiceAdapter {
    private final Supplier<Location> location;
    private final ConcurrentMap<String, NodeMetadata> nodes;
+   private final Multimap<String, SecurityGroup> groupsForNodes;
    private final ListeningExecutorService ioExecutor;
    private final Provider<Integer> idProvider;
+   private final Provider<Integer> groupIdProvider;
    private final String publicIpPrefix;
    private final String privateIpPrefix;
    private final String passwordPrefix;
    private final Supplier<Set<? extends Location>> locationSupplier;
    private final Map<OsFamily, Map<String, String>> osToVersionMap;
+   private final Optional<SecurityGroupExtension> securityGroupExtension;
 
    @Inject
    public StubComputeServiceAdapter(ConcurrentMap<String, NodeMetadata> nodes,
             @Named(Constants.PROPERTY_IO_WORKER_THREADS) ListeningExecutorService ioExecutor, Supplier<Location> location,
             @Named("NODE_ID") Provider<Integer> idProvider, @Named("PUBLIC_IP_PREFIX") String publicIpPrefix,
             @Named("PRIVATE_IP_PREFIX") String privateIpPrefix, @Named("PASSWORD_PREFIX") String passwordPrefix,
-            JustProvider locationSupplier, Map<OsFamily, Map<String, String>> osToVersionMap) {
+            JustProvider locationSupplier, Map<OsFamily, Map<String, String>> osToVersionMap,
+            Multimap<String, SecurityGroup> groupsForNodes, @Named("GROUP_ID") Provider<Integer> groupIdProvider,
+            Optional<SecurityGroupExtension> securityGroupExtension) {
       this.nodes = nodes;
       this.ioExecutor = ioExecutor;
       this.location = location;
@@ -89,6 +100,9 @@ public class StubComputeServiceAdapter implements JCloudsNativeComputeServiceAda
       this.passwordPrefix = passwordPrefix;
       this.locationSupplier = locationSupplier;
       this.osToVersionMap = osToVersionMap;
+      this.groupsForNodes = groupsForNodes;
+      this.groupIdProvider = groupIdProvider;
+      this.securityGroupExtension = securityGroupExtension;
    }
 
    protected void setStateOnNode(Status status, NodeMetadata node) {
@@ -133,6 +147,22 @@ public class StubComputeServiceAdapter implements JCloudsNativeComputeServiceAda
       builder.credentials(LoginCredentials.builder().user("root").password(passwordPrefix + id).build());
       NodeMetadata node = builder.build();
       nodes.put(node.getId(), node);
+
+      if (template.getOptions().getGroups().size() > 0) {
+         final String groupId = Iterables.getFirst(template.getOptions().getGroups(), "0");
+         Optional<SecurityGroup> secGroup = Iterables.tryFind(securityGroupExtension.get().listSecurityGroups(),
+                                                              new Predicate<SecurityGroup>() {
+                                                                 @Override
+                                                                 public boolean apply(SecurityGroup input) {
+                                                                    return input.getId().equals(groupId);
+                                                                 }
+                                                              });
+
+         if (secGroup.isPresent()) {
+            groupsForNodes.put(node.getId(), secGroup.get());
+         }
+      }
+      
       setStateOnNodeAfterDelay(Status.RUNNING, node, 100);
       return new NodeWithInitialCredentials(node);
    }
@@ -195,6 +225,8 @@ public class StubComputeServiceAdapter implements JCloudsNativeComputeServiceAda
          return;
       setStateOnNodeAfterDelay(Status.PENDING, node, 0);
       setStateOnNodeAfterDelay(Status.TERMINATED, node, 50);
+      groupsForNodes.removeAll(id);
+      
       ioExecutor.execute(new Runnable() {
 
          @Override

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java b/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java
index 91e858a..ab381b3 100644
--- a/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java
+++ b/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java
@@ -17,8 +17,12 @@
 package org.jclouds.compute.stub.config;
 
 import org.jclouds.compute.config.JCloudsNativeComputeServiceAdapterContextModule;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.concurrent.SingleThreaded;
 
+import com.google.common.base.Optional;
+import com.google.inject.Injector;
+
 /**
  * 
  * @author Adrian Cole
@@ -35,4 +39,10 @@ public class StubComputeServiceContextModule extends JCloudsNativeComputeService
       install(new StubComputeServiceDependenciesModule());
       super.configure();
    }
+
+   @Override
+   protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i) {
+      return Optional.of(i.getInstance(SecurityGroupExtension.class));
+   }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceDependenciesModule.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceDependenciesModule.java b/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceDependenciesModule.java
index de28ee1..0d07bbe 100644
--- a/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceDependenciesModule.java
+++ b/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceDependenciesModule.java
@@ -29,8 +29,11 @@ import org.jclouds.compute.domain.Hardware;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.NodeMetadata.Status;
 import org.jclouds.compute.domain.Processor;
+import org.jclouds.compute.domain.SecurityGroup;
 import org.jclouds.compute.domain.Volume;
 import org.jclouds.compute.domain.internal.VolumeImpl;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.stub.extensions.StubSecurityGroupExtension;
 import org.jclouds.domain.Credentials;
 import org.jclouds.location.Provider;
 import org.jclouds.predicates.SocketOpen;
@@ -40,9 +43,12 @@ import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
 import com.google.common.net.HostAndPort;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
 
 /**
  * 
@@ -52,6 +58,8 @@ public class StubComputeServiceDependenciesModule extends AbstractModule {
 
    @Override
    protected void configure() {
+      bind(new TypeLiteral<SecurityGroupExtension>() {
+      }).to(StubSecurityGroupExtension.class);
 
    }
 
@@ -73,6 +81,40 @@ public class StubComputeServiceDependenciesModule extends AbstractModule {
       return backing.get(creds.get().identity);
    }
 
+   protected static final LoadingCache<String, ConcurrentMap<String, SecurityGroup>> groupBacking = CacheBuilder.newBuilder()
+            .build(new CacheLoader<String, ConcurrentMap<String, SecurityGroup>>() {
+
+               @Override
+               public ConcurrentMap<String, SecurityGroup> load(String arg0) throws Exception {
+                  return new ConcurrentHashMap<String, SecurityGroup>();
+               }
+
+            });
+
+   @Provides
+   @Singleton
+   protected ConcurrentMap<String, SecurityGroup> provideGroups(@Provider Supplier<Credentials> creds)
+            throws ExecutionException {
+      return groupBacking.get(creds.get().identity);
+   }
+
+   protected static final LoadingCache<String, Multimap<String, SecurityGroup>> groupsForNodeBacking = CacheBuilder.newBuilder()
+            .build(new CacheLoader<String, Multimap<String, SecurityGroup>>() {
+
+               @Override
+               public Multimap<String, SecurityGroup> load(String arg0) throws Exception {
+                  return LinkedHashMultimap.create();
+               }
+
+            });
+
+   @Provides
+   @Singleton
+   protected Multimap<String, SecurityGroup> provideGroupsForNode(@Provider Supplier<Credentials> creds)
+            throws ExecutionException {
+      return groupsForNodeBacking.get(creds.get().identity);
+   }
+
    protected static final LoadingCache<String, AtomicInteger> nodeIds = CacheBuilder.newBuilder().build(
             new CacheLoader<String, AtomicInteger>() {
 
@@ -89,6 +131,22 @@ public class StubComputeServiceDependenciesModule extends AbstractModule {
       return nodeIds.get(creds.get().identity).incrementAndGet();
    }
 
+   protected static final LoadingCache<String, AtomicInteger> groupIds = CacheBuilder.newBuilder().build(
+            new CacheLoader<String, AtomicInteger>() {
+
+               @Override
+               public AtomicInteger load(String arg0) throws Exception {
+                  return new AtomicInteger(0);
+               }
+
+            });
+
+   @Provides
+   @Named("GROUP_ID")
+   protected Integer provideGroupIdForIdentity(@Provider Supplier<Credentials> creds) throws ExecutionException {
+      return groupIds.get(creds.get().identity).incrementAndGet();
+   }
+
    @Singleton
    @Provides
    @Named("PUBLIC_IP_PREFIX")

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/compute/stub/extensions/StubSecurityGroupExtension.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/stub/extensions/StubSecurityGroupExtension.java b/compute/src/main/java/org/jclouds/compute/stub/extensions/StubSecurityGroupExtension.java
new file mode 100644
index 0000000..5e8fdd9
--- /dev/null
+++ b/compute/src/main/java/org/jclouds/compute/stub/extensions/StubSecurityGroupExtension.java
@@ -0,0 +1,256 @@
+/*
+ * 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.compute.stub.extensions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.equalTo;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.collect.Iterables.filter;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+
+import org.jclouds.Constants;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.SecurityGroupBuilder;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.domain.Location;
+import org.jclouds.location.suppliers.all.JustProvider;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+/**
+ * An extension to compute service to allow for the manipulation of {@link SecurityGroup}s. Implementation
+ * is optional by providers.
+ * 
+ * @author Andrew Bayer
+ */
+public class StubSecurityGroupExtension implements SecurityGroupExtension {
+
+   private final Supplier<Location> location;
+   private final Provider<Integer> groupIdProvider;
+   private final Supplier<Set<? extends Location>> locationSupplier;
+   private final ListeningExecutorService ioExecutor;
+   private final ConcurrentMap<String, SecurityGroup> groups;
+   private final Multimap<String, SecurityGroup> groupsForNodes;
+
+   @Inject
+   public StubSecurityGroupExtension(ConcurrentMap<String, SecurityGroup> groups,
+                                     @Named(Constants.PROPERTY_IO_WORKER_THREADS) ListeningExecutorService ioExecutor,
+                                     Supplier<Location> location,
+                                     @Named("GROUP_ID") Provider<Integer> groupIdProvider, 
+                                     JustProvider locationSupplier,
+                                     Multimap<String, SecurityGroup> groupsForNodes) {
+      this.groups = groups;
+      this.ioExecutor = ioExecutor;
+      this.location = location;
+      this.groupIdProvider = groupIdProvider;
+      this.locationSupplier = locationSupplier;
+      this.groupsForNodes = groupsForNodes;
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroups() {
+      return ImmutableSet.copyOf(groups.values());
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroupsInLocation(final Location location) {
+      return ImmutableSet.copyOf(filter(groups.values(), new Predicate<SecurityGroup>() {
+               @Override
+               public boolean apply(SecurityGroup group) {
+                  return group.getLocation().equals(location);
+               }
+            }));
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroupsForNode(String nodeId) {
+      return ImmutableSet.copyOf(groupsForNodes.get(nodeId));
+   }
+
+   @Override
+   public SecurityGroup getSecurityGroupById(String id) {
+      return groups.get(id);
+   }
+   
+   @Override
+   public SecurityGroup createSecurityGroup(String name, Location location) {
+      SecurityGroupBuilder builder = new SecurityGroupBuilder();
+
+      String id = groupIdProvider.get() + "";
+      builder.ids(id);
+      builder.name(name);
+      builder.location(location);
+
+      SecurityGroup group = builder.build();
+
+      groups.put(group.getId(), group);
+
+      return group;
+   }
+
+   @Override
+   public boolean removeSecurityGroup(String id) {
+      if (groups.containsKey(id)) {
+         groups.remove(id);
+         return true;
+      }
+      return false;
+   }
+
+   @Override
+   public SecurityGroup addIpPermission(IpPermission ipPermission, SecurityGroup group) {
+      SecurityGroupBuilder builder = SecurityGroupBuilder.fromSecurityGroup(checkNotNull(group, "group"));
+
+      builder.ipPermission(checkNotNull(ipPermission, "ipPermission"));
+
+      SecurityGroup newGroup = builder.build();
+
+      if (groups.containsKey(newGroup.getId())) {
+         groups.remove(newGroup.getId());
+      }
+
+      groups.put(newGroup.getId(), newGroup);
+
+      return newGroup;
+   }
+
+   @Override
+   public SecurityGroup addIpPermission(IpProtocol protocol, int startPort, int endPort,
+                                 Multimap<String, String> tenantIdGroupNamePairs,
+                                 Iterable<String> ipRanges,
+                                 Iterable<String> groupIds, SecurityGroup group) {
+      IpPermission.Builder ipBuilder = IpPermission.builder();
+
+      ipBuilder.ipProtocol(protocol);
+      ipBuilder.fromPort(startPort);
+      ipBuilder.toPort(endPort);
+      if (tenantIdGroupNamePairs.size() > 0) {
+         ipBuilder.tenantIdGroupNamePairs(tenantIdGroupNamePairs);
+      }
+      if (Iterables.size(ipRanges) > 0) {
+         ipBuilder.cidrBlocks(ipRanges);
+      }
+      if (Iterables.size(groupIds) > 0) {
+         ipBuilder.groupIds(groupIds);
+      }
+
+      IpPermission perm = ipBuilder.build();
+
+      SecurityGroupBuilder builder = SecurityGroupBuilder.fromSecurityGroup(checkNotNull(group, "group"));
+
+      builder.ipPermission(perm);
+
+      SecurityGroup newGroup = builder.build();
+
+      if (groups.containsKey(newGroup.getId())) {
+         groups.remove(newGroup.getId());
+      }
+
+      groups.put(newGroup.getId(), newGroup);
+
+      return newGroup;
+   }
+      
+   @Override
+   public SecurityGroup removeIpPermission(IpPermission ipPermission, SecurityGroup group) {
+      SecurityGroupBuilder builder = SecurityGroupBuilder.fromSecurityGroup(checkNotNull(group, "group"));
+
+      builder.ipPermissions();
+
+      builder.ipPermissions(filter(group.getIpPermissions(), not(equalTo(ipPermission))));
+                            
+      SecurityGroup newGroup = builder.build();
+
+      if (groups.containsKey(newGroup.getId())) {
+         groups.remove(newGroup.getId());
+      }
+
+      groups.put(newGroup.getId(), newGroup);
+
+      return newGroup;
+   }
+   
+
+   @Override
+   public SecurityGroup removeIpPermission(IpProtocol protocol, int startPort, int endPort,
+                                    Multimap<String, String> tenantIdGroupNamePairs,
+                                    Iterable<String> ipRanges,
+                                    Iterable<String> groupIds, SecurityGroup group) {
+      IpPermission.Builder ipBuilder = IpPermission.builder();
+
+      ipBuilder.ipProtocol(protocol);
+      ipBuilder.fromPort(startPort);
+      ipBuilder.toPort(endPort);
+      if (tenantIdGroupNamePairs.size() > 0) {
+         ipBuilder.tenantIdGroupNamePairs(tenantIdGroupNamePairs);
+      }
+      if (Iterables.size(ipRanges) > 0) {
+         ipBuilder.cidrBlocks(ipRanges);
+      }
+      if (Iterables.size(groupIds) > 0) {
+         ipBuilder.groupIds(groupIds);
+      }
+
+      IpPermission perm = ipBuilder.build();
+
+      SecurityGroupBuilder builder = SecurityGroupBuilder.fromSecurityGroup(checkNotNull(group, "group"));
+
+      builder.ipPermissions();
+
+      builder.ipPermissions(filter(group.getIpPermissions(), not(equalTo(perm))));
+      
+      SecurityGroup newGroup = builder.build();
+
+      if (groups.containsKey(newGroup.getId())) {
+         groups.remove(newGroup.getId());
+      }
+
+      groups.put(newGroup.getId(), newGroup);
+
+      return newGroup;
+   }
+
+   @Override
+   public boolean supportsTenantIdGroupNamePairs() {
+      return false;
+   }
+
+   @Override
+   public boolean supportsGroupIds() {
+      return true;
+   }
+
+   @Override
+   public boolean supportsPortRangesForGroups() {
+      return true;
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/net/domain/IpPermission.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/net/domain/IpPermission.java b/compute/src/main/java/org/jclouds/net/domain/IpPermission.java
index 895d9d3..d04af0c 100644
--- a/compute/src/main/java/org/jclouds/net/domain/IpPermission.java
+++ b/compute/src/main/java/org/jclouds/net/domain/IpPermission.java
@@ -17,11 +17,15 @@
 package org.jclouds.net.domain;
 
 import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.transform;
+import static org.jclouds.util.Strings2.isCidrFormat;
 
 import java.util.Set;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
 import com.google.common.base.Objects;
 import com.google.common.base.Objects.ToStringHelper;
 import com.google.common.collect.ImmutableMultimap;
@@ -100,6 +104,7 @@ public class IpPermission implements Comparable<IpPermission> {
        * @see IpPermission#getCidrBlocks()
        */
       public Builder cidrBlock(String cidrBlock) {
+         checkArgument(isCidrFormat(cidrBlock), "cidrBlock %s is not a valid CIDR", cidrBlock);
          this.cidrBlocks.add(cidrBlock);
          return this;
       }
@@ -108,7 +113,16 @@ public class IpPermission implements Comparable<IpPermission> {
        * @see IpPermission#getCidrBlocks()
        */
       public Builder cidrBlocks(Iterable<String> cidrBlocks) {
-         Iterables.addAll(this.cidrBlocks, cidrBlocks);
+         Iterables.addAll(this.cidrBlocks, transform(cidrBlocks,
+                                                     new Function<String, String>() {
+                                                        @Override
+                                                        public String apply(String input) {
+                                                           checkArgument(isCidrFormat(input),
+                                                                         "input %s is not a valid CIDR",
+                                                                         input);
+                                                           return input;
+                                                        }
+                                                     }));
          return this;
       }
 
@@ -140,7 +154,7 @@ public class IpPermission implements Comparable<IpPermission> {
    private final IpProtocol ipProtocol;
    private final Set<String> cidrBlocks;
 
-   protected IpPermission(IpProtocol ipProtocol, int fromPort, int toPort,
+   public IpPermission(IpProtocol ipProtocol, int fromPort, int toPort,
             Multimap<String, String> tenantIdGroupNamePairs, Iterable<String> groupIds, Iterable<String> cidrBlocks) {
       this.fromPort = fromPort;
       this.toPort = toPort;
@@ -227,8 +241,8 @@ public class IpPermission implements Comparable<IpPermission> {
 
    protected ToStringHelper string() {
       return Objects.toStringHelper("").add("ipProtocol", ipProtocol).add("fromPort", fromPort).add("toPort", toPort)
-               .add("tenantIdGroupNamePairs", tenantIdGroupNamePairs).add("groupIds", groupIds).add("groupIds",
-                        groupIds);
+               .add("tenantIdGroupNamePairs", tenantIdGroupNamePairs).add("groupIds", groupIds).add("cidrBlocks",
+                        cidrBlocks);
    }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/main/java/org/jclouds/net/domain/IpProtocol.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/net/domain/IpProtocol.java b/compute/src/main/java/org/jclouds/net/domain/IpProtocol.java
index 50d4422..203f0f9 100644
--- a/compute/src/main/java/org/jclouds/net/domain/IpProtocol.java
+++ b/compute/src/main/java/org/jclouds/net/domain/IpProtocol.java
@@ -29,12 +29,22 @@ public enum IpProtocol {
 
    TCP, UDP, ICMP, ALL, UNRECOGNIZED;
 
+   public String value() {
+      return this == ALL ? "-1" : name().toLowerCase();
+   }
+
+   @Override
+   public String toString() {
+      return value();
+   }
+
    public static IpProtocol fromValue(String protocol) {
       try {
+         if (protocol.equalsIgnoreCase("-1"))
+            return ALL;
          return valueOf(checkNotNull(protocol, "protocol").toUpperCase());
       } catch (IllegalArgumentException e) {
          return UNRECOGNIZED;
       }
    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/test/java/org/jclouds/compute/extensions/internal/BaseSecurityGroupExtensionLiveTest.java
----------------------------------------------------------------------
diff --git a/compute/src/test/java/org/jclouds/compute/extensions/internal/BaseSecurityGroupExtensionLiveTest.java b/compute/src/test/java/org/jclouds/compute/extensions/internal/BaseSecurityGroupExtensionLiveTest.java
new file mode 100644
index 0000000..1a5c646
--- /dev/null
+++ b/compute/src/test/java/org/jclouds/compute/extensions/internal/BaseSecurityGroupExtensionLiveTest.java
@@ -0,0 +1,348 @@
+/*
+ * 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.compute.extensions.internal;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static com.google.common.base.Predicates.equalTo;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.collect.Iterables.filter;
+import static org.jclouds.compute.predicates.NodePredicates.inGroup;
+import static org.jclouds.util.Predicates2.retry;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.RunNodesException;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.SecurityGroupBuilder;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Location;
+import org.jclouds.logging.Logger;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+import org.jclouds.ssh.SshClient;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+
+/**
+ * Base test for {@link SecurityGroupExtension} implementations.
+ * 
+ * @author David Alves
+ * 
+ */
+public abstract class BaseSecurityGroupExtensionLiveTest extends BaseComputeServiceContextLiveTest {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   protected String groupId;
+
+   /**
+    * Returns the template for the base node, override to test different templates.
+    * 
+    * @return
+    */
+   public Template getNodeTemplate() {
+      return view.getComputeService().templateBuilder().build();
+   }
+
+      
+
+   @Test(groups = { "integration", "live" }, singleThreaded = true)
+   public void testCreateSecurityGroup() throws RunNodesException, InterruptedException, ExecutionException {
+
+      ComputeService computeService = view.getComputeService();
+
+      Location location = getNodeTemplate().getLocation();
+      
+      Optional<SecurityGroupExtension> securityGroupExtension = computeService.getSecurityGroupExtension();
+
+      assertTrue(securityGroupExtension.isPresent(), "security extension was not present");
+
+      SecurityGroup group = securityGroupExtension.get().createSecurityGroup("test-create-security-group", location);
+
+      logger.info("Group created: %s", group);
+
+      assertTrue(group.getName().contains("test-create-security-group"));
+
+      groupId = group.getId();
+   }
+
+   @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testCreateSecurityGroup")
+   public void testGetSecurityGroupById() throws RunNodesException, InterruptedException, ExecutionException {
+
+      ComputeService computeService = view.getComputeService();
+
+      Location location = getNodeTemplate().getLocation();
+      
+      Optional<SecurityGroupExtension> securityGroupExtension = computeService.getSecurityGroupExtension();
+
+      assertTrue(securityGroupExtension.isPresent(), "security extension was not present");
+
+      SecurityGroup group = securityGroupExtension.get().getSecurityGroupById(groupId);
+
+      logger.info("Group found: %s", group);
+
+      assertTrue(group.getName().contains("test-create-security-group"));
+   }
+
+   @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testGetSecurityGroupById")
+   public void testAddIpPermission() {
+
+      ComputeService computeService = view.getComputeService();
+
+      Optional<SecurityGroupExtension> securityGroupExtension = computeService.getSecurityGroupExtension();
+      assertTrue(securityGroupExtension.isPresent(), "security group extension was not present");
+
+      Optional<SecurityGroup> optGroup = getGroup(securityGroupExtension.get());
+
+      assertTrue(optGroup.isPresent());
+
+      SecurityGroup group = optGroup.get();
+
+      IpPermission.Builder builder = IpPermission.builder();
+
+      builder.ipProtocol(IpProtocol.TCP);
+      builder.fromPort(10);
+      builder.toPort(20);
+      builder.cidrBlock("0.0.0.0/0");
+
+      IpPermission perm = builder.build();
+
+      SecurityGroup newGroup = securityGroupExtension.get().addIpPermission(perm, group);
+
+      assertEquals(perm, Iterables.getOnlyElement(newGroup.getIpPermissions())); 
+   }
+   
+   @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testAddIpPermission")
+   public void testRemoveIpPermission() {
+
+      ComputeService computeService = view.getComputeService();
+
+      Optional<SecurityGroupExtension> securityGroupExtension = computeService.getSecurityGroupExtension();
+      assertTrue(securityGroupExtension.isPresent(), "security group extension was not present");
+
+      Optional<SecurityGroup> optGroup = getGroup(securityGroupExtension.get());
+
+      assertTrue(optGroup.isPresent());
+
+      SecurityGroup group = optGroup.get();
+
+      IpPermission.Builder builder = IpPermission.builder();
+
+      builder.ipProtocol(IpProtocol.TCP);
+      builder.fromPort(10);
+      builder.toPort(20);
+      builder.cidrBlock("0.0.0.0/0");
+
+      IpPermission perm = builder.build();
+
+      SecurityGroup newGroup = securityGroupExtension.get().removeIpPermission(perm, group);
+
+      assertEquals(0, Iterables.size(newGroup.getIpPermissions())); 
+   }
+
+   @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testRemoveIpPermission")
+   public void testAddIpPermissionsFromSpec() {
+
+      ComputeService computeService = view.getComputeService();
+
+      Optional<SecurityGroupExtension> securityGroupExtension = computeService.getSecurityGroupExtension();
+      assertTrue(securityGroupExtension.isPresent(), "security group extension was not present");
+
+      Optional<SecurityGroup> optGroup = getGroup(securityGroupExtension.get());
+
+      assertTrue(optGroup.isPresent());
+
+      SecurityGroup group = optGroup.get();
+
+      IpPermission.Builder builder = IpPermission.builder();
+
+      builder.ipProtocol(IpProtocol.TCP);
+      builder.fromPort(50);
+      builder.toPort(60);
+      builder.cidrBlock("0.0.0.0/0");
+
+      IpPermission perm = builder.build();
+
+      SecurityGroup newGroup = securityGroupExtension.get().addIpPermission(IpProtocol.TCP,
+                                                                            50,
+                                                                            60,
+                                                                            emptyMultimap(),
+                                                                            ImmutableSet.of("0.0.0.0/0"),
+                                                                            emptyStringSet(),
+                                                                            group);
+
+      assertTrue(newGroup.getIpPermissions().contains(perm)); 
+
+      if (securityGroupExtension.get().supportsGroupIds()) {
+         IpPermission.Builder secondBuilder = IpPermission.builder();
+         
+         int fromPort;
+         int toPort;
+
+         if (securityGroupExtension.get().supportsPortRangesForGroups()) {
+            fromPort = 70;
+            toPort = 80;
+         } else {
+            fromPort = 1;
+            toPort = 65535;
+         }
+         secondBuilder.ipProtocol(IpProtocol.TCP);
+         secondBuilder.fromPort(fromPort);
+         secondBuilder.toPort(toPort);
+         secondBuilder.groupId(group.getId());
+         
+         IpPermission secondPerm = secondBuilder.build();
+         
+         SecurityGroup secondNewGroup = securityGroupExtension.get().addIpPermission(IpProtocol.TCP,
+                                                                                     fromPort,
+                                                                                     toPort,
+                                                                                     emptyMultimap(),
+                                                                                     emptyStringSet(),
+                                                                                     ImmutableSet.of(group.getId()),
+                                                                                     newGroup);
+         
+         assertTrue(secondNewGroup.getIpPermissions().contains(secondPerm)); 
+      }
+
+      if (securityGroupExtension.get().supportsTenantIdGroupNamePairs()) {
+         IpPermission.Builder thirdBuilder = IpPermission.builder();
+
+         int fromPort;
+         int toPort;
+
+         if (securityGroupExtension.get().supportsPortRangesForGroups()) {
+            fromPort = 90;
+            toPort = 100;
+         } else {
+            fromPort = 1;
+            toPort = 65535;
+         }
+         thirdBuilder.ipProtocol(IpProtocol.TCP);
+         thirdBuilder.fromPort(fromPort);
+         thirdBuilder.toPort(toPort);
+         thirdBuilder.tenantIdGroupNamePair(group.getOwnerId(), group.getId());
+         
+         IpPermission thirdPerm = thirdBuilder.build();
+
+         SecurityGroup thirdNewGroup = securityGroupExtension.get().addIpPermission(IpProtocol.TCP,
+                                                                                    fromPort,
+                                                                                    toPort,
+                                                                                    thirdPerm.getTenantIdGroupNamePairs(),
+                                                                                    emptyStringSet(),
+                                                                                    emptyStringSet(),
+                                                                                    newGroup);
+
+         assertTrue(thirdNewGroup.getIpPermissions().contains(thirdPerm)); 
+      }
+   }
+   
+   @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testAddIpPermissionsFromSpec")
+   public void testCreateNodeWithSecurityGroup() throws RunNodesException, InterruptedException, ExecutionException {
+
+      ComputeService computeService = view.getComputeService();
+
+      Optional<SecurityGroupExtension> securityGroupExtension = computeService.getSecurityGroupExtension();
+
+      assertTrue(securityGroupExtension.isPresent(), "security group extension was not present");
+
+      Template template = view.getComputeService().templateBuilder()
+         .options(TemplateOptions.Builder.securityGroups(groupId))
+         .build();
+      
+      NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup("test-create-node-with-group", 1, template));
+
+      Set<SecurityGroup> groups = securityGroupExtension.get().listSecurityGroupsForNode(node.getId());
+
+      assertTrue(groups.size() > 0, "node has no groups");
+      
+      Optional<SecurityGroup> secGroup = Iterables.tryFind(securityGroupExtension.get().listSecurityGroupsForNode(node.getId()),
+                                                           new Predicate<SecurityGroup>() {
+                                                              @Override
+                                                              public boolean apply(SecurityGroup input) {
+                                                                 return input.getId().equals(groupId);
+                                                              }
+                                                           });
+
+      assertTrue(secGroup.isPresent());
+
+      computeService.destroyNodesMatching(inGroup(node.getGroup()));
+
+      
+   }
+
+   // testDeleteSecurityGroup currently disabled until I can find a way to get it to delete the security group while a terminated
+   // instance is still floating around in EC2. - abayer, 6/14/13
+   /*
+   @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testCreateNodeWithSecurityGroup")
+   public void testDeleteSecurityGroup() {
+
+      ComputeService computeService = view.getComputeService();
+
+      Optional<SecurityGroupExtension> securityGroupExtension = computeService.getSecurityGroupExtension();
+      assertTrue(securityGroupExtension.isPresent(), "security group extension was not present");
+
+      Optional<SecurityGroup> optGroup = getGroup(securityGroupExtension.get());
+
+      assertTrue(optGroup.isPresent());
+
+      SecurityGroup group = optGroup.get();
+
+      assertTrue(securityGroupExtension.get().removeSecurityGroup(group.getId()));
+   }
+   */
+   
+   private Multimap<String, String> emptyMultimap() {
+      return LinkedHashMultimap.create();
+   }
+
+   private Set<String> emptyStringSet() {
+      return Sets.newLinkedHashSet();
+   }
+   
+   private Optional<SecurityGroup> getGroup(SecurityGroupExtension ext) {
+      return Iterables.tryFind(ext.listSecurityGroups(), new Predicate<SecurityGroup>() {
+         @Override
+         public boolean apply(SecurityGroup input) {
+            return input.getId().equals(groupId);
+         }
+      });
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/test/java/org/jclouds/compute/stub/extensions/StubSecurityGroupExtensionIntegrationTest.java
----------------------------------------------------------------------
diff --git a/compute/src/test/java/org/jclouds/compute/stub/extensions/StubSecurityGroupExtensionIntegrationTest.java b/compute/src/test/java/org/jclouds/compute/stub/extensions/StubSecurityGroupExtensionIntegrationTest.java
new file mode 100644
index 0000000..8c2b31c
--- /dev/null
+++ b/compute/src/test/java/org/jclouds/compute/stub/extensions/StubSecurityGroupExtensionIntegrationTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.compute.extensions.internal;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.jclouds.util.Predicates2.retry;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Properties;
+import java.util.concurrent.ExecutionException;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.RunNodesException;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.SecurityGroupBuilder;
+import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Location;
+import org.jclouds.logging.Logger;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.ssh.SshClient;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+
+/**
+ * Base test for {@link SecurityGroupExtension} implementations.
+ * 
+ * @author Andrew Bayer
+ * 
+ */
+@Test(groups = { "integration", "live" }, singleThreaded = true, testName="StubSecurityGroupExtensionIntegrationTest")
+public class StubSecurityGroupExtensionIntegrationTest extends BaseSecurityGroupExtensionLiveTest {
+
+   public StubSecurityGroupExtensionIntegrationTest() {
+      provider = "stub";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties overrides = super.setupProperties();
+      // This is a hack to make sure we get a different set of node IDs, nodes, groups, etc from StubComputeServiceIntegrationTest.
+      overrides.setProperty(provider + ".identity", "sec-stub");
+
+      return overrides;
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/compute/src/test/java/org/jclouds/net/util/IpPermissionsTest.java
----------------------------------------------------------------------
diff --git a/compute/src/test/java/org/jclouds/net/util/IpPermissionsTest.java b/compute/src/test/java/org/jclouds/net/util/IpPermissionsTest.java
index 2c278f6..30f5453 100644
--- a/compute/src/test/java/org/jclouds/net/util/IpPermissionsTest.java
+++ b/compute/src/test/java/org/jclouds/net/util/IpPermissionsTest.java
@@ -37,6 +37,20 @@ public class IpPermissionsTest {
                .cidrBlock("0.0.0.0/0").build());
    }
 
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testAllProtocolInvalidCidr() {
+      IpPermissions authorization = IpPermissions.permitAnyProtocol();
+      assertEquals(authorization, IpPermission.builder().ipProtocol(IpProtocol.ALL).fromPort(1).toPort(65535)
+               .cidrBlock("a.0.0.0/0").build());
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testAllProtocolInvalidCidrMultiple() {
+      IpPermissions authorization = IpPermissions.permitAnyProtocol();
+      assertEquals(authorization, IpPermission.builder().ipProtocol(IpProtocol.ALL).fromPort(1).toPort(65535)
+                   .cidrBlocks(ImmutableSet.of("a.0.0.0/0", "0.0.0.0/0")).build());
+   }
+
    public void testAllProtocolCidrBound() {
       IpPermissions authorization = IpPermissions.permit(IpProtocol.ALL).originatingFromCidrBlock("1.1.1.1/32");
       assertEquals(authorization, IpPermission.builder().ipProtocol(IpProtocol.ALL).fromPort(1).toPort(65535)

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/core/src/main/java/org/jclouds/util/Strings2.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/util/Strings2.java b/core/src/main/java/org/jclouds/util/Strings2.java
index 8b85b53..2a47286 100644
--- a/core/src/main/java/org/jclouds/util/Strings2.java
+++ b/core/src/main/java/org/jclouds/util/Strings2.java
@@ -87,7 +87,16 @@ public class Strings2 {
                }
             }
          });
+   
+   private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
+   private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,3})";
+   private static final Pattern ADDRESS_PATTERN = Pattern.compile(IP_ADDRESS);
+   private static final Pattern CIDR_PATTERN = Pattern.compile(SLASH_FORMAT);
 
+   public static boolean isCidrFormat(String in) {
+      return CIDR_PATTERN.matcher(in).matches();
+   }
+      
    private static final Pattern URL_ENCODED_PATTERN = Pattern.compile(".*%[a-fA-F0-9][a-fA-F0-9].*");
 
    public static boolean isUrlEncoded(String in) {

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/core/src/test/java/org/jclouds/util/Strings2Test.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/jclouds/util/Strings2Test.java b/core/src/test/java/org/jclouds/util/Strings2Test.java
index d7e3cd5..5b1c9a0 100644
--- a/core/src/test/java/org/jclouds/util/Strings2Test.java
+++ b/core/src/test/java/org/jclouds/util/Strings2Test.java
@@ -53,4 +53,12 @@ public class Strings2Test {
       assertEquals(actual, urlDecode(urlEncode(actual)));
    }
 
+   public void testIsCidrFormat() {
+      assert Strings2.isCidrFormat("1.2.3.4/5");
+      assert Strings2.isCidrFormat("0.0.0.0/0");
+      assert !Strings2.isCidrFormat("banana");
+      assert !Strings2.isCidrFormat("1.2.3.4");
+      assert !Strings2.isCidrFormat("500.500.500.500/2423");
+   }
+      
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeService.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeService.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeService.java
index d9a15f3..8bcc4d5 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeService.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2ComputeService.java
@@ -46,6 +46,7 @@ import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.functions.GroupNamingConvention;
 import org.jclouds.compute.internal.PersistNodeCredentials;
 import org.jclouds.compute.options.TemplateOptions;
@@ -104,13 +105,14 @@ public class AWSEC2ComputeService extends EC2ComputeService {
          @Named("PLACEMENT") LoadingCache<RegionAndName, String> placementGroupMap,
          @Named("DELETED") Predicate<PlacementGroup> placementGroupDeleted, Optional<ImageExtension> imageExtension,
          GroupNamingConvention.Factory namingConvention,
-         @Named(PROPERTY_EC2_GENERATE_INSTANCE_NAMES) boolean generateInstanceNames) {
+         @Named(PROPERTY_EC2_GENERATE_INSTANCE_NAMES) boolean generateInstanceNames,
+         Optional<SecurityGroupExtension> securityGroupExtension) {
       super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy,
             getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
             startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
             nodeTerminated, nodeSuspended, initScriptRunnerFactory, runScriptOnNodeFactory, initAdminAccess,
             persistNodeCredentials, timeouts, userExecutor, client, credentialsMap, securityGroupMap, imageExtension,
-            namingConvention, generateInstanceNames);
+            namingConvention, generateInstanceNames, securityGroupExtension);
       this.client = client;
       this.placementGroupMap = placementGroupMap;
       this.placementGroupDeleted = placementGroupDeleted;

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java
index 7d18968..5341d57 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java
@@ -39,6 +39,7 @@ import org.jclouds.aws.ec2.compute.suppliers.AWSEC2HardwareSupplier;
 import org.jclouds.compute.config.BaseComputeServiceContextModule;
 import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.ec2.compute.config.EC2BindComputeStrategiesByClass;
 import org.jclouds.ec2.compute.domain.RegionAndName;
@@ -173,4 +174,9 @@ public class AWSEC2ComputeServiceContextModule extends BaseComputeServiceContext
    protected Optional<ImageExtension> provideImageExtension(Injector i) {
       return Optional.of(i.getInstance(ImageExtension.class));
    }
+
+   @Override
+   protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i) {
+      return Optional.of(i.getInstance(SecurityGroupExtension.class));
+   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/5f524ee6/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceDependenciesModule.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceDependenciesModule.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceDependenciesModule.java
index f82216f..8102cb6 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceDependenciesModule.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceDependenciesModule.java
@@ -31,6 +31,7 @@ import javax.inject.Singleton;
 
 import org.jclouds.aws.ec2.compute.AWSEC2ComputeService;
 import org.jclouds.aws.ec2.compute.AWSEC2TemplateOptions;
+import org.jclouds.aws.ec2.compute.extensions.AWSEC2SecurityGroupExtension;
 import org.jclouds.aws.ec2.compute.loaders.AWSEC2CreateSecurityGroupIfNeeded;
 import org.jclouds.aws.ec2.compute.suppliers.CallForImages;
 import org.jclouds.aws.ec2.domain.PlacementGroup;
@@ -41,8 +42,10 @@ import org.jclouds.aws.ec2.predicates.PlacementGroupAvailable;
 import org.jclouds.aws.ec2.predicates.PlacementGroupDeleted;
 import org.jclouds.compute.ComputeService;
 import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.SecurityGroup;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.config.ValueOfConfigurationKeyOrNull;
 import org.jclouds.domain.LoginCredentials;
@@ -50,9 +53,11 @@ import org.jclouds.ec2.compute.config.EC2ComputeServiceDependenciesModule;
 import org.jclouds.ec2.compute.domain.PasswordDataAndPrivateKey;
 import org.jclouds.ec2.compute.domain.RegionAndName;
 import org.jclouds.ec2.compute.extensions.EC2ImageExtension;
+import org.jclouds.ec2.compute.extensions.EC2SecurityGroupExtension;
 import org.jclouds.ec2.compute.functions.CreateUniqueKeyPair;
 import org.jclouds.ec2.compute.functions.CredentialsForInstance;
 import org.jclouds.ec2.compute.functions.EC2ImageParser;
+import org.jclouds.ec2.compute.functions.EC2SecurityGroupToSecurityGroup;
 import org.jclouds.ec2.compute.functions.PasswordCredentialsFromWindowsInstance;
 import org.jclouds.ec2.compute.functions.WindowsLoginCredentialsFromEncryptedData;
 import org.jclouds.ec2.compute.internal.EC2TemplateBuilderImpl;
@@ -105,8 +110,12 @@ public class AWSEC2ComputeServiceDependenciesModule extends EC2ComputeServiceDep
       install(new FactoryModuleBuilder().build(CallForImages.Factory.class));
       bind(new TypeLiteral<Function<org.jclouds.ec2.domain.Image, Image>>() {
       }).to(EC2ImageParser.class);
+      bind(new TypeLiteral<Function<org.jclouds.ec2.domain.SecurityGroup, SecurityGroup>>() {
+      }).to(EC2SecurityGroupToSecurityGroup.class);
       bind(new TypeLiteral<ImageExtension>() {
       }).to(EC2ImageExtension.class);
+      bind(new TypeLiteral<SecurityGroupExtension>() {
+      }).to(AWSEC2SecurityGroupExtension.class);
    }
 
    @Provides