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/09/17 20:51:08 UTC

git commit: JCLOUDS-267. Add SecurityGroupExtension support for Nova.

Updated Branches:
  refs/heads/master e29cdb142 -> 389ba6c94


JCLOUDS-267. Add SecurityGroupExtension support for Nova.


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

Branch: refs/heads/master
Commit: 389ba6c94a44b3e1331dfa4be5f159f6e5e33bae
Parents: e29cdb1
Author: Andrew Bayer <an...@gmail.com>
Authored: Fri Sep 13 10:31:02 2013 -0700
Committer: Andrew Bayer <an...@gmail.com>
Committed: Tue Sep 17 11:47:42 2013 -0700

----------------------------------------------------------------------
 .../config/NovaComputeServiceContextModule.java |  16 +-
 .../extensions/NovaSecurityGroupExtension.java  | 422 ++++++++++++++++++
 .../NovaSecurityGroupInZoneToSecurityGroup.java |  73 ++++
 .../NovaSecurityGroupToSecurityGroup.java       |  13 +-
 .../loaders/FindSecurityGroupOrCreate.java      |   3 +-
 .../domain/zonescoped/SecurityGroupInZone.java  |   2 +-
 .../nova/v2_0/extensions/SecurityGroupApi.java  |   2 +-
 .../predicates/SecurityGroupPredicates.java     | 139 +++++-
 .../NovaSecurityGroupExtensionExpectTest.java   | 423 +++++++++++++++++++
 .../NovaSecurityGroupExtensionLiveTest.java     |  35 ++
 ...aSecurityGroupInZoneToSecurityGroupTest.java |  93 ++++
 .../NovaSecurityGroupToSecurityGroupTest.java   |  89 ++--
 .../predicates/SecurityGroupPredicatesTest.java |  93 +++-
 .../securitygroup_details_extension.json        |  34 ++
 ...securitygroup_details_extension_norules.json |  10 +
 .../resources/securitygroup_list_extension.json |  51 +++
 .../securitygrouprule_created_cidr.json         |  13 +
 .../securitygrouprule_created_group.json        |  14 +
 .../server_with_security_groups_extension.json  |   1 +
 19 files changed, 1467 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
index 57a341d..311d4ab 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
@@ -40,6 +40,7 @@ import org.jclouds.compute.domain.OperatingSystem;
 import org.jclouds.compute.domain.OsFamily;
 import org.jclouds.compute.domain.SecurityGroup;
 import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
 import org.jclouds.domain.Location;
@@ -49,10 +50,12 @@ import org.jclouds.net.domain.IpPermission;
 import org.jclouds.openstack.nova.v2_0.compute.NovaComputeService;
 import org.jclouds.openstack.nova.v2_0.compute.NovaComputeServiceAdapter;
 import org.jclouds.openstack.nova.v2_0.compute.extensions.NovaImageExtension;
+import org.jclouds.openstack.nova.v2_0.compute.extensions.NovaSecurityGroupExtension;
 import org.jclouds.openstack.nova.v2_0.compute.functions.CreateSecurityGroupIfNeeded;
 import org.jclouds.openstack.nova.v2_0.compute.functions.FlavorInZoneToHardware;
 import org.jclouds.openstack.nova.v2_0.compute.functions.ImageInZoneToImage;
 import org.jclouds.openstack.nova.v2_0.compute.functions.ImageToOperatingSystem;
+import org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupInZoneToSecurityGroup;
 import org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupToSecurityGroup;
 import org.jclouds.openstack.nova.v2_0.compute.functions.OrphanedGroupsByZoneId;
 import org.jclouds.openstack.nova.v2_0.compute.functions.SecurityGroupRuleToIpPermission;
@@ -119,6 +122,9 @@ public class NovaComputeServiceContextModule extends
       bind(new TypeLiteral<Function<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup, SecurityGroup>>() {
       }).to(NovaSecurityGroupToSecurityGroup.class);
 
+      bind(new TypeLiteral<Function<SecurityGroupInZone, SecurityGroup>>() {
+      }).to(NovaSecurityGroupInZoneToSecurityGroup.class);
+
       bind(new TypeLiteral<Function<Set<? extends NodeMetadata>,  Multimap<String, String>>>() {
       }).to(OrphanedGroupsByZoneId.class);
 
@@ -153,6 +159,9 @@ public class NovaComputeServiceContextModule extends
       
       bind(new TypeLiteral<ImageExtension>() {
       }).to(NovaImageExtension.class);
+
+      bind(new TypeLiteral<SecurityGroupExtension>() {
+      }).to(NovaSecurityGroupExtension.class);
    }
 
    @Override
@@ -187,7 +196,7 @@ public class NovaComputeServiceContextModule extends
 
    @Provides
    @Singleton
