You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by an...@apache.org on 2018/02/02 15:31:33 UTC

[2/2] jclouds git commit: [JCLOUDS-1377] add support for injectable Neutron Context into Nova

[JCLOUDS-1377] add support for injectable Neutron Context into Nova

- fix NovaComputeServiceExpectTest
- fix NovaComputeServiceExpectTest
- fix CreateSecurityGroupIfNeededTest
- fix FindSecurityGroupInRegionOrCreateTest
- fix checkstyle
- fix removal from security group cache
- fix listSecurityGroupsForNode
- change both Nova and Neutron listSecurityGroupsForNode to use NovaApi.listSecurityGroupForServer


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

Branch: refs/heads/master
Commit: 09936b57fc90b5a3c7fe530358a2c6a757c32839
Parents: 2a56db0
Author: Andrea Turli <an...@gmail.com>
Authored: Wed Jan 17 11:46:55 2018 +0000
Committer: Andrea Turli <an...@gmail.com>
Committed: Fri Feb 2 16:26:22 2018 +0100

----------------------------------------------------------------------
 apis/openstack-nova/pom.xml                     |   5 +
 .../openstack/nova/v2_0/NovaApiMetadata.java    |   2 +
 .../v2_0/compute/NovaComputeServiceAdapter.java |   2 +-
 .../config/NovaComputeServiceContextModule.java |  60 ++--
 .../NeutronSecurityGroupExtension.java          | 325 +++++++++++++++++++
 .../extensions/NovaSecurityGroupExtension.java  |  40 +--
 .../compute/functions/CleanupResources.java     |  34 +-
 .../functions/CreateSecurityGroupIfNeeded.java  | 138 ++++++--
 .../NeutronSecurityGroupToSecurityGroup.java    |  85 +++++
 .../NovaSecurityGroupToSecurityGroup.java       |  97 ++++++
 .../loaders/FindSecurityGroupOrCreate.java      |  35 +-
 .../compute/options/NovaTemplateOptions.java    |   1 +
 ...desWithGroupEncodedIntoNameThenAddToSet.java |  61 ++--
 .../NeutronSecurityGroupInRegion.java           |  80 +++++
 .../openstack/nova/v2_0/features/ServerApi.java |  22 +-
 .../NovaComputeServiceAdapterExpectTest.java    |  14 +-
 .../compute/NovaComputeServiceExpectTest.java   |   6 +-
 .../compute/NovaComputeServiceLiveTest.java     |  21 +-
 .../NeutronSecurityGroupExtensionLiveTest.java  | 117 +++++++
 .../NovaSecurityGroupExtensionExpectTest.java   |   3 +-
 .../NovaSecurityGroupExtensionLiveTest.java     |   1 -
 .../FindSecurityGroupInRegionOrCreateTest.java  | 131 ++++++++
 .../loaders/FindSecurityGroupOrCreateTest.java  | 141 --------
 .../nova/v2_0/features/ServerApiExpectTest.java |  33 ++
 .../nova/v2_0/features/ServerApiLiveTest.java   |  21 +-
 .../CreateSecurityGroupIfNeededTest.java        | 112 ++++++-
 ...eComputeServiceTypicalSecurityGroupTest.java |  24 +-
 .../src/test/resources/image_list_detail.json   |  27 ++
 .../resources/image_list_detail_openstack.json  |  27 ++
 .../src/test/resources/logback-test.xml         |   2 +-
 .../BaseSecurityGroupExtensionLiveTest.java     |   5 +-
 31 files changed, 1322 insertions(+), 350 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/pom.xml
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/pom.xml b/apis/openstack-nova/pom.xml
index 85a9e28..8e90a0b 100644
--- a/apis/openstack-nova/pom.xml
+++ b/apis/openstack-nova/pom.xml
@@ -53,6 +53,11 @@
       <artifactId>openstack-keystone</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds.api</groupId>
+      <artifactId>openstack-neutron</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <!-- for the extension namespaces -->
     <dependency>
       <groupId>com.google.inject.extensions</groupId>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java
index a330afa..2ee56a7 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/NovaApiMetadata.java
@@ -17,6 +17,7 @@
 package org.jclouds.openstack.nova.v2_0;
 
 import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
+import static org.jclouds.compute.config.ComputeServiceProperties.TEMPLATE;
 import static org.jclouds.openstack.keystone.config.KeystoneProperties.CREDENTIAL_TYPE;
 import static org.jclouds.openstack.keystone.config.KeystoneProperties.KEYSTONE_VERSION;
 import static org.jclouds.openstack.keystone.config.KeystoneProperties.SERVICE_TYPE;
@@ -78,6 +79,7 @@ public class NovaApiMetadata extends BaseHttpApiMetadata<NovaApi>  {
       // before expiry by default.  We choose a value less than the latter
       // since the former persists between jclouds invocations.
       properties.setProperty(PROPERTY_SESSION_INTERVAL, 30 * 60 + "");
+      properties.put(TEMPLATE, "osFamily=UBUNTU,os64Bit=true,osVersionMatches=16.*");
       return properties;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java
index 02cfa2b..b2e18c8 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapter.java
@@ -140,7 +140,7 @@ public class NovaComputeServiceAdapter implements
          logger.warn(message);
          String tagString = metadataAndTagsAsCommaDelimitedValue.get("jclouds_tags");
          Set<String> tags = Sets.newHashSet(Splitter.on(',').split(tagString));
-         cleanupResources.removeSecurityGroupCreatedByJcloudsAndInvalidateCache(regionId, tags);
+         cleanupResources.removeSecurityGroupCreatedByJcloudsAndInvalidateCache(tags);
          throw new IllegalStateException(message);
       }
       logger.trace("<< server(%s)", lightweightServer.getId());

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/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 700a39b..4402d47 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
@@ -17,21 +17,22 @@
 package org.jclouds.openstack.nova.v2_0.compute.config;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
 import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
 import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.AUTO_ALLOCATE_FLOATING_IPS;
 import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.AUTO_GENERATE_KEYPAIRS;
-import static org.jclouds.openstack.nova.v2_0.config.NovaProperties.TIMEOUT_SECURITYGROUP_PRESENT;
 import static org.jclouds.util.Predicates2.retry;
 
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
 
 import javax.inject.Named;
 import javax.inject.Singleton;
 
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+import org.jclouds.Context;
 import org.jclouds.collect.Memoized;
 import org.jclouds.compute.ComputeService;
 import org.jclouds.compute.ComputeServiceAdapter;
@@ -53,6 +54,7 @@ import org.jclouds.functions.IdentityFunction;
 import org.jclouds.openstack.nova.v2_0.NovaApi;
 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.NeutronSecurityGroupExtension;
 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.CleanupResources;
@@ -60,7 +62,9 @@ import org.jclouds.openstack.nova.v2_0.compute.functions.CreateSecurityGroupIfNe
 import org.jclouds.openstack.nova.v2_0.compute.functions.FlavorInRegionToHardware;
 import org.jclouds.openstack.nova.v2_0.compute.functions.ImageInRegionToImage;
 import org.jclouds.openstack.nova.v2_0.compute.functions.ImageToOperatingSystem;
+import org.jclouds.openstack.nova.v2_0.compute.functions.NeutronSecurityGroupToSecurityGroup;
 import org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupInRegionToSecurityGroup;
+import org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupToSecurityGroup;
 import org.jclouds.openstack.nova.v2_0.compute.functions.OrphanedGroupsByRegionId;
 import org.jclouds.openstack.nova.v2_0.compute.functions.ServerInRegionToNodeMetadata;
 import org.jclouds.openstack.nova.v2_0.compute.loaders.FindSecurityGroupOrCreate;
@@ -77,7 +81,6 @@ import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.ServerInRegion;
-import org.jclouds.openstack.nova.v2_0.predicates.FindSecurityGroupWithNameAndReturnTrue;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
@@ -137,10 +140,10 @@ public class NovaComputeServiceContextModule extends
       bind(new TypeLiteral<CacheLoader<RegionAndId, Iterable<? extends FloatingIP>>>() {
       }).annotatedWith(Names.named("FLOATINGIP")).to(LoadFloatingIpsForInstance.class);
 