-   @Named(TIMEOUT_SECURITYGROUP_PRESENT)
+   @Named("SECURITYGROUP_PRESENT")
    protected Predicate<AtomicReference<ZoneAndName>> securityGroupEventualConsistencyDelay(
             FindSecurityGroupWithNameAndReturnTrue in,
             @Named(TIMEOUT_SECURITYGROUP_PRESENT) long msDelay) {
@@ -269,4 +278,9 @@ public class NovaComputeServiceContextModule extends
    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/389ba6c9/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java
new file mode 100644
index 0000000..416e3a9
--- /dev/null
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtension.java
@@ -0,0 +1,422 @@
+/*
+ * 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.openstack.nova.v2_0.compute.extensions;
+
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.and;
+import static com.google.common.base.Predicates.notNull;
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.nameIn;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleCidr;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleEndPort;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleGroup;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleProtocol;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleStartPort;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.jclouds.Constants;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.domain.Location;
+import org.jclouds.location.Zone;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+import org.jclouds.openstack.nova.v2_0.NovaApi;
+import org.jclouds.openstack.nova.v2_0.domain.Ingress;
+import org.jclouds.openstack.nova.v2_0.domain.SecurityGroupRule;
+import org.jclouds.openstack.nova.v2_0.domain.ServerWithSecurityGroups;
+import org.jclouds.openstack.nova.v2_0.domain.zonescoped.SecurityGroupInZone;
+import org.jclouds.openstack.nova.v2_0.domain.zonescoped.ZoneAndId;
+import org.jclouds.openstack.nova.v2_0.domain.zonescoped.ZoneAndName;
+import org.jclouds.openstack.nova.v2_0.domain.zonescoped.ZoneSecurityGroupNameAndPorts;
+import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi;
+import org.jclouds.openstack.nova.v2_0.extensions.ServerWithSecurityGroupsApi;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+/**
+ * An extension to compute service to allow for the manipulation of {@link org.jclouds.compute.domain.SecurityGroup}s. Implementation
+ * is optional by providers.
+ *
+ * @author Andrew Bayer
+ */
+public class NovaSecurityGroupExtension implements SecurityGroupExtension {
+
+   protected final NovaApi api;
+   protected final ListeningExecutorService userExecutor;
+   protected final Supplier<Set<String>> zoneIds;
+   protected final Function<SecurityGroupInZone, SecurityGroup> groupConverter;
+   protected final LoadingCache<ZoneAndName, SecurityGroupInZone> groupCreator;
+   protected final GroupNamingConvention.Factory namingConvention;
+
+   @Inject
+   public NovaSecurityGroupExtension(NovaApi api,
+                                    @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
+                                    @Zone Supplier<Set<String>> zoneIds,
+                                    Function<SecurityGroupInZone, SecurityGroup> groupConverter,
+                                    LoadingCache<ZoneAndName, SecurityGroupInZone> groupCreator,
+                                    GroupNamingConvention.Factory namingConvention) {
+
+      this.api = checkNotNull(api, "api");
+      this.userExecutor = checkNotNull(userExecutor, "userExecutor");
+      this.zoneIds = checkNotNull(zoneIds, "zoneIds");
+      this.groupConverter = checkNotNull(groupConverter, "groupConverter");
+      this.groupCreator = checkNotNull(groupCreator, "groupCreator");
+      this.namingConvention = checkNotNull(namingConvention, "namingConvention");
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroups() {
+      Iterable<? extends SecurityGroupInZone> rawGroups = pollSecurityGroups();
+      Iterable<SecurityGroup> groups = transform(filter(rawGroups, notNull()),
+              groupConverter);
+      return ImmutableSet.copyOf(groups);
+   }
+
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroupsInLocation(final Location location) {
+      String zone = location.getId();
+      if (zone == null) {
+         return ImmutableSet.of();
+      }
+      return listSecurityGroupsInLocation(zone);
+   }
+
+   public Set<SecurityGroup> listSecurityGroupsInLocation(String zone) {
+      Iterable<? extends SecurityGroupInZone> rawGroups = pollSecurityGroupsByZone(zone);
+      Iterable<SecurityGroup> groups = transform(filter(rawGroups, notNull()),
+              groupConverter);
+      return ImmutableSet.copyOf(groups);
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroupsForNode(String id) {
+      ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(checkNotNull(id, "id"));
+      String zone = zoneAndId.getZone();
+      String instanceId = zoneAndId.getId();
+
+      Optional<? extends ServerWithSecurityGroupsApi> serverApi = api.getServerWithSecurityGroupsExtensionForZone(zone);
+      Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupExtensionForZone(zone);
+
+      if (!serverApi.isPresent() || !sgApi.isPresent()) {
+         return ImmutableSet.of();
+      }
+
+      ServerWithSecurityGroups instance = serverApi.get().get(instanceId);
+      if (instance == null) {
+         return ImmutableSet.of();
+      }
+
+      Set<String> groupNames = instance.getSecurityGroupNames();
+      Set<? extends SecurityGroupInZone> rawGroups =
+              sgApi.get().list().filter(nameIn(groupNames)).transform(groupToGroupInZone(zone)).toSet();
+
+      return ImmutableSet.copyOf(transform(filter(rawGroups, notNull()), groupConverter));
+   }
+
+   @Override
+   public SecurityGroup getSecurityGroupById(String id) {
+      ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(checkNotNull(id, "id"));
+      String zone = zoneAndId.getZone();
+      String groupId = zoneAndId.getId();
+
+      Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupExtensionForZone(zone);
+
+      if (!sgApi.isPresent()) {
+         return null;
+      }
+
+      SecurityGroupInZone rawGroup = new SecurityGroupInZone(sgApi.get().get(groupId), zone);
+
+      return groupConverter.apply(rawGroup);
+   }
+
+   @Override
+   public SecurityGroup createSecurityGroup(String name, Location location) {
+      String zone = location.getId();
+      if (zone == null) {
+         return null;
+      }
+      return createSecurityGroup(name, zone);
+   }
+
+   public SecurityGroup createSecurityGroup(String name, String zone) {
+      String markerGroup = namingConvention.create().sharedNameForGroup(name);
+      ZoneSecurityGroupNameAndPorts zoneAndName = new ZoneSecurityGroupNameAndPorts(zone, markerGroup, ImmutableSet.<Integer> of());
+
+      SecurityGroupInZone rawGroup = groupCreator.apply(zoneAndName);
+      return groupConverter.apply(rawGroup);
+   }
+
+   @Override
+   public boolean removeSecurityGroup(String id) {
+      checkNotNull(id, "id");
+      ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(id);
+      String zone = zoneAndId.getZone();
+      String groupId = zoneAndId.getId();
+
+      Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupExtensionForZone(zone);
+
+      if (!sgApi.isPresent()) {
+         return false;
+      }
+
+      if (sgApi.get().get(groupId) == null) {
+         return false;
+      }
+
+      sgApi.get().delete(groupId);
+      // TODO: test this clear happens
+      groupCreator.invalidate(new ZoneSecurityGroupNameAndPorts(zone, groupId, ImmutableSet.<Integer> of()));
+      return true;
+   }
+
+   @Override
+   public SecurityGroup addIpPermission(IpPermission ipPermission, SecurityGroup group) {
+      String zone = group.getLocation().getId();
+      String id = group.getId();
+
+      Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupExtensionForZone(zone);
+
+      if (!sgApi.isPresent()) {
+         return null;
+      }
+
+      if (ipPermission.getCidrBlocks().size() > 0) {
+         for (String cidr : ipPermission.getCidrBlocks()) {
+            sgApi.get().createRuleAllowingCidrBlock(id,
+                    Ingress.builder()
+                            .ipProtocol(ipPermission.getIpProtocol())
+                            .fromPort(ipPermission.getFromPort())
+                            .toPort(ipPermission.getToPort())
+                            .build(),
+                    cidr);
+         }
+      }
+
+      if (ipPermission.getGroupIds().size() > 0) {
+         for (String zoneAndGroupRaw : ipPermission.getGroupIds()) {
+            ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(zoneAndGroupRaw);
+            String groupId = zoneAndId.getId();
+            sgApi.get().createRuleAllowingSecurityGroupId(id,
+                    Ingress.builder()
+                            .ipProtocol(ipPermission.getIpProtocol())
+                            .fromPort(ipPermission.getFromPort())
+                            .toPort(ipPermission.getToPort())
+                            .build(),
+                    groupId);
+         }
+      }
+
+      return getSecurityGroupById(ZoneAndId.fromZoneAndId(zone, id).slashEncode());
+   }
+
+   @Override
+   public SecurityGroup addIpPermission(IpProtocol protocol, int startPort, int endPort,
+                                        Multimap<String, String> tenantIdGroupNamePairs,
+                                        Iterable<String> ipRanges,
+                                        Iterable<String> groupIds, SecurityGroup group) {
+      String zone = group.getLocation().getId();
+      String id = group.getId();
+
+      Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupExtensionForZone(zone);
+
+      if (!sgApi.isPresent()) {
+         return null;
+      }
+
+      if (Iterables.size(ipRanges) > 0) {
+         for (String cidr : ipRanges) {
+            sgApi.get().createRuleAllowingCidrBlock(id,
+                    Ingress.builder()
+                            .ipProtocol(protocol)
+                            .fromPort(startPort)
+                            .toPort(endPort)
+                            .build(),
+                    cidr);
+         }
+      }
+
+      if (Iterables.size(groupIds) > 0) {
+         for (String zoneAndGroupRaw : groupIds) {
+            ZoneAndId zoneAndId = ZoneAndId.fromSlashEncoded(zoneAndGroupRaw);
+            String groupId = zoneAndId.getId();
+            sgApi.get().createRuleAllowingSecurityGroupId(id,
+                    Ingress.builder()
+                            .ipProtocol(protocol)
+                            .fromPort(startPort)
+                            .toPort(endPort)
+                            .build(),
+                    groupId);
+         }
+      }
+
+      return getSecurityGroupById(ZoneAndId.fromZoneAndId(zone, id).slashEncode());
+   }
+
+   @Override
+   public SecurityGroup removeIpPermission(IpPermission ipPermission, SecurityGroup group) {
+      String zone = group.getLocation().getId();
+      String id = group.getId();
+
+      Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupExtensionForZone(zone);
+
+      if (!sgApi.isPresent()) {
+         return null;
+      }
+
+      org.jclouds.openstack.nova.v2_0.domain.SecurityGroup securityGroup = sgApi.get().get(id);
+
+      if (ipPermission.getCidrBlocks().size() > 0) {
+         for (String cidr : ipPermission.getCidrBlocks()) {
+            for (SecurityGroupRule rule : filter(securityGroup.getRules(),
+                    and(ruleCidr(cidr), ruleProtocol(ipPermission.getIpProtocol()),
+                            ruleStartPort(ipPermission.getFromPort()),
+                            ruleEndPort(ipPermission.getToPort())))) {
+               sgApi.get().deleteRule(rule.getId());
+            }
+         }
+      }
+
+      if (ipPermission.getGroupIds().size() > 0) {
+         for (String groupId : ipPermission.getGroupIds()) {
+            for (SecurityGroupRule rule : filter(securityGroup.getRules(),
+                    and(ruleGroup(groupId), ruleProtocol(ipPermission.getIpProtocol()),
+                            ruleStartPort(ipPermission.getFromPort()),
+                            ruleEndPort(ipPermission.getToPort())))) {
+               sgApi.get().deleteRule(rule.getId());
+            }
+
+         }
+      }
+
+      return getSecurityGroupById(ZoneAndId.fromZoneAndId(zone, id).slashEncode());
+   }
+
+   @Override
+   public SecurityGroup removeIpPermission(IpProtocol protocol, int startPort, int endPort,
+                                           Multimap<String, String> tenantIdGroupNamePairs,
+                                           Iterable<String> ipRanges,
+                                           Iterable<String> groupIds, SecurityGroup group) {
+      String zone = group.getLocation().getId();
+      String id = group.getId();
+
+      Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupExtensionForZone(zone);
+
+      if (!sgApi.isPresent()) {
+         return null;
+      }
+
+      org.jclouds.openstack.nova.v2_0.domain.SecurityGroup securityGroup = sgApi.get().get(id);
+
+      if (Iterables.size(ipRanges) > 0) {
+         for (String cidr : ipRanges) {
+            for (SecurityGroupRule rule : filter(securityGroup.getRules(),
+                    and(ruleCidr(cidr),
+                            ruleProtocol(protocol),
+                            ruleStartPort(startPort),
+                            ruleEndPort(endPort)))) {
+               sgApi.get().deleteRule(rule.getId());
+            }
+         }
+      }
+
+      if (Iterables.size(groupIds) > 0) {
+         for (String groupId : groupIds) {
+            for (SecurityGroupRule rule : filter(securityGroup.getRules(),
+                    and(ruleGroup(groupId),
+                            ruleProtocol(protocol),
+                            ruleStartPort(startPort),
+                            ruleEndPort(endPort)))) {
+               sgApi.get().deleteRule(rule.getId());
+            }
+         }
+      }
+
+      return getSecurityGroupById(ZoneAndId.fromZoneAndId(zone, id).slashEncode());
+   }
+
+   @Override
+   public boolean supportsTenantIdGroupNamePairs() {
+      return false;
+   }
+
+   @Override
+   public boolean supportsGroupIds() {
+      return true;
+   }
+
+   @Override
+   public boolean supportsPortRangesForGroups() {
+      return false;
+   }
+
+   protected Iterable<? extends SecurityGroupInZone> pollSecurityGroups() {
+      Iterable<? extends Set<? extends SecurityGroupInZone>> groups
+              = transform(zoneIds.get(), allSecurityGroupsInZone());
+
+      return concat(groups);
+   }
+
+
+   protected Iterable<? extends SecurityGroupInZone> pollSecurityGroupsByZone(String zone) {
+      return allSecurityGroupsInZone().apply(zone);
+   }
+
+   protected Function<String, Set<? extends SecurityGroupInZone>> allSecurityGroupsInZone() {
+      return new Function<String, Set<? extends SecurityGroupInZone>>() {
+
+         @Override
+         public Set<? extends SecurityGroupInZone> apply(final String from) {
+            Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupExtensionForZone(from);
+
+            if (!sgApi.isPresent()) {
+               return ImmutableSet.of();
+            }
+
+
+            return sgApi.get().list().transform(groupToGroupInZone(from)).toSet();
+         }
+
+      };
+   }
+
+   protected Function<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup, SecurityGroupInZone> groupToGroupInZone(final String zone) {
+      return new Function<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup, SecurityGroupInZone>() {
+         @Override
+         public SecurityGroupInZone apply(org.jclouds.openstack.nova.v2_0.domain.SecurityGroup group) {
+            return new SecurityGroupInZone(group, zone);
+         }
+      };
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupInZoneToSecurityGroup.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupInZoneToSecurityGroup.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupInZoneToSecurityGroup.java
new file mode 100644
index 0000000..124cd65
--- /dev/null
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupInZoneToSecurityGroup.java
@@ -0,0 +1,73 @@
+/*
+ * 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.openstack.nova.v2_0.compute.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.Map;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.SecurityGroupBuilder;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Location;
+import org.jclouds.logging.Logger;
+import org.jclouds.openstack.nova.v2_0.domain.zonescoped.SecurityGroupInZone;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.inject.Inject;
+
+
+/**
+ * A function for transforming a Nova-specific SecurityGroup into a generic
+ * SecurityGroup object.
+ * 
+ * @author Andrew Bayer
+ */
+@Singleton
+public class NovaSecurityGroupInZoneToSecurityGroup implements Function<SecurityGroupInZone, SecurityGroup> {
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   protected final Function<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup, SecurityGroup> baseConverter;
+   protected final Supplier<Map<String, Location>> locationIndex;
+
+   @Inject
+   public NovaSecurityGroupInZoneToSecurityGroup(Function<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup, SecurityGroup> baseConverter,
+                                                 Supplier<Map<String, Location>> locationIndex) {
+      this.baseConverter = checkNotNull(baseConverter, "baseConverter");
+      this.locationIndex = checkNotNull(locationIndex, "locationIndex");
+   }
+
+   @Override
+   public SecurityGroup apply(SecurityGroupInZone group) {
+      SecurityGroupBuilder builder = SecurityGroupBuilder.fromSecurityGroup(baseConverter.apply(group.getSecurityGroup()));
+
+      Location zone = locationIndex.get().get(group.getZone());
+      checkState(zone != null, "location %s not in locationIndex: %s", group.getZone(), locationIndex.get());
+
+      builder.location(zone);
+
+      return builder.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java
index 3b70ae7..d39f849 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java
@@ -16,12 +16,8 @@
  */
 package org.jclouds.openstack.nova.v2_0.compute.functions;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.Iterables.transform;
 
-import java.util.NoSuchElementException;
-import java.util.Set;
-
 import javax.annotation.Resource;
 import javax.inject.Named;
 import javax.inject.Singleton;
@@ -34,9 +30,6 @@ import org.jclouds.net.domain.IpPermission;
 import org.jclouds.openstack.nova.v2_0.domain.SecurityGroupRule;
 
 import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.base.Supplier;
-import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
 
@@ -67,8 +60,10 @@ public class NovaSecurityGroupToSecurityGroup implements Function<org.jclouds.op
       builder.providerId(group.getId());
       builder.ownerId(group.getTenantId());
       builder.name(group.getName());
-      builder.ipPermissions(transform(group.getRules(), ruleToPermission));
-      
+      if (group.getRules() != null) {
+         builder.ipPermissions(transform(group.getRules(), ruleToPermission));
+      }
+
       return builder.build();
    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java
index 433f4b9..71951cd 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/loaders/FindSecurityGroupOrCreate.java
@@ -17,7 +17,6 @@
 package org.jclouds.openstack.nova.v2_0.compute.loaders;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
-import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.TIMEOUT_SECURITYGROUP_PRESENT;
 
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -44,7 +43,7 @@ public class FindSecurityGroupOrCreate extends CacheLoader<ZoneAndName, Security
 
    @Inject
    public FindSecurityGroupOrCreate(
-            @Named(TIMEOUT_SECURITYGROUP_PRESENT) Predicate<AtomicReference<ZoneAndName>> returnSecurityGroupExistsInZone,
+            @Named("SECURITYGROUP_PRESENT") Predicate<AtomicReference<ZoneAndName>> returnSecurityGroupExistsInZone,
             Function<ZoneSecurityGroupNameAndPorts, SecurityGroupInZone> groupCreator) {
       this.returnSecurityGroupExistsInZone = checkNotNull(returnSecurityGroupExistsInZone,
                "returnSecurityGroupExistsInZone");

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/zonescoped/SecurityGroupInZone.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/zonescoped/SecurityGroupInZone.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/zonescoped/SecurityGroupInZone.java
index dd892a2..28e2b0f 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/zonescoped/SecurityGroupInZone.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/zonescoped/SecurityGroupInZone.java
@@ -31,7 +31,7 @@ public class SecurityGroupInZone extends ZoneAndName {
       this.securityGroup = securityGroup;
    }
 
-   public SecurityGroup getServer() {
+   public SecurityGroup getSecurityGroup() {
       return securityGroup;
    }
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/SecurityGroupApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/SecurityGroupApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/SecurityGroupApi.java
index 9e8d260..867ccd3 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/SecurityGroupApi.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/extensions/SecurityGroupApi.java
@@ -77,7 +77,7 @@ public interface SecurityGroupApi {
     * @return a new Security Group Rule
     */
    SecurityGroupRule createRuleAllowingSecurityGroupId(String parentGroup, Ingress ingress,
-            String sourceCidr);
+            String groupId);
 
    /**
     * Delete a Security Group Rule.

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicates.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicates.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicates.java
index 108618f..412e37f 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicates.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicates.java
@@ -18,9 +18,14 @@ package org.jclouds.openstack.nova.v2_0.predicates;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import java.util.Set;
+
+import org.jclouds.net.domain.IpProtocol;
 import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
+import org.jclouds.openstack.nova.v2_0.domain.SecurityGroupRule;
 
 import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
 
 /**
  * Predicates handy when working with SecurityGroups
@@ -32,7 +37,7 @@ public class SecurityGroupPredicates {
 
    /**
     * matches name of the given security group
-    * 
+    *
     * @param name
     * @return predicate that matches name
     */
@@ -53,6 +58,28 @@ public class SecurityGroupPredicates {
    }
 
    /**
+    * matches name of the given security group against a list
+    *
+    * @param names
+    * @return predicate that matches one of the names
+    */
+   public static Predicate<SecurityGroup> nameIn(final Set<String> names) {
+      checkNotNull(names, "names must be defined");
+
+      return new Predicate<SecurityGroup>() {
+         @Override
+         public boolean apply(SecurityGroup ext) {
+            return Predicates.in(names).apply(ext.getName());
+         }
+
+         @Override
+         public String toString() {
+            return "nameIn(" + names + ")";
+         }
+      };
+   }
+
+   /**
     * matches name of the given security group
     * 
     * @param name
@@ -73,4 +100,114 @@ public class SecurityGroupPredicates {
          }
       };
    }
+
+   /**
+    * matches a security group rule by its cidr
+    *
+    * @param cidr
+    * @return predicate that matches cidr
+    */
+   public static Predicate<SecurityGroupRule> ruleCidr(final String cidr) {
+      checkNotNull(cidr, "cidr must be defined");
+
+      return new Predicate<SecurityGroupRule>() {
+         @Override
+         public boolean apply(SecurityGroupRule ext) {
+            return cidr.equals(ext.getIpRange());
+         }
+
+         @Override
+         public String toString() {
+            return "cidr(" + cidr + ")";
+         }
+      };
+   }
+
+   /**
+    * matches a security group rule by the security group it allows
+    *
+    * @param groupName
+    * @return predicate that matches group
+    */
+   public static Predicate<SecurityGroupRule> ruleGroup(final String groupName) {
+      checkNotNull(groupName, "groupName must be defined");
+
+      return new Predicate<SecurityGroupRule>() {
+         @Override
+         public boolean apply(SecurityGroupRule ext) {
+            return ext.getGroup() != null && groupName.equals(ext.getGroup().getName());
+         }
+
+         @Override
+         public String toString() {
+            return "ruleGroup(" + groupName + ")";
+         }
+      };
+   }
+
+   /**
+    * matches a security group rule by the protocol
+    *
+    * @param protocol
+    * @return predicate that matches protocol
+    */
+   public static Predicate<SecurityGroupRule> ruleProtocol(final IpProtocol protocol) {
+      checkNotNull(protocol, "protocol must be defined");
+
+      return new Predicate<SecurityGroupRule>() {
+         @Override
+         public boolean apply(SecurityGroupRule ext) {
+            return protocol.equals(ext.getIpProtocol());
+         }
+
+         @Override
+         public String toString() {
+            return "ruleProtocol(" + protocol + ")";
+         }
+      };
+   }
+
+   /**
+    * matches a security group rule by the start port
+    *
+    * @param startPort
+    * @return predicate that matches startPort
+    */
+   public static Predicate<SecurityGroupRule> ruleStartPort(final int startPort) {
+      checkNotNull(startPort, "startPort must be defined");
+
+      return new Predicate<SecurityGroupRule>() {
+         @Override
+         public boolean apply(SecurityGroupRule ext) {
+            return startPort == ext.getFromPort();
+         }
+
+         @Override
+         public String toString() {
+            return "ruleStartPort(" + startPort + ")";
+         }
+      };
+   }
+
+   /**
+    * matches a security group rule by the end port
+    *
+    * @param endPort
+    * @return predicate that matches endPort
+    */
+   public static Predicate<SecurityGroupRule> ruleEndPort(final int endPort) {
+      checkNotNull(endPort, "endPort must be defined");
+
+      return new Predicate<SecurityGroupRule>() {
+         @Override
+         public boolean apply(SecurityGroupRule ext) {
+            return endPort == ext.getToPort();
+         }
+
+         @Override
+         public String toString() {
+            return "ruleEndPort(" + endPort + ")";
+         }
+      };
+   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtensionExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtensionExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtensionExpectTest.java
new file mode 100644
index 0000000..b80351e
--- /dev/null
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtensionExpectTest.java
@@ -0,0 +1,423 @@
+/*
+ * 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.openstack.nova.v2_0.compute.extensions;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Properties;
+import java.util.Set;
+
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+import org.jclouds.openstack.nova.v2_0.internal.BaseNovaComputeServiceExpectTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+
+/**
+ * 
+ * @author Andrew Bayer
+ */
+@Test(groups = "unit", testName = "NovaSecurityGroupExtensionExpectTest")
+public class NovaSecurityGroupExtensionExpectTest extends BaseNovaComputeServiceExpectTest {
+
+   protected String zone = "az-1.region-a.geo-1";
+
+   @Override
+   protected Properties setupProperties() {
+      Properties overrides = super.setupProperties();
+      overrides.setProperty("jclouds.zones", zone);
+      return overrides;
+   }
+
+   public void testListSecurityGroups() {
+      HttpRequest list = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse listResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_list.json")).build();
+
+
+      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
+      requestResponseMap.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess);
+      requestResponseMap.put(extensionsOfNovaRequest, extensionsOfNovaResponse);
+      requestResponseMap.put(list, listResponse).build();
+
+      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
+
+      Set<SecurityGroup> groups = extension.listSecurityGroups();
+      assertEquals(groups.size(), 1);
+   }
+
+   public void testListSecurityGroupsInLocation() {
+      HttpRequest list = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse listResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_list.json")).build();
+
+
+      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
+      requestResponseMap.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess);
+      requestResponseMap.put(extensionsOfNovaRequest, extensionsOfNovaResponse);
+      requestResponseMap.put(list, listResponse).build();
+
+      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
+
+      Set<SecurityGroup> groups = extension.listSecurityGroupsInLocation(new LocationBuilder()
+              .scope(LocationScope.ZONE)
+              .id(zone)
+              .description("zone")
+              .build());
+      assertEquals(groups.size(), 1);
+   }
+
+   public void testListSecurityGroupsForNode() {
+      HttpRequest serverReq = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-create-server-ext/8d0a6ca5-8849-4b3d-b86e-f24c92490ebb"))
+              .headers(
+                      ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                              authToken).build()).build();
+
+      HttpResponse serverResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/server_with_security_groups_extension.json")).build();
+
+      HttpRequest list = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse listResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_list.json")).build();
+
+
+      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
+      requestResponseMap.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess);
+      requestResponseMap.put(extensionsOfNovaRequest, extensionsOfNovaResponse);
+      requestResponseMap.put(serverReq, serverResponse);
+      requestResponseMap.put(list, listResponse).build();
+
+      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
+
+      Set<SecurityGroup> groups = extension.listSecurityGroupsForNode(zone + "/8d0a6ca5-8849-4b3d-b86e-f24c92490ebb");
+      assertEquals(groups.size(), 1);
+   }
+
+   public void testGetSecurityGroupById() {
+      HttpRequest getSecurityGroup = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups/160")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse getSecurityGroupResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension.json")).build();
+
+      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
+      requestResponseMap.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess);
+      requestResponseMap.put(extensionsOfNovaRequest, extensionsOfNovaResponse);
+      requestResponseMap.put(getSecurityGroup, getSecurityGroupResponse).build();
+
+      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
+
+      SecurityGroup group = extension.getSecurityGroupById(zone + "/160");
+      assertEquals(group.getId(), "160");
+   }
+
+   public void testCreateSecurityGroup() {
+      HttpRequest create = HttpRequest.builder().method("POST").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build())
+              .payload(
+                      payloadFromStringWithContentType(
+                              "{\"security_group\":{\"name\":\"jclouds-test\",\"description\":\"jclouds-test\"}}",
+                              "application/json")).build();
+
+      HttpResponse createResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_created.json")).build();
+
+      HttpRequest list = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups")).headers(
+              ImmutableMultimap.<String, String>builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse listResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_list_extension.json")).build();
+
+      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
+      requestResponseMap.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess);
+      requestResponseMap.put(extensionsOfNovaRequest, extensionsOfNovaResponse);
+      requestResponseMap.put(create, createResponse);
+      requestResponseMap.put(list, listResponse).build();
+
+      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
+
+      SecurityGroup group = extension.createSecurityGroup("test", new LocationBuilder()
+              .scope(LocationScope.ZONE)
+              .id(zone)
+              .description("zone")
+              .build());
+      assertEquals(group.getId(), "160");
+   }
+
+   public void testRemoveSecurityGroup() {
+      HttpRequest delete = HttpRequest.builder().method("DELETE").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups/160"))
+              .headers(
+                      ImmutableMultimap.<String, String>builder().put("Accept", "application/json")
+                              .put("X-Auth-Token", authToken).build()).build();
+
+      HttpResponse deleteResponse = HttpResponse.builder().statusCode(202).build();
+
+      HttpRequest getSecurityGroup = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups/160")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse getSecurityGroupResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension.json")).build();
+
+      Builder<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder();
+      requestResponseMap.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess);
+      requestResponseMap.put(extensionsOfNovaRequest, extensionsOfNovaResponse);
+      requestResponseMap.put(getSecurityGroup, getSecurityGroupResponse);
+      requestResponseMap.put(delete, deleteResponse).build();
+
+      SecurityGroupExtension extension = requestsSendResponses(requestResponseMap.build()).getSecurityGroupExtension().get();
+
+      assertTrue(extension.removeSecurityGroup(zone + "/160"), "Expected removal of securitygroup to be successful");
+   }
+
+   public void testAddIpPermissionCidrFromIpPermission() {
+      HttpRequest createRule = HttpRequest
+              .builder()
+              .method("POST")
+              .endpoint("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-group-rules")
+              .addHeader("Accept", "application/json")
+              .addHeader("X-Auth-Token", authToken)
+              .payload(
+                      payloadFromStringWithContentType(
+                              "{\"security_group_rule\":{\"parent_group_id\":\"160\",\"cidr\":\"10.2.6.0/24\",\"ip_protocol\":\"tcp\",\"from_port\":\"22\",\"to_port\":\"22\"}}",
+                              "application/json")).build();
+
+      HttpResponse createRuleResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygrouprule_created_cidr.json")).build();
+
+      HttpRequest getSecurityGroup = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups/160")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse getSecurityGroupNoRulesResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension_norules.json")).build();
+
+      HttpResponse getSecurityGroupResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension.json")).build();
+
+
+      SecurityGroupExtension extension = orderedRequestsSendResponses(ImmutableList.of(keystoneAuthWithUsernameAndPasswordAndTenantName,
+              extensionsOfNovaRequest, getSecurityGroup, createRule, getSecurityGroup),
+              ImmutableList.of(responseWithKeystoneAccess, extensionsOfNovaResponse, getSecurityGroupNoRulesResponse,
+                      createRuleResponse, getSecurityGroupResponse)).getSecurityGroupExtension().get();
+
+      IpPermission.Builder builder = IpPermission.builder();
+
+      builder.ipProtocol(IpProtocol.TCP);
+      builder.fromPort(22);
+      builder.toPort(22);
+      builder.cidrBlock("10.2.6.0/24");
+
+      IpPermission perm = builder.build();
+
+      SecurityGroup origGroup = extension.getSecurityGroupById(zone + "/160");
+
+      assertNotNull(origGroup);
+      SecurityGroup newGroup = extension.addIpPermission(perm, origGroup);
+
+      assertNotNull(newGroup);
+   }
+
+   public void testAddIpPermissionCidrFromParams() {
+      HttpRequest createRule = HttpRequest
+              .builder()
+              .method("POST")
+              .endpoint("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-group-rules")
+              .addHeader("Accept", "application/json")
+              .addHeader("X-Auth-Token", authToken)
+              .payload(
+                      payloadFromStringWithContentType(
+                              "{\"security_group_rule\":{\"parent_group_id\":\"160\",\"cidr\":\"10.2.6.0/24\",\"ip_protocol\":\"tcp\",\"from_port\":\"22\",\"to_port\":\"22\"}}",
+                              "application/json")).build();
+
+      HttpResponse createRuleResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygrouprule_created_cidr.json")).build();
+
+      HttpRequest getSecurityGroup = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups/160")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse getSecurityGroupNoRulesResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension_norules.json")).build();
+
+      HttpResponse getSecurityGroupResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension.json")).build();
+
+
+      SecurityGroupExtension extension = orderedRequestsSendResponses(ImmutableList.of(keystoneAuthWithUsernameAndPasswordAndTenantName,
+              extensionsOfNovaRequest, getSecurityGroup, createRule, getSecurityGroup),
+              ImmutableList.of(responseWithKeystoneAccess, extensionsOfNovaResponse, getSecurityGroupNoRulesResponse,
+                      createRuleResponse, getSecurityGroupResponse)).getSecurityGroupExtension().get();
+
+      SecurityGroup origGroup = extension.getSecurityGroupById(zone + "/160");
+
+      assertNotNull(origGroup);
+      SecurityGroup newGroup = extension.addIpPermission(IpProtocol.TCP,
+              22,
+              22,
+              emptyMultimap(),
+              ImmutableSet.of("10.2.6.0/24"),
+              emptyStringSet(),
+              origGroup);
+
+      assertNotNull(newGroup);
+   }
+
+   public void testAddIpPermissionGroupFromIpPermission() {
+      HttpRequest createRule = HttpRequest
+              .builder()
+              .method("POST")
+              .endpoint("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-group-rules")
+              .addHeader("Accept", "application/json")
+              .addHeader("X-Auth-Token", authToken)
+              .payload(
+                      payloadFromStringWithContentType(
+                              "{\"security_group_rule\":{\"group_id\":\"11111\",\"parent_group_id\":\"160\",\"ip_protocol\":\"tcp\",\"from_port\":\"22\",\"to_port\":\"22\"}}",
+                              "application/json")).build();
+
+      HttpResponse createRuleResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygrouprule_created_group.json")).build();
+
+      HttpRequest getSecurityGroup = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups/160")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse getSecurityGroupNoRulesResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension_norules.json")).build();
+
+      HttpResponse getSecurityGroupResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension.json")).build();
+
+
+      SecurityGroupExtension extension = orderedRequestsSendResponses(ImmutableList.of(keystoneAuthWithUsernameAndPasswordAndTenantName,
+              extensionsOfNovaRequest, getSecurityGroup, createRule, getSecurityGroup),
+              ImmutableList.of(responseWithKeystoneAccess, extensionsOfNovaResponse, getSecurityGroupNoRulesResponse,
+                      createRuleResponse, getSecurityGroupResponse)).getSecurityGroupExtension().get();
+
+      IpPermission.Builder builder = IpPermission.builder();
+
+      builder.ipProtocol(IpProtocol.TCP);
+      builder.fromPort(22);
+      builder.toPort(22);
+      builder.groupId("admin/11111");
+
+      IpPermission perm = builder.build();
+
+      SecurityGroup origGroup = extension.getSecurityGroupById(zone + "/160");
+
+      assertNotNull(origGroup);
+      SecurityGroup newGroup = extension.addIpPermission(perm, origGroup);
+
+      assertNotNull(newGroup);
+   }
+
+   public void testAddIpPermissionGroupFromParams() {
+      HttpRequest createRule = HttpRequest
+              .builder()
+              .method("POST")
+              .endpoint("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-group-rules")
+              .addHeader("Accept", "application/json")
+              .addHeader("X-Auth-Token", authToken)
+              .payload(
+                      payloadFromStringWithContentType(
+                              "{\"security_group_rule\":{\"group_id\":\"11111\",\"parent_group_id\":\"160\",\"ip_protocol\":\"tcp\",\"from_port\":\"22\",\"to_port\":\"22\"}}",
+                              "application/json")).build();
+
+      HttpResponse createRuleResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygrouprule_created_group.json")).build();
+
+      HttpRequest getSecurityGroup = HttpRequest.builder().method("GET").endpoint(
+              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/os-security-groups/160")).headers(
+              ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
+                      authToken).build()).build();
+
+      HttpResponse getSecurityGroupNoRulesResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension_norules.json")).build();
+
+      HttpResponse getSecurityGroupResponse = HttpResponse.builder().statusCode(200).payload(
+              payloadFromResource("/securitygroup_details_extension.json")).build();
+
+
+      SecurityGroupExtension extension = orderedRequestsSendResponses(ImmutableList.of(keystoneAuthWithUsernameAndPasswordAndTenantName,
+              extensionsOfNovaRequest, getSecurityGroup, createRule, getSecurityGroup),
+              ImmutableList.of(responseWithKeystoneAccess, extensionsOfNovaResponse, getSecurityGroupNoRulesResponse,
+                      createRuleResponse, getSecurityGroupResponse)).getSecurityGroupExtension().get();
+
+      SecurityGroup origGroup = extension.getSecurityGroupById(zone + "/160");
+
+      assertNotNull(origGroup);
+      SecurityGroup newGroup = extension.addIpPermission(IpProtocol.TCP,
+              22,
+              22,
+              emptyMultimap(),
+              emptyStringSet(),
+              ImmutableSet.of("admin/11111"),
+              origGroup);
+
+      assertNotNull(newGroup);
+   }
+
+   private Multimap<String, String> emptyMultimap() {
+      return LinkedHashMultimap.create();
+   }
+
+   private Set<String> emptyStringSet() {
+      return Sets.newLinkedHashSet();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtensionLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtensionLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtensionLiveTest.java
new file mode 100644
index 0000000..4a7dbed
--- /dev/null
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NovaSecurityGroupExtensionLiveTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.openstack.nova.v2_0.compute.extensions;
+
+import org.jclouds.compute.extensions.internal.BaseSecurityGroupExtensionLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ * Live test for openstack-nova {@link org.jclouds.compute.extensions.SecurityGroupExtension} implementation.
+ *
+ * @author Andrew Bayer
+ *
+ */
+@Test(groups = "live", singleThreaded = true, testName = "NovaSecurityGroupExtensionLiveTest")
+public class NovaSecurityGroupExtensionLiveTest extends BaseSecurityGroupExtensionLiveTest {
+
+   public NovaSecurityGroupExtensionLiveTest() {
+      provider = "openstack-nova";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupInZoneToSecurityGroupTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupInZoneToSecurityGroupTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupInZoneToSecurityGroupTest.java
new file mode 100644
index 0000000..7c588ad
--- /dev/null
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupInZoneToSecurityGroupTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.openstack.nova.v2_0.compute.functions;
+
+import static com.google.common.collect.Iterables.transform;
+import static org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupToSecurityGroupTest.securityGroupWithCidr;
+import static org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupToSecurityGroupTest.securityGroupWithGroup;
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.domain.Location;
+import org.jclouds.domain.LocationBuilder;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.openstack.nova.v2_0.domain.zonescoped.SecurityGroupInZone;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * @author Andrew Bayer
+ */
+@Test(groups = "unit", testName = "NovaSecurityGroupInZoneToSecurityGroupTest")
+public class NovaSecurityGroupInZoneToSecurityGroupTest {
+
+   private static final SecurityGroupRuleToIpPermission ruleConverter = new SecurityGroupRuleToIpPermission();
+   Location provider = new LocationBuilder().scope(LocationScope.PROVIDER).id("openstack-nova")
+           .description("openstack-nova").build();
+   Location zone = new LocationBuilder().id("az-1.region-a.geo-1").description("az-1.region-a.geo-1")
+           .scope(LocationScope.ZONE).parent(provider).build();
+   Supplier<Map<String, Location>> locationIndex = Suppliers.<Map<String, Location>> ofInstance(ImmutableMap
+           .<String, Location>of("az-1.region-a.geo-1", zone));
+
+
+   @Test
+   public void testApplyWithGroup() {
+      NovaSecurityGroupInZoneToSecurityGroup parser = createGroupParser();
+
+      SecurityGroupInZone origGroup = new SecurityGroupInZone(securityGroupWithGroup(), zone.getId());
+
+      SecurityGroup newGroup = parser.apply(origGroup);
+
+      assertEquals(newGroup.getId(), origGroup.getSecurityGroup().getId());
+      assertEquals(newGroup.getProviderId(), origGroup.getSecurityGroup().getId());
+      assertEquals(newGroup.getName(), origGroup.getSecurityGroup().getName());
+      assertEquals(newGroup.getOwnerId(), origGroup.getSecurityGroup().getTenantId());
+      assertEquals(newGroup.getIpPermissions(), ImmutableSet.copyOf(transform(origGroup.getSecurityGroup().getRules(), ruleConverter)));
+      assertEquals(newGroup.getLocation().getId(), origGroup.getZone());
+   }
+
+   @Test
+   public void testApplyWithCidr() {
+
+      NovaSecurityGroupInZoneToSecurityGroup parser = createGroupParser();
+
+      SecurityGroupInZone origGroup = new SecurityGroupInZone(securityGroupWithCidr(), zone.getId());
+
+      SecurityGroup newGroup = parser.apply(origGroup);
+
+      assertEquals(newGroup.getId(), origGroup.getSecurityGroup().getId());
+      assertEquals(newGroup.getProviderId(), origGroup.getSecurityGroup().getId());
+      assertEquals(newGroup.getName(), origGroup.getSecurityGroup().getName());
+      assertEquals(newGroup.getOwnerId(), origGroup.getSecurityGroup().getTenantId());
+      assertEquals(newGroup.getIpPermissions(), ImmutableSet.copyOf(transform(origGroup.getSecurityGroup().getRules(), ruleConverter)));
+      assertEquals(newGroup.getLocation().getId(), origGroup.getZone());
+   }
+
+   private NovaSecurityGroupInZoneToSecurityGroup createGroupParser() {
+      NovaSecurityGroupToSecurityGroup baseParser = new NovaSecurityGroupToSecurityGroup(ruleConverter);
+
+      NovaSecurityGroupInZoneToSecurityGroup parser = new NovaSecurityGroupInZoneToSecurityGroup(baseParser, locationIndex);
+
+      return parser;
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroupTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroupTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroupTest.java
index 8db1e15..16d01d3 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroupTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroupTest.java
@@ -18,19 +18,13 @@ package org.jclouds.openstack.nova.v2_0.compute.functions;
 
 import static com.google.common.collect.Iterables.transform;
 import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertTrue;
-
-import java.util.Set;
 
 import org.jclouds.compute.domain.SecurityGroup;
-import org.jclouds.compute.reference.ComputeServiceConstants;
-import org.jclouds.net.domain.IpPermission;
 import org.jclouds.net.domain.IpProtocol;
 import org.jclouds.openstack.nova.v2_0.domain.SecurityGroupRule;
 import org.jclouds.openstack.nova.v2_0.domain.TenantIdAndName;
 import org.testng.annotations.Test;
 
-import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableSet;
 
 /**
@@ -40,32 +34,59 @@ import com.google.common.collect.ImmutableSet;
 public class NovaSecurityGroupToSecurityGroupTest {
 
    private static final SecurityGroupRuleToIpPermission ruleConverter = new SecurityGroupRuleToIpPermission();
-   
-   @Test
-   public void testApplyWithGroup() {
+
+   public static org.jclouds.openstack.nova.v2_0.domain.SecurityGroup securityGroupWithGroup() {
       TenantIdAndName group = TenantIdAndName.builder().tenantId("tenant").name("name").build();
-      
+
+      SecurityGroupRule ruleToConvert = SecurityGroupRule.builder()
+              .id("some-id")
+              .ipProtocol(IpProtocol.TCP)
+              .fromPort(10)
+              .toPort(20)
+              .group(group)
+              .parentGroupId("some-other-id")
+              .build();
+
+      org.jclouds.openstack.nova.v2_0.domain.SecurityGroup origGroup = org.jclouds.openstack.nova.v2_0.domain.SecurityGroup.builder()
+              .tenantId("tenant")
+              .id("some-id")
+              .name("some-group")
+              .description("some-description")
+              .rules(ruleToConvert)
+              .build();
+
+      return origGroup;
+   }
+
+   public static org.jclouds.openstack.nova.v2_0.domain.SecurityGroup securityGroupWithCidr() {
       SecurityGroupRule ruleToConvert = SecurityGroupRule.builder()
-         .id("some-id")
-         .ipProtocol(IpProtocol.TCP)
-         .fromPort(10)
-         .toPort(20)
-         .group(group)
-         .parentGroupId("some-other-id")
-         .build();
+              .id("some-id")
+              .ipProtocol(IpProtocol.TCP)
+              .fromPort(10)
+              .toPort(20)
+              .ipRange("0.0.0.0/0")
+              .parentGroupId("some-other-id")
+              .build();
 
       org.jclouds.openstack.nova.v2_0.domain.SecurityGroup origGroup = org.jclouds.openstack.nova.v2_0.domain.SecurityGroup.builder()
-         .tenantId("tenant")
-         .id("some-id")
-         .name("some-group")
-         .description("some-description")
-         .rules(ruleToConvert)
-         .build();
+              .tenantId("tenant")
+              .id("some-id")
+              .name("some-group")
+              .description("some-description")
+              .rules(ruleToConvert)
+              .build();
+
+      return origGroup;
+   }
 
+   @Test
+   public void testApplyWithGroup() {
       NovaSecurityGroupToSecurityGroup parser = createGroupParser();
 
+      org.jclouds.openstack.nova.v2_0.domain.SecurityGroup origGroup = securityGroupWithGroup();
+
       SecurityGroup newGroup = parser.apply(origGroup);
-      
+
       assertEquals(newGroup.getId(), origGroup.getId());
       assertEquals(newGroup.getProviderId(), origGroup.getId());
       assertEquals(newGroup.getName(), origGroup.getName());
@@ -75,27 +96,13 @@ public class NovaSecurityGroupToSecurityGroupTest {
 
    @Test
    public void testApplyWithCidr() {
-      SecurityGroupRule ruleToConvert = SecurityGroupRule.builder()
-         .id("some-id")
-         .ipProtocol(IpProtocol.TCP)
-         .fromPort(10)
-         .toPort(20)
-         .ipRange("0.0.0.0/0")
-         .parentGroupId("some-other-id")
-         .build();
-
-      org.jclouds.openstack.nova.v2_0.domain.SecurityGroup origGroup = org.jclouds.openstack.nova.v2_0.domain.SecurityGroup.builder()
-         .tenantId("tenant")
-         .id("some-id")
-         .name("some-group")
-         .description("some-description")
-         .rules(ruleToConvert)
-         .build();
 
       NovaSecurityGroupToSecurityGroup parser = createGroupParser();
 
+      org.jclouds.openstack.nova.v2_0.domain.SecurityGroup origGroup = securityGroupWithCidr();
+
       SecurityGroup group = parser.apply(origGroup);
-      
+
       assertEquals(group.getId(), origGroup.getId());
       assertEquals(group.getProviderId(), origGroup.getId());
       assertEquals(group.getName(), origGroup.getName());

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicatesTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicatesTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicatesTest.java
index 521d7d5..26e5ce2 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicatesTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/predicates/SecurityGroupPredicatesTest.java
@@ -17,10 +17,22 @@
 package org.jclouds.openstack.nova.v2_0.predicates;
 
 import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.nameEquals;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.nameIn;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleCidr;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleEndPort;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleGroup;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleProtocol;
+import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.ruleStartPort;
+import static org.testng.Assert.assertTrue;
 
+import org.jclouds.net.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;
 import org.testng.annotations.Test;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * 
  * @author Adrian Cole
@@ -28,15 +40,90 @@ import org.testng.annotations.Test;
 @Test(groups = "unit", testName = "SecurityGroupPredicatesTest")
 public class SecurityGroupPredicatesTest {
    SecurityGroup ref = SecurityGroup.builder().id("12345").name("jclouds").description("description").build();
+   SecurityGroupRule ruleRef = SecurityGroupRule.builder().id("6789").parentGroupId("12345").ipRange("0.0.0.0/0")
+           .fromPort(10).toPort(20).ipProtocol(IpProtocol.TCP)
+           .group(TenantIdAndName.builder().tenantId("11111111").name("abcd").build())
+           .build();
 
-   @Test
    public void testnameEqualsWhenEqual() {
-      assert nameEquals("jclouds").apply(ref);
+      assertTrue(nameEquals("jclouds").apply(ref), "expected 'jclouds' as the name of " + ref);
    }
 
    @Test
    public void testnameEqualsWhenNotEqual() {
-      assert !nameEquals("foo").apply(ref);
+      assertTrue(!nameEquals("foo").apply(ref), "expected 'foo' not to be the name of " + ref);
+   }
+
+   @Test
+   public void testNameInWhenIn() {
+      assertTrue(nameIn(ImmutableSet.of("jclouds", "pants")).apply(ref),
+              "expected the name of " + ref + " to be one of 'jclouds' or 'pants'");
+   }
+
+   @Test
+   public void testNameInWhenNotIn() {
+      assertTrue(!nameIn(ImmutableSet.of("foo", "pants")).apply(ref),
+              "expected the name of " + ref + " to not be either of 'foo' or 'pants'");
+
+   }
+
+   @Test
+   public void testRuleCidrWhenEqual() {
+      assertTrue(ruleCidr("0.0.0.0/0").apply(ruleRef),
+              "expected the CIDR to be '0.0.0.0/0' for " + ruleRef);
+   }
+
+   @Test
+   public void testRuleCidrWhenNotEqual() {
+      assertTrue(!ruleCidr("1.1.1.1/0").apply(ruleRef),
+              "expected the CIDR to not be '1.1.1.1/0' for " + ruleRef);
+   }
+
+   @Test
+   public void testRuleGroupWhenEqual() {
+      assertTrue(ruleGroup("abcd").apply(ruleRef),
+              "expected the group to be equal to 'abcd' for " + ruleRef);
+   }
+
+   @Test
+   public void testRuleGroupWhenNotEqual() {
+      assertTrue(!ruleGroup("pants").apply(ruleRef),
+              "expected the group to not be equal to 'pants' for " + ruleRef);
+   }
+
+   @Test
+   public void testRuleProtocolWhenEqual() {
+      assertTrue(ruleProtocol(IpProtocol.TCP).apply(ruleRef),
+              "expected TCP for " + ruleRef);
    }
 
+   @Test
+   public void testRuleProtocolWhenNotEqual() {
+      assertTrue(!ruleProtocol(IpProtocol.UDP).apply(ruleRef),
+              "expected not UDP for " + ruleRef);
+   }
+
+   @Test
+   public void testRuleStartPortWhenEqual() {
+      assertTrue(ruleStartPort(10).apply(ruleRef),
+              "expected start port 10 for " + ruleRef);
+   }
+
+   @Test
+   public void testRuleStartPortWhenNotEqual() {
+      assertTrue(!ruleStartPort(50).apply(ruleRef),
+              "expected start port not to be 50 for " + ruleRef);
+   }
+
+   @Test
+   public void testRuleEndPortWhenEqual() {
+      assertTrue(ruleEndPort(20).apply(ruleRef),
+              "expected end port 20 for " + ruleRef);
+   }
+
+   @Test
+   public void testRuleEndPortWhenNotEqual() {
+      assertTrue(!ruleEndPort(50).apply(ruleRef),
+              "expected end port not to be 50 for " + ruleRef);
+   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/resources/securitygroup_details_extension.json
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/resources/securitygroup_details_extension.json b/apis/openstack-nova/src/test/resources/securitygroup_details_extension.json
new file mode 100644
index 0000000..a644296
--- /dev/null
+++ b/apis/openstack-nova/src/test/resources/securitygroup_details_extension.json
@@ -0,0 +1,34 @@
+{
+    "security_group":
+        {
+          "rules": [
+              {
+                "from_port": 22,
+                "group": {},
+                "ip_protocol": "tcp",
+                "to_port": 22,
+                "parent_group_id": 160,
+                "ip_range": {
+                    "cidr": "10.2.6.0/24"
+                 },
+                 "id": 108
+              },
+              {
+                 "from_port": 22,
+                 "group": {
+                     "tenant_id": "admin",
+                     "name": "11111"
+                  },
+                  "ip_protocol": "tcp",
+                  "to_port": 22,
+                  "parent_group_id": 160,
+                  "ip_range": {},
+                  "id": 109
+               }
+          ],
+          "tenant_id": "tenant0",
+          "id": 160,
+          "name": "name0",
+          "description": "description0"
+        }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/resources/securitygroup_details_extension_norules.json
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/resources/securitygroup_details_extension_norules.json b/apis/openstack-nova/src/test/resources/securitygroup_details_extension_norules.json
new file mode 100644
index 0000000..27aca88
--- /dev/null
+++ b/apis/openstack-nova/src/test/resources/securitygroup_details_extension_norules.json
@@ -0,0 +1,10 @@
+{
+    "security_group":
+        {
+          "rules": [],
+          "tenant_id": "tenant0",
+          "id": 160,
+          "name": "name0",
+          "description": "description0"
+        }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/resources/securitygroup_list_extension.json
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/resources/securitygroup_list_extension.json b/apis/openstack-nova/src/test/resources/securitygroup_list_extension.json
new file mode 100644
index 0000000..14866e3
--- /dev/null
+++ b/apis/openstack-nova/src/test/resources/securitygroup_list_extension.json
@@ -0,0 +1,51 @@
+{
+  "security_groups":[
+    {
+      "rules":[
+        {
+          "from_port":22,
+          "group":{
+
+          },
+          "ip_protocol":"tcp",
+          "to_port":22,
+          "parent_group_id":3,
+          "ip_range":{
+            "cidr":"0.0.0.0/0"
+          },
+          "id":107
+        },
+        {
+          "from_port":7600,
+          "group":{
+
+          },
+          "ip_protocol":"tcp",
+          "to_port":7600,
+          "parent_group_id":3,
+          "ip_range":{
+            "cidr":"0.0.0.0/0"
+          },
+          "id":118
+        },
+        {
+          "from_port":8084,
+          "group":{
+
+          },
+          "ip_protocol":"tcp",
+          "to_port":8084,
+          "parent_group_id":3,
+          "ip_range":{
+            "cidr":"0.0.0.0/0"
+          },
+          "id":119
+        }
+      ],
+      "tenant_id":"dev_16767499955063",
+      "id":160,
+      "name":"jclouds-test",
+      "description":"jclouds-test"
+    }
+  ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/resources/securitygrouprule_created_cidr.json
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/resources/securitygrouprule_created_cidr.json b/apis/openstack-nova/src/test/resources/securitygrouprule_created_cidr.json
new file mode 100644
index 0000000..cc91ac7
--- /dev/null
+++ b/apis/openstack-nova/src/test/resources/securitygrouprule_created_cidr.json
@@ -0,0 +1,13 @@
+{
+    "security_group_rule": {
+        "from_port": 22,
+        "group": {},
+        "ip_protocol": "tcp",
+        "to_port": 22,
+        "parent_group_id": 160,
+        "ip_range": {
+            "cidr": "10.2.6.0/24"
+        },
+        "id": 108
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/resources/securitygrouprule_created_group.json
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/resources/securitygrouprule_created_group.json b/apis/openstack-nova/src/test/resources/securitygrouprule_created_group.json
new file mode 100644
index 0000000..ff17bcd
--- /dev/null
+++ b/apis/openstack-nova/src/test/resources/securitygrouprule_created_group.json
@@ -0,0 +1,14 @@
+{
+    "security_group_rule": {
+        "from_port": 22,
+        "group": {
+            "tenant_id": "admin",
+            "name": "11111"
+        },
+        "ip_protocol": "tcp",
+        "to_port": 22,
+        "parent_group_id": 160,
+        "ip_range": {},
+        "id": 109
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/389ba6c9/apis/openstack-nova/src/test/resources/server_with_security_groups_extension.json
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/resources/server_with_security_groups_extension.json b/apis/openstack-nova/src/test/resources/server_with_security_groups_extension.json
new file mode 100644
index 0000000..7a3c4b2
--- /dev/null
+++ b/apis/openstack-nova/src/test/resources/server_with_security_groups_extension.json
@@ -0,0 +1 @@
+{"server": {"status": "ACTIVE", "updated": "2012-05-04T12:15:01Z", "hostId": "02c7c81e36024d2bfdb473cb762900138bc07777922479d3d4f8f690", "user_id": "1e8a56719e0d4ab4b7edb85c77f7290f", "name": "test", "links": [{"href": "http://172.16.89.148:8774/v2/4287930c796741aa898425f40832cb3c/servers/8d0a6ca5-8849-4b3d-b86e-f24c92490ebb", "rel": "self"}, {"href": "http://172.16.89.148:8774/4287930c796741aa898425f40832cb3c/servers/8d0a6ca5-8849-4b3d-b86e-f24c92490ebb", "rel": "bookmark"}], "created": "2012-05-04T12:14:57Z", "tenant_id": "4287930c796741aa898425f40832cb3c", "image": {"id": "ea17cc36-f7c9-40cd-b6bf-a952b74870f2", "links": [{"href": "http://172.16.89.148:8774/4287930c796741aa898425f40832cb3c/images/ea17cc36-f7c9-40cd-b6bf-a952b74870f2", "rel": "bookmark"}]}, "addresses": {"private": [{"version": 4, "addr": "10.0.0.8"}]}, "accessIPv4": "", "accessIPv6": "", "key_name": "", "progress": 0, "flavor": {"id": "1", "links": [{"href": "http://172.16.89.148:8774/4287930c796741aa898425f40832c
 b3c/flavors/1", "rel": "bookmark"}]}, "config_drive": "", "id": "8d0a6ca5-8849-4b3d-b86e-f24c92490ebb", "security_groups": [{"name": "name1"}], "metadata": {}}}
\ No newline at end of file