-      bind(new TypeLiteral<Function<RegionSecurityGroupNameAndPorts, SecurityGroupInRegion>>() {
+      bind(new TypeLiteral<Function<RegionSecurityGroupNameAndPorts, SecurityGroup>>() {
       }).to(CreateSecurityGroupIfNeeded.class);
 
-      bind(new TypeLiteral<CacheLoader<RegionAndName, SecurityGroupInRegion>>() {
+      bind(new TypeLiteral<CacheLoader<RegionAndName, SecurityGroup>>() {
       }).to(FindSecurityGroupOrCreate.class);
 
       bind(CreateNodesWithGroupEncodedIntoNameThenAddToSet.class).to(
@@ -149,11 +152,37 @@ public class NovaComputeServiceContextModule extends
       bind(new TypeLiteral<ImageExtension>() {
       }).to(NovaImageExtension.class);
 
-      bind(new TypeLiteral<SecurityGroupExtension>() {
-      }).to(NovaSecurityGroupExtension.class);
-
       bind(new TypeLiteral<Function<NodeMetadata, Boolean>>() {
       }).to(CleanupResources.class);
+
+      install(new FactoryModuleBuilder().build(NeutronSecurityGroupToSecurityGroup.Factory.class));
+      install(new FactoryModuleBuilder().build(NovaSecurityGroupToSecurityGroup.Factory.class));
+
+      bind(new TypeLiteral<SecurityGroupExtension>() {
+      }).toProvider(SecurityGroupExtensionProvider.class);
+
+   }
+
+   @Singleton
+   public static class SecurityGroupExtensionProvider implements Provider<SecurityGroupExtension> {
+      @Inject(optional = true)
+      @Named("openstack-neutron")
+      protected Supplier<Context> neutronApiContextSupplier;
+
+      private final NeutronSecurityGroupExtension neutronSecurityGroupExtension;
+      private final NovaSecurityGroupExtension novaSecurityGroupExtension;
+
+      @Inject
+      SecurityGroupExtensionProvider(NeutronSecurityGroupExtension neutronSecurityGroupExtension,
+                                            NovaSecurityGroupExtension novaSecurityGroupExtension) {
+         this.neutronSecurityGroupExtension = neutronSecurityGroupExtension;
+         this.novaSecurityGroupExtension = novaSecurityGroupExtension;
+      }
+
+      @Override
+      public SecurityGroupExtension get() {
+         return neutronApiContextSupplier != null ? neutronSecurityGroupExtension : novaSecurityGroupExtension;
+      }
    }
 
    @Override
@@ -192,8 +221,8 @@ public class NovaComputeServiceContextModule extends
 
    @Provides
    @Singleton
-   protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap(
-            CacheLoader<RegionAndName, SecurityGroupInRegion> in) {
+   protected final LoadingCache<RegionAndName, SecurityGroup> securityGroupMap(
+           CacheLoader<RegionAndName, SecurityGroup> in) {
       return CacheBuilder.newBuilder().build(in);
    }
 
@@ -205,15 +234,6 @@ public class NovaComputeServiceContextModule extends
 
    @Provides
    @Singleton
-   @Named("SECURITYGROUP_PRESENT")
-   protected final Predicate<AtomicReference<RegionAndName>> securityGroupEventualConsistencyDelay(
-            FindSecurityGroupWithNameAndReturnTrue in,
-            @Named(TIMEOUT_SECURITYGROUP_PRESENT) long msDelay) {
-      return retry(in, msDelay, 100L, MILLISECONDS);
-   }
-
-   @Provides
-   @Singleton
    protected final Supplier<Map<String, Location>> createLocationIndexedById(
             @Memoized Supplier<Set<? extends Location>> locations) {
       return Suppliers.compose(new Function<Set<? extends Location>, Map<String, Location>>() {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NeutronSecurityGroupExtension.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NeutronSecurityGroupExtension.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NeutronSecurityGroupExtension.java
new file mode 100644
index 0000000..dae8af7
--- /dev/null
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/extensions/NeutronSecurityGroupExtension.java
@@ -0,0 +1,325 @@
+/*
+ * 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.notNull;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+
+import org.jclouds.Context;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Location;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.location.Region;
+import org.jclouds.logging.Logger;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+import org.jclouds.openstack.neutron.v2.NeutronApi;
+import org.jclouds.openstack.neutron.v2.domain.Rule;
+import org.jclouds.openstack.neutron.v2.domain.RuleDirection;
+import org.jclouds.openstack.neutron.v2.domain.RuleEthertype;
+import org.jclouds.openstack.neutron.v2.domain.RuleProtocol;
+import org.jclouds.openstack.neutron.v2.features.SecurityGroupApi;
+import org.jclouds.openstack.nova.v2_0.NovaApi;
+import org.jclouds.openstack.nova.v2_0.compute.functions.NeutronSecurityGroupToSecurityGroup;
+import org.jclouds.openstack.nova.v2_0.compute.functions.NovaSecurityGroupToSecurityGroup;
+import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
+import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
+import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts;
+import org.jclouds.rest.ApiContext;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.inject.Inject;
+
+/**
+ * An extension to compute service to allow for the manipulation of {@link org.jclouds.compute.domain.SecurityGroup}s. Implementation
+ * is optional by providers.
+ */
+public class NeutronSecurityGroupExtension implements SecurityGroupExtension {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   private final NovaApi api;
+   private final Supplier<Set<String>> regionIds;
+   private final GroupNamingConvention.Factory namingConvention;
+   private final LoadingCache<RegionAndName, SecurityGroup> groupCreator;
+   private final Supplier<Map<String, Location>> locationIndex;
+   private final NeutronSecurityGroupToSecurityGroup.Factory neutronSecurityGroupToSecurityGroup;
+   private final NovaSecurityGroupToSecurityGroup.Factory novaSecurityGroupToSecurityGroup;
+
+   @Inject(optional = true)
+   @Named("openstack-neutron")
+   private Supplier<Context> neutronContextSupplier;
+
+   @Inject
+   NeutronSecurityGroupExtension(NovaApi api,
+                                 @Region Supplier<Set<String>> regionIds,
+                                 GroupNamingConvention.Factory namingConvention, LoadingCache<RegionAndName, SecurityGroup> groupCreator,
+                                 Supplier<Map<String, Location>> locationIndex,
+                                 NeutronSecurityGroupToSecurityGroup.Factory neutronSecurityGroupToSecurityGroup,
+                                 NovaSecurityGroupToSecurityGroup.Factory novaSecurityGroupToSecurityGroup) {
+      this.api = api;
+      this.regionIds = checkNotNull(regionIds, "regionIds");
+      this.namingConvention = checkNotNull(namingConvention, "namingConvention");
+      this.groupCreator = groupCreator;
+      this.locationIndex = locationIndex;
+      this.neutronSecurityGroupToSecurityGroup = neutronSecurityGroupToSecurityGroup;
+      this.novaSecurityGroupToSecurityGroup = novaSecurityGroupToSecurityGroup;
+   }
+
+   @Override
+   public SecurityGroup createSecurityGroup(String name, Location location) {
+      String region = location.getId();
+      if (region == null) {
+         return null;
+      }
+      logger.debug(">> creating security group %s in %s...", name, location);
+
+      String markerGroup = namingConvention.create().sharedNameForGroup(name);
+      RegionSecurityGroupNameAndPorts regionAndName = new RegionSecurityGroupNameAndPorts(region, markerGroup, ImmutableSet.<Integer> of());
+      return groupCreator.getUnchecked(regionAndName);
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroups() {
+      Set<SecurityGroup> securityGroups = Sets.newHashSet();
+
+      for (String regionId : regionIds.get()) {
+         Location location = locationIndex.get().get(regionId);
+         securityGroups.addAll(listSecurityGroupsInLocation(location));
+      }
+      return ImmutableSet.copyOf(securityGroups);
+   }
+
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroupsInLocation(final Location location) {
+      String region = location.getId();
+      if (region == null) {
+         return ImmutableSet.of();
+      }
+      return getSecurityGroupApi(region).listSecurityGroups().concat().transform(neutronSecurityGroupToSecurityGroup.create(location)).toSet();
+   }
+
+   @Override
+   public Set<SecurityGroup> listSecurityGroupsForNode(String id) {
+      RegionAndId regionAndId = RegionAndId.fromSlashEncoded(checkNotNull(id, "id"));
+      String region = regionAndId.getRegion();
+      Location location = locationIndex.get().get(region);
+      Set<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup> allGroups = api.getServerApi(region).listSecurityGroupForServer(regionAndId.getId());
+      return ImmutableSet.copyOf(transform(filter(allGroups, notNull()), novaSecurityGroupToSecurityGroup.create(location)));
+   }
+
+   @Override
+   public SecurityGroup getSecurityGroupById(String id) {
+      RegionAndId regionAndId = RegionAndId.fromSlashEncoded(checkNotNull(id, "id"));
+      String region = regionAndId.getRegion();
+      String groupId = regionAndId.getId();
+
+      SecurityGroupApi securityGroupApi = getSecurityGroupApi(region);
+
+      Location location = locationIndex.get().get(region);
+      return neutronSecurityGroupToSecurityGroup.create(location).apply(securityGroupApi.getSecurityGroup(groupId));
+   }
+
+   @Override
+   public boolean removeSecurityGroup(String id) {
+      checkNotNull(id, "id");
+      RegionAndId regionAndId = RegionAndId.fromSlashEncoded(id);
+      String region = regionAndId.getRegion();
+      String groupId = regionAndId.getId();
+
+      SecurityGroupApi securityGroupApi = getSecurityGroupApi(region);
+
+      // Would be nice to delete the group and invalidate the cache atomically - i.e. use a mutex.
+      // Will make sure that a create operation in parallel won't see inconsistent state.
+
+      boolean deleted = securityGroupApi.deleteSecurityGroup(groupId);
+
+      for (SecurityGroup cachedSg : groupCreator.asMap().values()) {
+         if (id.equals(cachedSg.getId())) {
+            String groupName = cachedSg.getName();
+            groupCreator.invalidate(new RegionSecurityGroupNameAndPorts(region, groupName, ImmutableSet.<Integer>of()));
+            break;
+         }
+      }
+
+      return deleted;
+   }
+
+   @Override
+   public SecurityGroup addIpPermission(IpPermission ipPermission, SecurityGroup group) {
+      String region = group.getLocation().getId();
+      RegionAndId groupRegionAndId = RegionAndId.fromSlashEncoded(group.getId());
+      String id = groupRegionAndId.getId();
+      SecurityGroupApi securityGroupApi = getSecurityGroupApi(region);
+
+      if (!ipPermission.getCidrBlocks().isEmpty()) {
+         for (String cidr : ipPermission.getCidrBlocks()) {
+            securityGroupApi.create(Rule.CreateRule.createBuilder(RuleDirection.INGRESS, group.getProviderId())
+                    .protocol(RuleProtocol.fromValue(ipPermission.getIpProtocol().name()))
+                    .ethertype(RuleEthertype.IPV4)
+                    .portRangeMin(ipPermission.getFromPort())
+                    .portRangeMax(ipPermission.getToPort())
+                    .remoteIpPrefix(cidr)
+                    .build());
+         }
+      }
+
+      if (!ipPermission.getGroupIds().isEmpty()) {
+         for (String regionAndGroupRaw : ipPermission.getGroupIds()) {
+            RegionAndId regionAndId = RegionAndId.fromSlashEncoded(regionAndGroupRaw);
+            String groupId = regionAndId.getId();
+            securityGroupApi.create(Rule.CreateRule.createBuilder(RuleDirection.INGRESS, groupId)
+                    .protocol(RuleProtocol.fromValue(ipPermission.getIpProtocol().name()))
+                    .ethertype(RuleEthertype.IPV4)
+                    .portRangeMin(ipPermission.getFromPort())
+                    .portRangeMax(ipPermission.getToPort())
+                    .remoteGroupId(groupId)
+                    .build());
+         }
+      }
+
+      return getSecurityGroupById(RegionAndId.fromRegionAndId(region, id).slashEncode());
+   }
+
+   @Override
+   public SecurityGroup addIpPermission(IpProtocol protocol, int startPort, int endPort,
+                                        Multimap<String, String> tenantIdGroupNamePairs,
+                                        Iterable<String> ipRanges,
+                                        Iterable<String> groupIds, SecurityGroup group) {
+      IpPermission.Builder permBuilder = IpPermission.builder();
+      permBuilder.ipProtocol(protocol);
+      permBuilder.fromPort(startPort);
+      permBuilder.toPort(endPort);
+      permBuilder.tenantIdGroupNamePairs(tenantIdGroupNamePairs);
+      permBuilder.cidrBlocks(ipRanges);
+      permBuilder.groupIds(groupIds);
+
+      return addIpPermission(permBuilder.build(), group);
+   }
+
+   @Override
+   public SecurityGroup removeIpPermission(final IpPermission ipPermission, SecurityGroup group) {
+      String region = group.getLocation().getId();
+      RegionAndId groupRegionAndId = RegionAndId.fromSlashEncoded(group.getId());
+      String id = groupRegionAndId.getId();
+
+      SecurityGroupApi securityGroupApi = getSecurityGroupApi(region);
+
+      org.jclouds.openstack.neutron.v2.domain.SecurityGroup securityGroup = securityGroupApi.getSecurityGroup(id);
+
+      if (!ipPermission.getCidrBlocks().isEmpty()) {
+         for (final String cidr : ipPermission.getCidrBlocks()) {
+            for (Rule rule : filter(securityGroup.getRules(),
+                    new Predicate<Rule>() {
+                       @Override
+                       public boolean apply(@Nullable Rule input) {
+                          return input.getRemoteIpPrefix() != null && input.getRemoteIpPrefix().equals(cidr) &&
+                                 input.getProtocol() != null && input.getProtocol().name().equals(ipPermission.getIpProtocol().name()) &&
+                                 input.getPortRangeMin() != null && input.getPortRangeMin() == ipPermission.getFromPort() &&
+                                 input.getPortRangeMax() != null && input.getPortRangeMax() == ipPermission.getToPort();
+                       }
+                    })) {
+               securityGroupApi.deleteRule(rule.getId());
+            }
+         }
+      }
+
+      if (!ipPermission.getGroupIds().isEmpty()) {
+         for (final String groupId : ipPermission.getGroupIds()) {
+            for (Rule rule : filter(securityGroup.getRules(),
+                    new Predicate<Rule>() {
+                       @Override
+                       public boolean apply(@Nullable Rule input) {
+                          return input.getRemoteGroupId() != null && input.getRemoteGroupId().equals(groupId) &&
+                                 input.getProtocol() != null && input.getProtocol().name().equals(ipPermission.getIpProtocol().name()) &&
+                                 input.getPortRangeMin() != null && input.getPortRangeMin() == ipPermission.getFromPort() &&
+                                 input.getPortRangeMax() != null && input.getPortRangeMax() == ipPermission.getToPort();
+                       }
+                    })) {
+               securityGroupApi.deleteRule(rule.getId());
+            }
+         }
+      }
+
+      return getSecurityGroupById(RegionAndId.fromRegionAndId(region, id).slashEncode());
+   }
+
+   @Override
+   public SecurityGroup removeIpPermission(IpProtocol protocol, int startPort, int endPort,
+                                           Multimap<String, String> tenantIdGroupNamePairs,
+                                           Iterable<String> ipRanges,
+                                           Iterable<String> groupIds, SecurityGroup group) {
+      IpPermission.Builder permBuilder = IpPermission.builder();
+      permBuilder.ipProtocol(protocol);
+      permBuilder.fromPort(startPort);
+      permBuilder.toPort(endPort);
+      permBuilder.tenantIdGroupNamePairs(tenantIdGroupNamePairs);
+      permBuilder.cidrBlocks(ipRanges);
+      permBuilder.groupIds(groupIds);
+
+      return removeIpPermission(permBuilder.build(), group);
+   }
+
+   @Override
+   public boolean supportsTenantIdGroupNamePairs() {
+      return false;
+   }
+
+   @Override
+   public boolean supportsTenantIdGroupIdPairs() {
+      return false;
+   }
+
+   @Override
+   public boolean supportsGroupIds() {
+      return true;
+   }
+
+   @Override
+   public boolean supportsPortRangesForGroups() {
+      return false;
+   }
+
+   @Override
+   public boolean supportsExclusionCidrBlocks() {
+      return false;
+   }
+
+   private SecurityGroupApi getSecurityGroupApi(String region) {
+      return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getSecurityGroupApi(region);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/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
index 4f992b9..153a406 100644
--- 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
@@ -16,14 +16,12 @@
  */
 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;
@@ -32,9 +30,9 @@ import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates
 
 import java.util.Set;
 
-import javax.inject.Inject;
 import javax.inject.Named;
 
+import com.google.inject.Inject;
 import org.jclouds.Constants;
 import org.jclouds.compute.domain.SecurityGroup;
 import org.jclouds.compute.extensions.SecurityGroupExtension;
@@ -46,13 +44,11 @@ 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.regionscoped.RegionAndId;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
 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;
@@ -73,7 +69,7 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension {
    protected final ListeningExecutorService userExecutor;
    protected final Supplier<Set<String>> regionIds;
    protected final Function<SecurityGroupInRegion, SecurityGroup> groupConverter;
-   protected final LoadingCache<RegionAndName, SecurityGroupInRegion> groupCreator;
+   protected final LoadingCache<RegionAndName, SecurityGroup> groupCreator;
    protected final GroupNamingConvention.Factory namingConvention;
 
    @Inject
@@ -81,7 +77,7 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension {
                                     @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
                                     @Region Supplier<Set<String>> regionIds,
                                     Function<SecurityGroupInRegion, SecurityGroup> groupConverter,
-                                    LoadingCache<RegionAndName, SecurityGroupInRegion> groupCreator,
+                                    LoadingCache<RegionAndName, SecurityGroup> groupCreator,
                                     GroupNamingConvention.Factory namingConvention) {
 
       this.api = checkNotNull(api, "api");
@@ -121,25 +117,9 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension {
    public Set<SecurityGroup> listSecurityGroupsForNode(String id) {
       RegionAndId regionAndId = RegionAndId.fromSlashEncoded(checkNotNull(id, "id"));
       String region = regionAndId.getRegion();
-      String instanceId = regionAndId.getId();
-
-      Optional<? extends ServerWithSecurityGroupsApi> serverApi = api.getServerWithSecurityGroupsApi(region);
-      Optional<? extends SecurityGroupApi> sgApi = api.getSecurityGroupApi(region);
-
-      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();
-       final FluentIterable<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup> allGroups = sgApi.get().list();
-       Set<? extends SecurityGroupInRegion> rawGroups =
-              allGroups.filter(nameIn(groupNames)).transform(groupToGroupInRegion(allGroups, region)).toSet();
-
+      Set<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup> allGroups = api.getServerApi(region).listSecurityGroupForServer(regionAndId.getId());
+      Set<? extends SecurityGroupInRegion> rawGroups =
+              FluentIterable.from(allGroups).transform(groupToGroupInRegion(allGroups, region)).toSet();
       return ImmutableSet.copyOf(transform(filter(rawGroups, notNull()), groupConverter));
    }
 
@@ -174,8 +154,8 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension {
       String markerGroup = namingConvention.create().sharedNameForGroup(name);
       RegionSecurityGroupNameAndPorts regionAndName = new RegionSecurityGroupNameAndPorts(region, markerGroup, ImmutableSet.<Integer> of());
 
-      SecurityGroupInRegion rawGroup = groupCreator.getUnchecked(regionAndName);
-      return groupConverter.apply(rawGroup);
+      SecurityGroup rawGroup = groupCreator.getUnchecked(regionAndName);
+      return rawGroup;
    }
 
    @Override
@@ -196,8 +176,8 @@ public class NovaSecurityGroupExtension implements SecurityGroupExtension {
 
       boolean deleted = sgApi.get().delete(groupId);
 
-      for (SecurityGroupInRegion cachedSg : groupCreator.asMap().values()) {
-         if (groupId.equals(cachedSg.getSecurityGroup().getId())) {
+      for (SecurityGroup cachedSg : groupCreator.asMap().values()) {
+         if (id.equals(cachedSg.getId())) {
             String groupName = cachedSg.getName();
             groupCreator.invalidate(new RegionSecurityGroupNameAndPorts(region, groupName, ImmutableSet.<Integer>of()));
             break;

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java
index 9030395..5da0b87 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CleanupResources.java
@@ -27,13 +27,12 @@ import javax.inject.Named;
 import javax.inject.Singleton;
 
 import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.logging.Logger;
-import org.jclouds.openstack.nova.v2_0.NovaApi;
-import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
 
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
@@ -47,38 +46,29 @@ public class CleanupResources implements Function<NodeMetadata, Boolean> {
    @Named(ComputeServiceConstants.COMPUTE_LOGGER)
    protected Logger logger = Logger.NULL;
 
-   protected final NovaApi novaApi;
    protected final RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate;
-   protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap;
+   protected final LoadingCache<RegionAndName, SecurityGroup> securityGroupMap;
 
-   @Inject
-   public CleanupResources(NovaApi novaApi, RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate,
-                           LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupMap) {
+   private final SecurityGroupExtension securityGroupExtension;
 
-      this.novaApi = novaApi;
+   @Inject
+   public CleanupResources(RemoveFloatingIpFromNodeAndDeallocate removeFloatingIpFromNodeAndDeallocate,
+         LoadingCache<RegionAndName, SecurityGroup> securityGroupMap, SecurityGroupExtension securityGroupExtension) {
       this.removeFloatingIpFromNodeAndDeallocate = removeFloatingIpFromNodeAndDeallocate;
       this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap");
+      this.securityGroupExtension = securityGroupExtension;
    }
 
    @Override
    public Boolean apply(NodeMetadata node) {
       final RegionAndId regionAndId = RegionAndId.fromSlashEncoded(node.getId());
       removeFloatingIpFromNodeifAny(regionAndId);
-      return removeSecurityGroupCreatedByJcloudsAndInvalidateCache(regionAndId.getRegion(), node.getTags());
+      return removeSecurityGroupCreatedByJcloudsAndInvalidateCache(node.getTags());
    }
 
-   public boolean removeSecurityGroupCreatedByJcloudsAndInvalidateCache(String regionId, Set<String> tags) {
+   public boolean removeSecurityGroupCreatedByJcloudsAndInvalidateCache(Set<String> tags) {
       String securityGroupIdCreatedByJclouds = getSecurityGroupIdCreatedByJclouds(tags);
-      if (securityGroupIdCreatedByJclouds != null) {
-         SecurityGroup securityGroup = novaApi.getSecurityGroupApi(regionId).get().get(securityGroupIdCreatedByJclouds);
-         RegionAndName regionAndName = RegionAndName.fromRegionAndName(regionId, securityGroup.getName());
-         logger.debug(">> deleting securityGroup(%s)", regionAndName);
-         novaApi.getSecurityGroupApi(regionId).get().delete(securityGroupIdCreatedByJclouds);
-         securityGroupMap.invalidate(regionAndName);
-         logger.debug("<< deleted securityGroup(%s)", regionAndName);
-         return true;
-      }
-      return false;
+      return securityGroupExtension.removeSecurityGroup(securityGroupIdCreatedByJclouds);
    }
 
    private void removeFloatingIpFromNodeifAny(RegionAndId regionAndId) {
@@ -88,7 +78,7 @@ public class CleanupResources implements Function<NodeMetadata, Boolean> {
          logger.warn(e, "<< error removing and deallocating ip from node(%s): %s", regionAndId, e.getMessage());
       }
    }
-   
+
    private String getSecurityGroupIdCreatedByJclouds(Set<String> tags) {
       return FluentIterable.from(tags).filter(new Predicate<String>() {
          @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java
index deea2e9..3faa490 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/CreateSecurityGroupIfNeeded.java
@@ -17,73 +17,153 @@
 package org.jclouds.openstack.nova.v2_0.compute.functions;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.Iterables.find;
 import static org.jclouds.openstack.nova.v2_0.predicates.SecurityGroupPredicates.nameEquals;
 
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
 import javax.annotation.Resource;
-import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
+import com.google.common.annotations.VisibleForTesting;
+import org.jclouds.Context;
+import org.jclouds.compute.domain.SecurityGroup;
 import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.domain.Location;
 import org.jclouds.logging.Logger;
 import org.jclouds.net.domain.IpProtocol;
+import org.jclouds.openstack.neutron.v2.NeutronApi;
+import org.jclouds.openstack.neutron.v2.domain.Rule;
+import org.jclouds.openstack.neutron.v2.domain.RuleDirection;
+import org.jclouds.openstack.neutron.v2.domain.RuleProtocol;
+import org.jclouds.openstack.neutron.v2.features.SecurityGroupApi;
 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.SecurityGroup;
+import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndId;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
-import org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi;
+import org.jclouds.rest.ApiContext;
 
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
 import com.google.common.collect.FluentIterable;
+import com.google.inject.Inject;
 
 @Singleton
-public class CreateSecurityGroupIfNeeded implements Function<RegionSecurityGroupNameAndPorts, SecurityGroupInRegion> {
+public class CreateSecurityGroupIfNeeded implements Function<RegionSecurityGroupNameAndPorts, SecurityGroup> {
    @Resource
    @Named(ComputeServiceConstants.COMPUTE_LOGGER)
    protected Logger logger = Logger.NULL;
+
    protected final NovaApi novaApi;
+   private final Supplier<Map<String, Location>> locationIndex;
+   private final Function<SecurityGroupInRegion, SecurityGroup> securityGroupInRegionSecurityGroupFunction;
+   private final NeutronSecurityGroupToSecurityGroup.Factory neutronSecurityGroupToSecurityGroup;
+
+   @Inject(optional = true)
+   @Named("openstack-neutron")
+   Supplier<Context> neutronContextSupplier;
 
    @Inject
-   public CreateSecurityGroupIfNeeded(NovaApi novaApi) {
-      this.novaApi = checkNotNull(novaApi, "novaApi");
+   @VisibleForTesting
+   public CreateSecurityGroupIfNeeded(NovaApi novaApi, Supplier<Map<String, Location>> locationIndex,
+                               Function<SecurityGroupInRegion, SecurityGroup> securityGroupInRegionSecurityGroupFunction,
+                               NeutronSecurityGroupToSecurityGroup.Factory neutronSecurityGroupToSecurityGroup) {
+      this.novaApi = novaApi;
+      this.locationIndex = locationIndex;
+      this.securityGroupInRegionSecurityGroupFunction = securityGroupInRegionSecurityGroupFunction;
+      this.neutronSecurityGroupToSecurityGroup = neutronSecurityGroupToSecurityGroup;
    }
 
    @Override
-   public SecurityGroupInRegion apply(RegionSecurityGroupNameAndPorts regionSecurityGroupNameAndPorts) {
-      checkNotNull(regionSecurityGroupNameAndPorts, "regionSecurityGroupNameAndPorts");
-
+   public SecurityGroup apply(final RegionSecurityGroupNameAndPorts regionSecurityGroupNameAndPorts) {
       String regionId = regionSecurityGroupNameAndPorts.getRegion();
-      Optional<? extends SecurityGroupApi> api = novaApi.getSecurityGroupApi(regionId);
-      checkArgument(api.isPresent(), "Security groups are required, but the extension is not available in region %s!", regionId);
-      final FluentIterable<SecurityGroup> allGroups = api.get().list();
+      Location location = locationIndex.get().get(regionId);
+
       logger.debug(">> creating securityGroup %s", regionSecurityGroupNameAndPorts);
-      try {
-         SecurityGroup securityGroup = api.get().createWithDescription(
-            regionSecurityGroupNameAndPorts.getName(), regionSecurityGroupNameAndPorts.getName());
 
-         logger.debug("<< created securityGroup(%s)", securityGroup);
-         for (int port : regionSecurityGroupNameAndPorts.getPorts()) {
-            authorizeGroupToItselfAndAllIPsToTCPPort(api.get(), securityGroup, port);
+      SecurityGroupApi securityGroupApi = getNeutronSecurityGroupApi(regionId);
+      if (securityGroupApi != null) {
+         org.jclouds.openstack.neutron.v2.domain.SecurityGroup group = securityGroupApi
+               .create(org.jclouds.openstack.neutron.v2.domain.SecurityGroup.CreateSecurityGroup.createBuilder()
+                     .name(regionSecurityGroupNameAndPorts.getName()).description("security group created by jclouds")
+                     .build());
+         return createSecurityGroupFrom(group, location, regionSecurityGroupNameAndPorts.getPorts());
+      } else {
+         // try to use Nova
+         Optional<? extends org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi> api = novaApi
+               .getSecurityGroupApi(regionId);
+         checkArgument(api.isPresent(),
+               "Security groups are required, but the extension is not available in region %s!", regionId);
+         final FluentIterable<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup> allGroups = api.get().list();
+         logger.debug(">> creating securityGroup %s", regionSecurityGroupNameAndPorts);
+         try {
+            org.jclouds.openstack.nova.v2_0.domain.SecurityGroup novaSecurityGroup = api.get().createWithDescription(
+                  regionSecurityGroupNameAndPorts.getName(), regionSecurityGroupNameAndPorts.getName());
+
+            logger.debug("<< created securityGroup(%s)", novaSecurityGroup);
+            for (int port : regionSecurityGroupNameAndPorts.getPorts()) {
+               authorizeGroupToItselfAndAllIPsToTCPPort(api.get(), novaSecurityGroup, port);
+            }
+            return securityGroupInRegionSecurityGroupFunction
+                  .apply(new SecurityGroupInRegion(api.get().get(novaSecurityGroup.getId()), regionId, allGroups));
+         } catch (IllegalStateException e) {
+            logger.trace("<< trying to find securityGroup(%s): %s", regionSecurityGroupNameAndPorts, e.getMessage());
+            org.jclouds.openstack.nova.v2_0.domain.SecurityGroup group = find(allGroups,
+                  nameEquals(regionSecurityGroupNameAndPorts.getName()));
+            logger.debug("<< reused securityGroup(%s)", group.getId());
+            return securityGroupInRegionSecurityGroupFunction
+                  .apply(new SecurityGroupInRegion(group, regionId, allGroups));
+         }
+      }
+   }
+
+   private SecurityGroup createSecurityGroupFrom(final org.jclouds.openstack.neutron.v2.domain.SecurityGroup group,
+         Location location, Set<Integer> ports) {
+      SecurityGroup securityGroup = neutronSecurityGroupToSecurityGroup.create(location).apply(group);
+      logger.debug("<< created securityGroup(%s)", securityGroup);
+
+      SecurityGroupApi securityGroupApi = getNeutronSecurityGroupApi(location.getId());
+      try {
+         for (int inboundPort : ports) {
+            logger.debug(">> authorizing securityGroup(%s) permission to 0.0.0.0/0 on port %d", securityGroup, inboundPort);
+            securityGroupApi.create(
+                  Rule.CreateRule.createBuilder(RuleDirection.INGRESS, RegionAndId.fromSlashEncoded(securityGroup.getId()).getId()).protocol(RuleProtocol.TCP)
+                        .portRangeMin(inboundPort).portRangeMax(inboundPort).remoteIpPrefix("0.0.0.0/0").build());
+            logger.debug("<< authorized securityGroup(%s) permission to 0.0.0.0/0 on port %d", securityGroup, inboundPort);
          }
-         return new SecurityGroupInRegion(api.get().get(securityGroup.getId()), regionId, allGroups);
+         return securityGroup;
       } catch (IllegalStateException e) {
-         logger.trace("<< trying to find securityGroup(%s): %s", regionSecurityGroupNameAndPorts, e.getMessage());
-         SecurityGroup group = find(allGroups, nameEquals(regionSecurityGroupNameAndPorts.getName()));
-         logger.debug("<< reused securityGroup(%s)", group.getId());
-         return new SecurityGroupInRegion(group, regionId, allGroups);
+         logger.trace("<< trying to find securityGroup(%s): %s", group, e.getMessage());
+
+         return securityGroupApi.listSecurityGroups().concat()
+               .filter(new Predicate<org.jclouds.openstack.neutron.v2.domain.SecurityGroup>() {
+                  @Override
+                  public boolean apply(@Nullable org.jclouds.openstack.neutron.v2.domain.SecurityGroup input) {
+                     return input.getName().equals(group.getName());
+                  }
+               }).transform(neutronSecurityGroupToSecurityGroup.create(location)).first().orNull();
       }
    }
 
-   private void authorizeGroupToItselfAndAllIPsToTCPPort(SecurityGroupApi securityGroupApi,
-                                                         SecurityGroup securityGroup, int port) {
+   private SecurityGroupApi getNeutronSecurityGroupApi(String region) {
+      if (neutronContextSupplier == null)
+         return null;
+      return ((ApiContext<NeutronApi>) neutronContextSupplier.get()).getApi().getSecurityGroupApi(region);
+   }
+
+   private void authorizeGroupToItselfAndAllIPsToTCPPort(
+         org.jclouds.openstack.nova.v2_0.extensions.SecurityGroupApi securityGroupApi,
+         org.jclouds.openstack.nova.v2_0.domain.SecurityGroup securityGroup, int port) {
       logger.debug(">> authorizing securityGroup(%s) permission to 0.0.0.0/0 on port %d", securityGroup, port);
-      securityGroupApi.createRuleAllowingCidrBlock(securityGroup.getId(), Ingress.builder().ipProtocol(
-         IpProtocol.TCP).fromPort(port).toPort(port).build(), "0.0.0.0/0");
+      securityGroupApi.createRuleAllowingCidrBlock(securityGroup.getId(),
+            Ingress.builder().ipProtocol(IpProtocol.TCP).fromPort(port).toPort(port).build(), "0.0.0.0/0");
       logger.debug("<< authorized securityGroup(%s) permission to 0.0.0.0/0 on port %d", securityGroup, port);
-
    }
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NeutronSecurityGroupToSecurityGroup.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NeutronSecurityGroupToSecurityGroup.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NeutronSecurityGroupToSecurityGroup.java
new file mode 100644
index 0000000..0a6ffbc
--- /dev/null
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NeutronSecurityGroupToSecurityGroup.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.nova.v2_0.compute.functions;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.inject.assistedinject.Assisted;
+import org.jclouds.compute.domain.SecurityGroup;
+import org.jclouds.compute.domain.SecurityGroupBuilder;
+import org.jclouds.domain.Location;
+import org.jclouds.net.domain.IpPermission;
+import org.jclouds.net.domain.IpProtocol;
+import org.jclouds.openstack.neutron.v2.domain.Rule;
+import org.jclouds.openstack.neutron.v2.domain.RuleDirection;
+
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+
+public class NeutronSecurityGroupToSecurityGroup implements Function<org.jclouds.openstack.neutron.v2.domain.SecurityGroup, SecurityGroup> {
+
+    public interface Factory {
+        NeutronSecurityGroupToSecurityGroup create(Location location);
+    }
+
+    private final Location location;
+
+    @Inject
+    public NeutronSecurityGroupToSecurityGroup(@Assisted Location location) {
+        this.location = location;
+    }
+
+    @Override
+    public SecurityGroup apply(@Nullable org.jclouds.openstack.neutron.v2.domain.SecurityGroup group) {
+        SecurityGroupBuilder builder = new SecurityGroupBuilder();
+        builder.providerId(group.getId());
+        builder.ownerId(group.getTenantId());
+        builder.name(group.getName());
+        final String regionId = location.getId();
+        builder.location(location);
+
+        builder.id(regionId + "/" + group.getId());
+        if (group.getRules() != null) {
+            builder.ipPermissions(filter(transform(group.getRules(), new Function<Rule, IpPermission>() {
+                @Override
+                public IpPermission apply(Rule from) {
+                    if (from.getDirection() == RuleDirection.EGRESS) return null;
+                    IpPermission.Builder builder = IpPermission.builder();
+                    if (from.getProtocol() != null) {
+                        builder.ipProtocol(IpProtocol.fromValue(from.getProtocol().name()));
+                    } else {
+                        builder.ipProtocol(IpProtocol.TCP);
+                    }
+                    if (from.getPortRangeMin() != null) builder.fromPort(from.getPortRangeMin());
+                    if (from.getPortRangeMax() != null) builder.toPort(from.getPortRangeMax());
+                    if (from.getRemoteGroupId() != null) {
+                        builder.groupId(regionId + "/" + from.getRemoteGroupId());
+                    } else if (from.getRemoteIpPrefix() != null){
+                        builder.cidrBlock(from.getRemoteIpPrefix());
+                    }
+
+                    return builder.build();
+                }
+            }), Predicates.notNull()));
+        }
+
+        return builder.build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/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
new file mode 100644
index 0000000..8739f60
--- /dev/null
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/functions/NovaSecurityGroupToSecurityGroup.java
@@ -0,0 +1,97 @@
+/*
+ * 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.filter;
+import static com.google.common.collect.Iterables.transform;
+
+import javax.annotation.Nullable;
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+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.net.domain.IpPermission;
+import org.jclouds.openstack.nova.v2_0.domain.SecurityGroupRule;
+import org.jclouds.openstack.nova.v2_0.domain.TenantIdAndName;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.inject.assistedinject.Assisted;
+
+/**
+ * A function for transforming a Nova-specific SecurityGroup into a generic
+ * SecurityGroup object.
+ */
+public class NovaSecurityGroupToSecurityGroup
+      implements Function<org.jclouds.openstack.nova.v2_0.domain.SecurityGroup, SecurityGroup> {
+
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+
+   public interface Factory {
+      NovaSecurityGroupToSecurityGroup create(Location location);
+   }
+
+   private final Location location;
+
+   @Inject
+   public NovaSecurityGroupToSecurityGroup(@Assisted Location location) {
+      this.location = location;
+   }
+
+   @Override
+   public SecurityGroup apply(@Nullable org.jclouds.openstack.nova.v2_0.domain.SecurityGroup group) {
+      SecurityGroupBuilder builder = new SecurityGroupBuilder();
+      builder.providerId(group.getId());
+      builder.ownerId(group.getTenantId());
+      builder.name(group.getName());
+      final String regionId = location.getId();
+      builder.location(location);
+
+      builder.id(regionId + "/" + group.getId());
+      if (group.getRules() != null) {
+         builder.ipPermissions(filter(transform(group.getRules(), new Function<SecurityGroupRule, IpPermission>() {
+            @Override
+            public IpPermission apply(SecurityGroupRule input) {
+               return securityGroupRuleToIpPermission(input);
+            }
+         }), Predicates.notNull()));
+      }
+      return builder.build();
+   }
+
+   private IpPermission securityGroupRuleToIpPermission(SecurityGroupRule rule) {
+      IpPermission.Builder builder = IpPermission.builder();
+      builder.ipProtocol(rule.getIpProtocol());
+      builder.fromPort(rule.getFromPort());
+      builder.toPort(rule.getToPort());
+      final TenantIdAndName ruleGroup = rule.getGroup();
+      if (ruleGroup != null) {
+         builder.groupId(location.getId() + "/" + ruleGroup.getTenantId());
+      }
+      if (rule.getIpRange() != null) {
+         builder.cidrBlock(rule.getIpRange());
+      }
+      return builder.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/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 1a980e5..274e4ef 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
@@ -19,54 +19,31 @@ 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 java.util.concurrent.atomic.AtomicReference;
-
 import javax.inject.Inject;
-import javax.inject.Named;
 
+import org.jclouds.compute.domain.SecurityGroup;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
 
 import com.google.common.base.Function;
-import com.google.common.base.Predicate;
 import com.google.common.cache.CacheLoader;
-import com.google.common.util.concurrent.Atomics;
 
-public class FindSecurityGroupOrCreate extends CacheLoader<RegionAndName, SecurityGroupInRegion> {
+public class FindSecurityGroupOrCreate extends CacheLoader<RegionAndName, SecurityGroup> {
 
-   protected final Predicate<AtomicReference<RegionAndName>> returnSecurityGroupExistsInRegion;
-   protected final Function<RegionSecurityGroupNameAndPorts, SecurityGroupInRegion> groupCreator;
+   protected final Function<RegionSecurityGroupNameAndPorts, SecurityGroup> groupCreator;
 
    @Inject
    public FindSecurityGroupOrCreate(
-            @Named("SECURITYGROUP_PRESENT") Predicate<AtomicReference<RegionAndName>> returnSecurityGroupExistsInRegion,
-            Function<RegionSecurityGroupNameAndPorts, SecurityGroupInRegion> groupCreator) {
-      this.returnSecurityGroupExistsInRegion = checkNotNull(returnSecurityGroupExistsInRegion,
-               "returnSecurityGroupExistsInRegion");
+            Function<RegionSecurityGroupNameAndPorts, SecurityGroup> groupCreator) {
       this.groupCreator = checkNotNull(groupCreator, "groupCreator");
    }
 
    @Override
-   public SecurityGroupInRegion load(RegionAndName in) {
-      AtomicReference<RegionAndName> securityGroupInRegionRef = Atomics.newReference(checkNotNull(in,
-               "regionSecurityGroupNameAndPorts"));
-      if (returnSecurityGroupExistsInRegion.apply(securityGroupInRegionRef)) {
-         return returnExistingSecurityGroup(securityGroupInRegionRef);
-      } else {
+   public SecurityGroup load(RegionAndName in) {
          return createNewSecurityGroup(in);
-      }
-   }
-
-   private SecurityGroupInRegion returnExistingSecurityGroup(AtomicReference<RegionAndName> securityGroupInRegionRef) {
-      RegionAndName securityGroupInRegion = securityGroupInRegionRef.get();
-      checkState(securityGroupInRegion instanceof SecurityGroupInRegion,
-               "programming error: predicate %s should update the atomic reference to the actual security group found",
-               returnSecurityGroupExistsInRegion);
-      return SecurityGroupInRegion.class.cast(securityGroupInRegion);
    }
 
-   private SecurityGroupInRegion createNewSecurityGroup(RegionAndName in) {
+   private SecurityGroup createNewSecurityGroup(RegionAndName in) {
       checkState(
                checkNotNull(in, "regionSecurityGroupNameAndPorts") instanceof RegionSecurityGroupNameAndPorts,
                "programming error: when issuing get to this cacheloader, you need to pass an instance of RegionSecurityGroupNameAndPorts, not %s",

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java
index 992302e..f9f4fb5 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/options/NovaTemplateOptions.java
@@ -80,6 +80,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
          eTo.configDrive(getConfigDrive());
          eTo.novaNetworks(getNovaNetworks());
          eTo.availabilityZone(getAvailabilityZone());
+         eTo.blockDeviceMappings(getBlockDeviceMappings());
       }
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java
index 5f747e6..532643a 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java
@@ -23,7 +23,6 @@ import static com.google.common.base.Preconditions.checkState;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicReference;
 
 import javax.inject.Inject;
@@ -33,25 +32,25 @@ import javax.inject.Singleton;
 import org.jclouds.Constants;
 import org.jclouds.compute.config.CustomizationResponse;
 import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.SecurityGroup;
 import org.jclouds.compute.domain.Template;
+import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.functions.GroupNamingConvention;
 import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName;
 import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
 import org.jclouds.compute.strategy.ListNodesStrategy;
 import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet;
+import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.openstack.nova.v2_0.NovaApi;
 import org.jclouds.openstack.nova.v2_0.compute.functions.AllocateAndAddFloatingIpToNode;
 import org.jclouds.openstack.nova.v2_0.compute.options.NodeAndNovaTemplateOptions;
 import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
 import org.jclouds.openstack.nova.v2_0.domain.KeyPair;
-import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName;
 import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts;
-import org.jclouds.openstack.nova.v2_0.domain.regionscoped.SecurityGroupInRegion;
 
 import com.google.common.base.Function;
 import com.google.common.base.Strings;
-import com.google.common.base.Throwables;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -70,8 +69,9 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
    public static final String JCLOUDS_SG_PREFIX = "jclouds_sg";
 
    private final AllocateAndAddFloatingIpToNode createAndAddFloatingIpToNode;
-   protected final LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupCache;
-   protected final NovaApi novaApi;
+   private final LoadingCache<RegionAndName, SecurityGroup> securityGroupCache;
+   private final NovaApi novaApi;
+   private final SecurityGroupExtension securityGroupExtension;
 
    @Inject
    protected ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet(
@@ -81,13 +81,16 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
             CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory,
             @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor,
             AllocateAndAddFloatingIpToNode createAndAddFloatingIpToNode,
-            LoadingCache<RegionAndName, SecurityGroupInRegion> securityGroupCache, NovaApi novaApi) {
+            LoadingCache<RegionAndName, SecurityGroup> securityGroupCache,
+            NovaApi novaApi,
+            SecurityGroupExtension securityGroupExtension) {
       super(addNodeWithTagStrategy, listNodesStrategy, namingConvention, userExecutor,
                customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory);
       this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache");
       this.createAndAddFloatingIpToNode = checkNotNull(createAndAddFloatingIpToNode,
                "createAndAddFloatingIpToNode");
       this.novaApi = checkNotNull(novaApi, "novaApi");
+      this.securityGroupExtension = securityGroupExtension;
    }
 
    @Override
@@ -106,14 +109,8 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
          checkArgument(novaApi.getKeyPairApi(region).isPresent(),
                  "Key Pairs are required by options, but the extension is not available! options: %s", templateOptions);
       }
-
       final List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts());
-      if (!templateOptions.getGroups().isEmpty() || !inboundPorts.isEmpty()) {
-         checkArgument(novaApi.getSecurityGroupApi(region).isPresent(),
-                 "Security groups are required by options, but the extension is not available! options: %s",
-                 templateOptions);
-      }
-      
+
       KeyPair keyPair = null;
       if (templateOptions.shouldGenerateKeyPair()) {
          keyPair = generateKeyPair(region, namingConvention.create().sharedNameForGroup(group));
@@ -132,29 +129,29 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT
       ImmutableList.Builder<String> tagsBuilder = ImmutableList.builder();
 
       if (!templateOptions.getGroups().isEmpty()) {
-         Set<String> securityGroupNames = novaApi.getSecurityGroupApi(region).get().list()
-                 .transform(new Function<SecurityGroup, String>() {
-                    @Override
-                    public String apply(SecurityGroup input) {
-                       return input.getName();
-                    }
-                 })
-                 .toSet();
+         Iterable<String> securityGroupNames = Iterables.transform(securityGroupExtension.listSecurityGroups(), new Function<org.jclouds.compute.domain.SecurityGroup, String>() {
+            @Override
+            public String apply(@Nullable org.jclouds.compute.domain.SecurityGroup input) {
+               return input.getName();
+            }
+         });
          for (String securityGroupName : templateOptions.getGroups()) {
-            checkState(securityGroupNames.contains(securityGroupName), "Cannot find security group with name " + securityGroupName + ". \nSecurity groups available are: \n" + Iterables.toString(securityGroupNames)); // {
+            checkState(Iterables.contains(securityGroupNames, securityGroupName), "Cannot find security group with name " + securityGroupName + ". \nSecurity groups available are: \n" + Iterables.toString(securityGroupNames)); // {
          }
-      }
-      else if (!inboundPorts.isEmpty()) {
-         SecurityGroupInRegion securityGroupInRegion;
+
+      } else if (!inboundPorts.isEmpty()) {
          String securityGroupName = namingConvention.create().sharedNameForGroup(group);
-         try {
-            securityGroupInRegion = securityGroupCache.get(new RegionSecurityGroupNameAndPorts(region, securityGroupName, inboundPorts));
-         } catch (ExecutionException e) {
-            throw Throwables.propagate(e.getCause());
+
+         // populate the security group cache with existing security groups
+         for (SecurityGroup existingSecurityGroup : securityGroupExtension.listSecurityGroupsInLocation(template.getLocation())) {
+            securityGroupCache.put(new RegionSecurityGroupNameAndPorts(region, existingSecurityGroup.getName(), inboundPorts), existingSecurityGroup);
          }
-         templateOptions.securityGroups(securityGroupName);
-         tagsBuilder.add(String.format("%s-%s", JCLOUDS_SG_PREFIX, securityGroupInRegion.getSecurityGroup().getId()));
+
+         SecurityGroup securityGroup = securityGroupCache.getUnchecked(new RegionSecurityGroupNameAndPorts(region, securityGroupName, inboundPorts));
+         templateOptions.securityGroups(securityGroup.getName());
+         tagsBuilder.add(String.format("%s-%s", JCLOUDS_SG_PREFIX, securityGroup.getId()));
       }
+
       templateOptions.tags(tagsBuilder.build());
 
       Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes,

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/regionscoped/NeutronSecurityGroupInRegion.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/regionscoped/NeutronSecurityGroupInRegion.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/regionscoped/NeutronSecurityGroupInRegion.java
new file mode 100644
index 0000000..11e3f90
--- /dev/null
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/domain/regionscoped/NeutronSecurityGroupInRegion.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.openstack.nova.v2_0.domain.regionscoped;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.jclouds.openstack.neutron.v2.domain.SecurityGroup;
+import org.jclouds.openstack.nova.v2_0.domain.TenantIdAndName;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+public class NeutronSecurityGroupInRegion extends RegionAndName {
+   protected final SecurityGroup securityGroup;
+
+   protected final Multimap<TenantIdAndName, SecurityGroup> groupsByName;
+
+   public NeutronSecurityGroupInRegion(SecurityGroup securityGroup, String regionId, Iterable<SecurityGroup> allGroupsInRegion) {
+      super(regionId, checkNotNull(securityGroup, "securityGroup").getName());
+      this.securityGroup = securityGroup;
+      this.groupsByName = HashMultimap.create();
+      for (SecurityGroup groupInRegion : allGroupsInRegion) {
+         final TenantIdAndName tenantIdAndName = TenantIdAndName.builder()
+            .tenantId(groupInRegion.getTenantId())
+            .name(groupInRegion.getName())
+            .build();
+         this.groupsByName.put(tenantIdAndName, groupInRegion);
+      }
+   }
+
+   public SecurityGroup getSecurityGroup() {
+      return securityGroup;
+   }
+
+   /**
+    * Returns a map of group {@link TenantIdAndName}s to groups.
+    *
+    * The returned value is a collection, to take into account the possibility that certain clouds
+    * may permit duplicate group names.
+    *
+    * @return The map of names to (collections of) groups.
+    */
+   public Map<TenantIdAndName, Collection<SecurityGroup>> getGroupsByName() {
+       return groupsByName.asMap();
+   }
+
+   // superclass hashCode/equals are good enough, and help us use RegionAndName and SecurityGroupInRegion
+   // interchangeably as Map keys
+
+   @Override
+   protected ToStringHelper string() {
+      return super.string()
+          .add("securityGroup", securityGroup)
+          .add("groupsByName", groupsByName);
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java
index 33bf09a..512f4a0 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java
@@ -19,6 +19,7 @@ package org.jclouds.openstack.nova.v2_0.features;
 import com.google.common.base.Optional;
 
 import java.util.Map;
+import java.util.Set;
 
 import javax.inject.Named;
 import javax.ws.rs.Consumes;
@@ -31,6 +32,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 
+import org.jclouds.Fallbacks;
 import org.jclouds.Fallbacks.AbsentOn403Or404Or500;
 import org.jclouds.Fallbacks.EmptyMapOnNotFoundOr404;
 import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
@@ -40,6 +42,7 @@ import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
 import org.jclouds.collect.PagedIterable;
 import org.jclouds.fallbacks.MapHttp4xxCodesToExceptions;
 import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.openstack.nova.v2_0.domain.SecurityGroup;
 import org.jclouds.openstack.v2_0.domain.PaginatedCollection;
 import org.jclouds.openstack.keystone.auth.filters.AuthenticateRequest;
 import org.jclouds.openstack.keystone.v2_0.KeystoneFallbacks.EmptyPaginatedCollectionOnNotFoundOr404;
@@ -362,8 +365,8 @@ public interface ServerApi {
     *
     * @param id
     *           id of the image
-    * @param metadata
-    *           a Map containing the metadata
+    * @param key
+    *           a key containing the metadata
     * @return the value or null if not present
     */
    @Named("server:getMetadata")
@@ -428,4 +431,19 @@ public interface ServerApi {
    @ResponseParser(ParseDiagnostics.class)
    @Fallback(AbsentOn403Or404Or500.class)
    Optional<Map<String, String>> getDiagnostics(@PathParam("id") String id);
+
+   /**
+    * Lists Security Groups for a server.
+
+    * @param id
+    *           id of the server
+    * @return a list of security groups attached to the server
+    */
+   @Named("server:getSecurityGroups")
+   @GET
+   @Path("/{id}/os-security-groups")
+   @SelectJson("security_groups")
+   @Fallback(Fallbacks.EmptySetOnNotFoundOr404.class)
+   Set<SecurityGroup> listSecurityGroupForServer(@PathParam("id") String id);
+
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/09936b57/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java
index 5528349..1317fc5 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceAdapterExpectTest.java
@@ -66,7 +66,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
          .addHeader("Accept", "application/json")
          .addHeader("X-Auth-Token", authToken)
          .payload(payloadFromStringWithContentType(
-                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"networks\": [{\"uuid\": \"4ebd35cf-bfe7-4d93-b0d8-eb468ce2245a\"}]}}", "application/json"))
+                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"networks\":[{\"uuid\":\"4ebd35cf-bfe7-4d93-b0d8-eb468ce2245a\"}]}}", "application/json"))
          .build();
 
       HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
@@ -101,7 +101,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
          .addHeader("Accept", "application/json")
          .addHeader("X-Auth-Token", authToken)
          .payload(payloadFromStringWithContentType(
-                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"OS-DCF:diskConfig\":\"AUTO\"}}", "application/json"))
+                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"OS-DCF:diskConfig\":\"AUTO\"}}", "application/json"))
          .build();
 
       HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
@@ -136,7 +136,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
          .addHeader("Accept", "application/json")
          .addHeader("X-Auth-Token", authToken)
          .payload(payloadFromStringWithContentType(
-                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"config_drive\":\"true\"}}", "application/json"))
+                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"config_drive\":\"true\"}}", "application/json"))
          .build();
 
       HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
@@ -170,7 +170,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
          .addHeader("Accept", "application/json")
          .addHeader("X-Auth-Token", authToken)
          .payload(payloadFromStringWithContentType(
-                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"networks\":[{\"uuid\":\"12345\", \"port\":\"67890\", \"fixed_ip\":\"192.168.0.1\"},{\"uuid\":\"54321\", \"port\":\"09876\", \"fixed_ip\":\"192.168.0.2\"},{\"uuid\":\"non-nova-uuid\"}]}}", "application/json"))
+                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"networks\":[{\"uuid\":\"12345\",\"port\":\"67890\",\"fixed_ip\":\"192.168.0.1\"},{\"uuid\":\"54321\",\"port\":\"09876\",\"fixed_ip\":\"192.168.0.2\"},{\"uuid\":\"non-nova-uuid\"}]}}", "application/json"))
          .build();
 
       HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
@@ -217,7 +217,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
          .addHeader("Accept", "application/json")
          .addHeader("X-Auth-Token", authToken)
          .payload(payloadFromStringWithContentType(
-                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"security_groups\":[{\"name\":\"group1\"}, {\"name\":\"group2\"}]}}", "application/json"))
+                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"security_groups\":[{\"name\":\"group1\"},{\"name\":\"group2\"}]}}", "application/json"))
          .build();
 
       HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
@@ -258,7 +258,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
          .addHeader("Accept", "application/json")
          .addHeader("X-Auth-Token", authToken)
          .payload(payloadFromStringWithContentType(
-                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"key_name\":\"foo\"}}", "application/json"))
+                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\",\"key_name\":\"foo\"}}", "application/json"))
          .build();
 
 
@@ -301,7 +301,7 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
          .addHeader("Accept", "application/json")
          .addHeader("X-Auth-Token", authToken)
          .payload(payloadFromStringWithContentType(
-                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\"}}", "application/json"))
+                  "{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"2235\",\"flavorRef\":\"100\"}}", "application/json"))
          .build();
 
       HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")