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

[1/3] git commit: JCLOUDS-105. Real fix for keypairs being ignored if VM says it's password-enabled.

Updated Branches:
  refs/heads/master 7651657cf -> a3488dc43


JCLOUDS-105. Real fix for keypairs being ignored if VM says it's password-enabled.


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

Branch: refs/heads/master
Commit: a3488dc43cd60580374532f3780bdb1b1ec8697c
Parents: fbe637c
Author: Andrew Bayer <an...@gmail.com>
Authored: Fri May 31 10:53:18 2013 -0700
Committer: Andrew Bayer <an...@gmail.com>
Committed: Thu Jun 6 10:26:20 2013 -0700

----------------------------------------------------------------------
 .../strategy/CloudStackComputeServiceAdapter.java  |   23 ++++--
 .../CloudStackComputeServiceAdapterExpectTest.java |   63 ++++++++++++-
 .../CloudStackComputeServiceAdapterLiveTest.java   |   71 ++++-----------
 3 files changed, 91 insertions(+), 66 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/a3488dc4/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
index a4f7176..5ad2fff 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
@@ -174,12 +174,19 @@ public class CloudStackComputeServiceAdapter implements
       }
 
       if (templateOptions.getKeyPair() != null) {
+         SshKeyPair keyPair = null;
          if (templateOptions.getLoginPrivateKey() != null) {
             String pem = templateOptions.getLoginPrivateKey();
-            SshKeyPair keyPair = SshKeyPair.builder().name(templateOptions.getKeyPair())
+            keyPair = SshKeyPair.builder().name(templateOptions.getKeyPair())
                .fingerprint(fingerprintPrivateKey(pem)).privateKey(pem).build();
             keyPairCache.asMap().put(keyPair.getName(), keyPair);
             options.keyPair(keyPair.getName());
+         } else if (client.getSSHKeyPairClient().getSSHKeyPair(templateOptions.getKeyPair()) != null) {
+            keyPair = client.getSSHKeyPairClient().getSSHKeyPair(templateOptions.getKeyPair());
+         }
+         if (keyPair != null) {
+            keyPairCache.asMap().put(keyPair.getName(), keyPair);
+            options.keyPair(keyPair.getName());
          }
       } else if (templateOptions.shouldGenerateKeyPair()) {
          SshKeyPair keyPair = keyPairCache.getUnchecked(namingConvention.create()
@@ -214,13 +221,15 @@ public class CloudStackComputeServiceAdapter implements
          templateId, options);
       VirtualMachine vm = blockUntilJobCompletesAndReturnResult.<VirtualMachine>apply(job);
       logger.debug("--- virtualmachine: %s", vm);
-      LoginCredentials credentials = null;
-      if (vm.isPasswordEnabled()) {
-         assert vm.getPassword() != null : vm;
-         credentials = LoginCredentials.builder().password(vm.getPassword()).build();
+      LoginCredentials.Builder credentialsBuilder = LoginCredentials.builder();
+      if (!vm.isPasswordEnabled() || templateOptions.getKeyPair() != null) {
+         SshKeyPair keyPair = keyPairCache.getUnchecked(templateOptions.getKeyPair());
+         credentialsBuilder.privateKey(keyPair.getPrivateKey());
       } else {
-         credentials = LoginCredentials.fromCredentials(credentialStore.get("keypair#" + templateOptions.getKeyPair()));
+         assert vm.getPassword() != null : vm;
+         credentialsBuilder.password(vm.getPassword());
       }
+      
       if (templateOptions.shouldSetupStaticNat()) {
          Capabilities capabilities = client.getConfigurationClient().listCapabilities();
          // TODO: possibly not all network ids, do we want to do this
@@ -241,7 +250,7 @@ public class CloudStackComputeServiceAdapter implements
             }
          }
       }
-      return new NodeAndInitialCredentials<VirtualMachine>(vm, vm.getId() + "", credentials);
+      return new NodeAndInitialCredentials<VirtualMachine>(vm, vm.getId() + "", credentialsBuilder.build());
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/a3488dc4/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
index 1e6ff6f..4eb89aa 100644
--- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
@@ -83,6 +83,57 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
         .payload(payloadFromResource("/queryasyncjobresultresponse-authorizeingress.json"))
         .build();
    
+   HttpRequest listCapabilitiesNotListAll = HttpRequest.builder().method("GET")
+     .endpoint("http://localhost:8080/client/api")
+     .addQueryParam("response", "json")
+     .addQueryParam("command", "listCapabilities")
+     .addQueryParam("apiKey", "APIKEY")      
+     .addQueryParam("signature", "l3PVoJnKK2G2gHk3HPHtpwWjlW4%3D")
+     .addHeader("Accept", "application/json")
+     .build();
+
+   public void testCreateNodeWithGroupEncodedIntoName() {
+      HttpRequest deployVM = HttpRequest.builder().method("GET")
+            .endpoint("http://localhost:8080/client/api")
+            .addQueryParam("response", "json")
+            .addQueryParam("command", "deployVirtualMachine")
+            .addQueryParam("zoneid", "1")
+            .addQueryParam("serviceofferingid", "1")
+            .addQueryParam("templateid", "4")
+            .addQueryParam("displayname", "test-e92")
+            .addQueryParam("name", "test-e92")
+            .addQueryParam("networkids", "204")
+            .addQueryParam("apiKey", "APIKEY")
+            .addQueryParam("signature", "wJ%2BiflOS3am5qcjQOd8Y/Pw8/Dc%3D")
+            .addHeader("Accept", "application/json")
+            .build(); 
+  
+      Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
+            .put(listTemplates, listTemplatesResponse)
+            .put(listOsTypes, listOsTypesResponse)
+            .put(listOsCategories, listOsCategoriesResponse)
+            .put(listZones, listZonesResponse)
+            .put(listServiceOfferings, listServiceOfferingsResponse)
+            .put(listAccounts, listAccountsResponse)
+            .put(listNetworks, listNetworksResponse)
+            .put(getZone, getZoneResponse)
+            .put(deployVM, deployVMResponse)
+            .put(queryAsyncJobResult, queryAsyncJobResultResponse)
+            .build();
+
+      Injector forNode = requestsSendResponses(requestResponseMap);
+
+      Template template = forNode.getInstance(TemplateBuilder.class).osFamily(OsFamily.CENTOS).build();
+      template.getOptions().as(CloudStackTemplateOptions.class).setupStaticNat(false);
+
+      CloudStackComputeServiceAdapter adapter = forNode.getInstance(CloudStackComputeServiceAdapter.class);
+
+      NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
+            template);
+      assertNotNull(server);
+      assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
+   }
+
    public void testCreateNodeWithGroupEncodedIntoNameWithKeyPair() throws IOException {
       HttpRequest deployVM = HttpRequest.builder().method("GET")
             .endpoint("http://localhost:8080/client/api")
@@ -126,7 +177,7 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
       NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
             template);
       assertNotNull(server);
-      assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
+      assertEquals(server.getCredentials().getPrivateKey(), privKey);
    }
 
    public void testCreateNodeWithGroupEncodedIntoNameWithGenerateKeyPair() throws IOException {
@@ -171,7 +222,7 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
       NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
             template);
       assertNotNull(server);
-      assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
+      assertNotNull(server.getCredentials().getPrivateKey());
    }
    
    public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairDefaultSecurityGroup() throws IOException {
@@ -220,7 +271,7 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
       NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
             template);
       assertNotNull(server);
-      assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
+      assertEquals(server.getCredentials().getPrivateKey(), privKey);
    }
 
    public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairGenerateSecurityGroup() throws IOException {
@@ -271,11 +322,12 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
          .overrideLoginPrivateKey(privKey);
 
       CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
+      CloudStackContext context = forKeyPair.getInstance(CloudStackContext.class);
 
       NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
             template);
       assertNotNull(server);
-      assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
+      assertEquals(server.getCredentials().getPrivateKey(), privKey);
    }
 
    public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairAssignedToAccountAndDomain() throws IOException {
@@ -321,11 +373,12 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
          .overrideLoginPrivateKey(privKey);
 
       CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
+      CloudStackContext context = forKeyPair.getInstance(CloudStackContext.class);
 
       NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
             template);
       assertNotNull(server);
-      assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
+      assertEquals(server.getCredentials().getPrivateKey(), privKey);
    }   
    
    @Override

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/a3488dc4/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterLiveTest.java
index a1ad652..82efa60 100644
--- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterLiveTest.java
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterLiveTest.java
@@ -31,17 +31,27 @@ import javax.inject.Singleton;
 
 import org.jclouds.cloudstack.CloudStackClient;
 import org.jclouds.cloudstack.compute.config.CloudStackComputeServiceContextModule;
+import org.jclouds.cloudstack.compute.functions.OrphanedGroupsByZoneId;
+import org.jclouds.cloudstack.compute.loaders.CreateUniqueKeyPair;
+import org.jclouds.cloudstack.compute.loaders.FindSecurityGroupOrCreate;
 import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
 import org.jclouds.cloudstack.compute.strategy.CloudStackComputeServiceAdapter;
 import org.jclouds.cloudstack.compute.strategy.OptionsConverter;
+import org.jclouds.cloudstack.config.CloudStackParserModule;
+import org.jclouds.cloudstack.config.CloudStackRestClientModule;
 import org.jclouds.cloudstack.domain.FirewallRule;
 import org.jclouds.cloudstack.domain.IPForwardingRule;
 import org.jclouds.cloudstack.domain.Network;
 import org.jclouds.cloudstack.domain.NetworkType;
+import org.jclouds.cloudstack.domain.SecurityGroup;
 import org.jclouds.cloudstack.domain.ServiceOffering;
+import org.jclouds.cloudstack.domain.SshKeyPair;
 import org.jclouds.cloudstack.domain.User;
 import org.jclouds.cloudstack.domain.VirtualMachine;
 import org.jclouds.cloudstack.domain.Zone;
+import org.jclouds.cloudstack.domain.ZoneAndName;
+import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
+import org.jclouds.cloudstack.functions.CreateSecurityGroupIfNeeded;
 import org.jclouds.cloudstack.functions.GetFirewallRulesByVirtualMachine;
 import org.jclouds.cloudstack.functions.GetIPForwardingRulesByVirtualMachine;
 import org.jclouds.cloudstack.functions.StaticNATVirtualMachineInNetwork;
@@ -55,15 +65,19 @@ import org.jclouds.cloudstack.suppliers.ZoneIdToZoneSupplier;
 import org.jclouds.collect.Memoized;
 import org.jclouds.compute.ComputeServiceAdapter.NodeAndInitialCredentials;
 import org.jclouds.compute.ComputeTestUtils;
+import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
+import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.functions.DefaultCredentialsFromImageOrOverridingCredentials;
 import org.jclouds.compute.strategy.PrioritizeCredentialsFromTemplate;
 import org.jclouds.domain.Credentials;
+import org.jclouds.location.Provider;
 import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
 import org.testng.annotations.AfterGroups;
 import org.testng.annotations.BeforeGroups;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
@@ -72,6 +86,7 @@ import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
 import com.google.common.net.HostAndPort;
 import com.google.common.net.InetAddresses;
 import com.google.inject.AbstractModule;
@@ -96,60 +111,8 @@ public class CloudStackComputeServiceAdapterLiveTest extends BaseCloudStackClien
    @BeforeGroups(groups = { "live" })
    public void setupContext() {
       super.setupContext();
-      Module module = new AbstractModule() {
-
-         @Override
-         protected void configure() {
-            bindProperties(binder(), setupProperties());
-            bind(new TypeLiteral<Supplier<User>>() {
-            }).annotatedWith(Memoized.class).to(GetCurrentUser.class).in(Scopes.SINGLETON);
-            bind(new TypeLiteral<Supplier<Map<String, Network>>>() {
-            }).annotatedWith(Memoized.class).to(NetworksForCurrentUser.class).in(Scopes.SINGLETON);
-            bind(new TypeLiteral<Map<String, Credentials>>() {
-            }).toInstance(credentialStore);
-            bind(CloudStackClient.class).toInstance(client);
-            bind(new TypeLiteral<Map<NetworkType, ? extends OptionsConverter>>() {}).
-               toInstance(new CloudStackComputeServiceContextModule().optionsConverters());
-            bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
-            bind(new TypeLiteral<CacheLoader<String, Set<IPForwardingRule>>>() {
-            }).to(GetIPForwardingRulesByVirtualMachine.class);
-            bind(new TypeLiteral<CacheLoader<String, Set<FirewallRule>>>() {
-            }).to(GetFirewallRulesByVirtualMachine.class);
-            bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
-               to(ZoneIdToZone.class);
-            bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
-               to(ZoneIdToZoneSupplier.class);
-            install(new FactoryModuleBuilder().build(StaticNATVirtualMachineInNetwork.Factory.class));
-         }
-         
-         @Provides
-         @Singleton
-         Supplier<Credentials> supplyCredentials(){
-            return Suppliers.ofInstance(new Credentials(identity, credential));
-         }
-
-         @Provides
-         @Singleton
-         protected Predicate<String> jobComplete(JobComplete jobComplete) {
-            return retry(jobComplete, 1200, 1, 5, SECONDS);
-         }
-
-         @Provides
-         @Singleton
-         protected LoadingCache<String, Set<IPForwardingRule>> getIPForwardingRulesByVirtualMachine(
-               GetIPForwardingRulesByVirtualMachine getIPForwardingRules) {
-            return CacheBuilder.newBuilder().build(getIPForwardingRules);
-         }
-
-
-         @Provides
-         @Singleton
-         protected LoadingCache<String, Set<FirewallRule>> getFirewallRulesByVirtualMachine(
-            GetFirewallRulesByVirtualMachine getFirewallRules) {
-            return CacheBuilder.newBuilder().build(getFirewallRules);
-         }
-      };
-      adapter = Guice.createInjector(module, new SLF4JLoggingModule()).getInstance(
+
+      adapter = context.utils().injector().getInstance(
             CloudStackComputeServiceAdapter.class);
 
       keyPairName = prefix + "-adapter-test-keypair";


[2/3] JCLOUDS-106. Add proper support for generation/detection of keypair/security groups to CloudStack.

Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPairTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPairTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPairTest.java
new file mode 100644
index 0000000..1bb09e6
--- /dev/null
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPairTest.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.cloudstack.compute.loaders;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertEquals;
+
+import java.net.UnknownHostException;
+
+import org.jclouds.cloudstack.CloudStackClient;
+import org.jclouds.cloudstack.domain.SshKeyPair;
+import org.jclouds.cloudstack.features.SSHKeyPairClient;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.TypeLiteral;
+
+/**
+ * @author Adam Lowe
+ * @author Andrew Bayer
+ */
+@Test(groups = "unit", testName = "CreateUniqueKeyPairTest")
+public class CreateUniqueKeyPairTest {
+
+   @Test
+   public void testLoad() throws UnknownHostException {
+      final CloudStackClient client = createMock(CloudStackClient.class);
+      SSHKeyPairClient keyClient = createMock(SSHKeyPairClient.class);
+
+      SshKeyPair pair = createMock(SshKeyPair.class);
+
+      expect(client.getSSHKeyPairClient()).andReturn(keyClient);
+      expect(keyClient.createSSHKeyPair("group-1")).andReturn(pair);
+
+      replay(client, keyClient);
+
+      CreateUniqueKeyPair parser = Guice.createInjector(new AbstractModule() {
+
+         @Override
+         protected void configure() {
+            bind(new TypeLiteral<Supplier<String>>() {
+            }).toInstance(Suppliers.ofInstance("1"));
+            bind(CloudStackClient.class).toInstance(client);
+         }
+
+      }).getInstance(CreateUniqueKeyPair.class);
+
+      assertEquals(parser.load("group-1"), pair);
+
+      verify(client, keyClient);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreateTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreateTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreateTest.java
new file mode 100644
index 0000000..fa6b749
--- /dev/null
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreateTest.java
@@ -0,0 +1,261 @@
+/*
+ * 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.cloudstack.compute.loaders;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
+import static org.jclouds.util.Predicates2.retry;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertEquals;
+
+import java.net.UnknownHostException;
+import javax.inject.Singleton;
+
+import org.jclouds.cloudstack.CloudStackClient;
+import org.jclouds.cloudstack.domain.IngressRule;
+import org.jclouds.cloudstack.domain.SecurityGroup;
+import org.jclouds.cloudstack.domain.Zone;
+import org.jclouds.cloudstack.domain.ZoneAndName;
+import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
+import org.jclouds.cloudstack.features.AsyncJobClient;
+import org.jclouds.cloudstack.features.SecurityGroupClient;
+import org.jclouds.cloudstack.features.ZoneClient;
+import org.jclouds.cloudstack.functions.CreateSecurityGroupIfNeeded;
+import org.jclouds.cloudstack.functions.ZoneIdToZone;
+import org.jclouds.cloudstack.predicates.JobComplete;
+import org.jclouds.cloudstack.suppliers.ZoneIdToZoneSupplier;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
+
+/**
+ * @author Adam Lowe
+ * @author Andrew Bayer
+ */
+@Test(groups = "unit", testName = "FindSecurityGroupOrCreateTest")
+public class FindSecurityGroupOrCreateTest {
+
+   @Test
+   public void testLoad() throws UnknownHostException {
+      final CloudStackClient client = createMock(CloudStackClient.class);
+      SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
+      ZoneClient zoneClient = createMock(ZoneClient.class);
+      AsyncJobClient jobClient = createMock(AsyncJobClient.class);
+      
+      SecurityGroup group = createMock(SecurityGroup.class);
+      
+      Zone zone = createMock(Zone.class);
+
+      expect(group.getIngressRules()).andReturn(ImmutableSet.<IngressRule> of());
+      expect(group.getId()).andReturn("sec-1234").anyTimes();
+      expect(zone.isSecurityGroupsEnabled()).andReturn(true);
+      
+      expect(client.getSecurityGroupClient()).andReturn(secClient)
+         .anyTimes();
+      expect(client.getZoneClient()).andReturn(zoneClient);
+      expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
+
+      expect(zoneClient.getZone("zone-1")).andReturn(zone);
+      expect(secClient.getSecurityGroupByName("group-1")).andReturn(null);
+      expect(secClient.createSecurityGroup("group-1")).andReturn(group);
+      expect(secClient.authorizeIngressPortsToCIDRs("sec-1234",
+                                                    "TCP",
+                                                    22,
+                                                    22,
+                                                    ImmutableSet.of("0.0.0.0/0"))).andReturn("job-1234");
+
+      replay(client, secClient, zoneClient, zone, group);
+
+      ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
+         .zone("zone-1")
+         .name("group-1")
+         .ports(ImmutableSet.of(22))
+         .cidrs(ImmutableSet.<String> of()).build();
+      
+      FindSecurityGroupOrCreate parser = Guice.createInjector(new AbstractModule() {
+            
+            @Override
+            protected void configure() {
+               bind(new TypeLiteral<Supplier<String>>() {
+                  }).toInstance(Suppliers.ofInstance("1"));
+               bind(CloudStackClient.class).toInstance(client);
+               bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
+                  to(ZoneIdToZone.class);
+               bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
+                  to(ZoneIdToZoneSupplier.class);
+               bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
+
+               bind(new TypeLiteral<Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup>>() {
+                  }).to(CreateSecurityGroupIfNeeded.class);
+               
+               bind(new TypeLiteral<CacheLoader<ZoneAndName, SecurityGroup>>() {
+                  }).to(FindSecurityGroupOrCreate.class);
+            }
+            
+            @Provides
+            @Singleton
+            protected Predicate<String> jobComplete(JobComplete jobComplete) {
+               return retry(jobComplete, 1200, 1, 5, SECONDS);
+            }
+
+      }).getInstance(FindSecurityGroupOrCreate.class);
+
+      assertEquals(parser.load(input), group);
+
+      verify(client, secClient, zoneClient, zone, group);
+   }
+
+   
+   @Test
+   public void testLoadAlreadyExists() throws UnknownHostException {
+      final CloudStackClient client = createMock(CloudStackClient.class);
+      SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
+      ZoneClient zoneClient = createMock(ZoneClient.class);
+      AsyncJobClient jobClient = createMock(AsyncJobClient.class);
+      
+      SecurityGroup group = createMock(SecurityGroup.class);
+      
+      Zone zone = createMock(Zone.class);
+
+      expect(group.getId()).andReturn("sec-1234").anyTimes();
+      
+      expect(client.getSecurityGroupClient()).andReturn(secClient)
+         .anyTimes();
+      expect(client.getZoneClient()).andReturn(zoneClient);
+      expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
+
+      expect(secClient.getSecurityGroupByName("group-1")).andReturn(group);
+
+      replay(client, secClient, zoneClient, zone, group);
+
+      ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
+         .zone("zone-1")
+         .name("group-1")
+         .ports(ImmutableSet.of(22))
+         .cidrs(ImmutableSet.<String> of()).build();
+      
+      FindSecurityGroupOrCreate parser = Guice.createInjector(new AbstractModule() {
+            
+            @Override
+            protected void configure() {
+               bind(new TypeLiteral<Supplier<String>>() {
+                  }).toInstance(Suppliers.ofInstance("1"));
+               bind(CloudStackClient.class).toInstance(client);
+               bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
+                  to(ZoneIdToZone.class);
+               bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
+                  to(ZoneIdToZoneSupplier.class);
+               bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
+
+               bind(new TypeLiteral<Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup>>() {
+                  }).to(CreateSecurityGroupIfNeeded.class);
+               
+               bind(new TypeLiteral<CacheLoader<ZoneAndName, SecurityGroup>>() {
+                  }).to(FindSecurityGroupOrCreate.class);
+            }
+            
+            @Provides
+            @Singleton
+            protected Predicate<String> jobComplete(JobComplete jobComplete) {
+               return retry(jobComplete, 1200, 1, 5, SECONDS);
+            }
+
+      }).getInstance(FindSecurityGroupOrCreate.class);
+
+      assertEquals(parser.load(input), group);
+
+      verify(client, secClient, zoneClient, zone, group);
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testLoadZoneNoSecurityGroups() throws UnknownHostException {
+      final CloudStackClient client = createMock(CloudStackClient.class);
+      SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
+      ZoneClient zoneClient = createMock(ZoneClient.class);
+      AsyncJobClient jobClient = createMock(AsyncJobClient.class);
+      
+      SecurityGroup group = createMock(SecurityGroup.class);
+      
+      Zone zone = createMock(Zone.class);
+
+      expect(zone.isSecurityGroupsEnabled()).andReturn(false);
+      
+      expect(client.getSecurityGroupClient()).andReturn(secClient)
+         .anyTimes();
+      expect(client.getZoneClient()).andReturn(zoneClient);
+      expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
+
+      expect(zoneClient.getZone("zone-1")).andReturn(zone);
+      expect(secClient.getSecurityGroupByName("group-1")).andReturn(null);
+
+      replay(client, secClient, zoneClient, zone, group);
+
+      ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
+         .zone("zone-1")
+         .name("group-1")
+         .ports(ImmutableSet.of(22))
+         .cidrs(ImmutableSet.<String> of()).build();
+      
+      FindSecurityGroupOrCreate parser = Guice.createInjector(new AbstractModule() {
+            
+            @Override
+            protected void configure() {
+               bind(new TypeLiteral<Supplier<String>>() {
+                  }).toInstance(Suppliers.ofInstance("1"));
+               bind(CloudStackClient.class).toInstance(client);
+               bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
+                  to(ZoneIdToZone.class);
+               bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
+                  to(ZoneIdToZoneSupplier.class);
+               bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
+
+               bind(new TypeLiteral<Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup>>() {
+                  }).to(CreateSecurityGroupIfNeeded.class);
+               
+               bind(new TypeLiteral<CacheLoader<ZoneAndName, SecurityGroup>>() {
+                  }).to(FindSecurityGroupOrCreate.class);
+            }
+            
+            @Provides
+            @Singleton
+            protected Predicate<String> jobComplete(JobComplete jobComplete) {
+               return retry(jobComplete, 1200, 1, 5, SECONDS);
+            }
+
+      }).getInstance(FindSecurityGroupOrCreate.class);
+
+      assertEquals(parser.load(input), group);
+
+      verify(client, secClient, zoneClient, zone, group);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptionsTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptionsTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptionsTest.java
index 33a66fc..7bb81f5 100644
--- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptionsTest.java
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptionsTest.java
@@ -18,6 +18,8 @@ package org.jclouds.cloudstack.compute.options;
 
 import static org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions.Builder.account;
 import static org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions.Builder.domainId;
+import static org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions.Builder.generateKeyPair;
+import static org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions.Builder.generateSecurityGroup;
 import static org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions.Builder.ipOnDefaultNetwork;
 import static org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions.Builder.ipsToNetworks;
 import static org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions.Builder.keyPair;
@@ -88,6 +90,27 @@ public class CloudStackTemplateOptionsTest {
    }
 
    @Test
+   public void testGenerateSecurityGroupDefaultsFalse() {
+      TemplateOptions options = new CloudStackTemplateOptions();
+      assertFalse(options.as(CloudStackTemplateOptions.class)
+         .shouldGenerateSecurityGroup());
+   }
+
+   @Test
+   public void testGenerateSecurityGroup() {
+      TemplateOptions options = new CloudStackTemplateOptions().generateSecurityGroup(true);
+      assertTrue(options.as(CloudStackTemplateOptions.class)
+         .shouldGenerateSecurityGroup());
+   }
+
+   @Test
+   public void testGenerateSecurityGroupStatic() {
+      TemplateOptions options = generateSecurityGroup(true);
+      assertTrue(options.as(CloudStackTemplateOptions.class)
+         .shouldGenerateSecurityGroup());
+   }
+
+   @Test
    public void testDefaultNetworkIds() {
       TemplateOptions options = new CloudStackTemplateOptions();
       assertEquals(options.as(CloudStackTemplateOptions.class).getNetworkIds(), ImmutableSet.of());
@@ -171,6 +194,27 @@ public class CloudStackTemplateOptionsTest {
    }
 
    @Test
+   public void testGenerateKeyPairDefaultsFalse() {
+      TemplateOptions options = new CloudStackTemplateOptions();
+      assertFalse(options.as(CloudStackTemplateOptions.class)
+         .shouldGenerateKeyPair());
+   }
+
+   @Test
+   public void testGenerateKeyPair() {
+      TemplateOptions options = new CloudStackTemplateOptions().generateKeyPair(true);
+      assertTrue(options.as(CloudStackTemplateOptions.class)
+         .shouldGenerateKeyPair());
+   }
+
+   @Test
+   public void testGenerateKeyPairStatic() {
+      TemplateOptions options = generateKeyPair(true);
+      assertTrue(options.as(CloudStackTemplateOptions.class)
+         .shouldGenerateKeyPair());
+   }
+
+   @Test
    public void testKeyPair() {
       TemplateOptions options = keyPair("test");
       assertEquals(options.as(CloudStackTemplateOptions.class).getKeyPair(), "test");

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClientTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClientTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClientTest.java
index 516d8f1..4240530 100644
--- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClientTest.java
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClientTest.java
@@ -104,6 +104,24 @@ public class SecurityGroupAsyncClientTest extends BaseCloudStackAsyncClientTest<
 
    }
 
+   public void testGetSecurityGroupByName() throws SecurityException, NoSuchMethodException, IOException {
+      Invokable<?, ?> method = method(SecurityGroupAsyncClient.class, "getSecurityGroupByName", String.class);
+      GeneratedHttpRequest httpRequest = processor.createRequest(method, ImmutableList.<Object> of("some-name"));
+
+      assertRequestLineEquals(httpRequest,
+            "GET http://localhost:8080/client/api?response=json&command=listSecurityGroups&listAll=true&securitygroupname=some-name HTTP/1.1");
+      assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\n");
+      assertPayloadEquals(httpRequest, null, null, false);
+
+      assertResponseParserClassEquals(method, httpRequest,
+            Functions.compose(IdentityFunction.INSTANCE, IdentityFunction.INSTANCE).getClass());
+      assertSaxResponseParserClassEquals(method, null);
+      assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
+
+      checkFilters(httpRequest);
+
+   }
+
    public void testCreateSecurityGroup() throws SecurityException, NoSuchMethodException, IOException {
       Invokable<?, ?> method = method(SecurityGroupAsyncClient.class, "createSecurityGroup", String.class);
       GeneratedHttpRequest httpRequest = processor.createRequest(method, ImmutableList.<Object> of("goo"));

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupClientLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupClientLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupClientLiveTest.java
index f2d9d3f..f5cb8ed 100644
--- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupClientLiveTest.java
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/features/SecurityGroupClientLiveTest.java
@@ -190,8 +190,10 @@ public class SecurityGroupClientLiveTest extends BaseCloudStackClientLiveTest {
 
    protected void checkGroup(SecurityGroup group) {
       // http://bugs.cloud.com/show_bug.cgi?id=8968
-      if (group.getIngressRules().size() <= 1)
+      if (group.getIngressRules().size() <= 1) {
          assertEquals(group, client.getSecurityGroupClient().getSecurityGroup(group.getId()));
+         assertEquals(group, client.getSecurityGroupClient().getSecurityGroupByName(group.getName()));
+      }
       assert group.getId() != null : group;
       assert group.getName() != null : group;
       assert group.getAccount() != null : group;

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeededTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeededTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeededTest.java
new file mode 100644
index 0000000..e131911
--- /dev/null
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeededTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.cloudstack.functions;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
+import static org.jclouds.util.Predicates2.retry;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.testng.Assert.assertEquals;
+
+import java.net.UnknownHostException;
+import javax.inject.Singleton;
+
+import org.jclouds.cloudstack.CloudStackClient;
+import org.jclouds.cloudstack.domain.IngressRule;
+import org.jclouds.cloudstack.domain.SecurityGroup;
+import org.jclouds.cloudstack.domain.Zone;
+import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
+import org.jclouds.cloudstack.features.AsyncJobClient;
+import org.jclouds.cloudstack.features.SecurityGroupClient;
+import org.jclouds.cloudstack.features.ZoneClient;
+import org.jclouds.cloudstack.functions.ZoneIdToZone;
+import org.jclouds.cloudstack.predicates.JobComplete;
+import org.jclouds.cloudstack.suppliers.ZoneIdToZoneSupplier;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Provides;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
+
+/**
+ * @author Andrew Bayer
+ */
+@Test(groups = "unit", testName = "CreateSecurityGroupIfNeededTest")
+public class CreateSecurityGroupIfNeededTest {
+
+   @Test
+   public void testApply() throws UnknownHostException {
+      final CloudStackClient client = createMock(CloudStackClient.class);
+      SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
+      ZoneClient zoneClient = createMock(ZoneClient.class);
+      AsyncJobClient jobClient = createMock(AsyncJobClient.class);
+      
+      SecurityGroup group = createMock(SecurityGroup.class);
+      
+      Zone zone = createMock(Zone.class);
+
+      expect(group.getIngressRules()).andReturn(ImmutableSet.<IngressRule> of());
+      expect(group.getId()).andReturn("sec-1234").anyTimes();
+      expect(zone.isSecurityGroupsEnabled()).andReturn(true);
+      
+      expect(client.getSecurityGroupClient()).andReturn(secClient)
+         .anyTimes();
+      expect(client.getZoneClient()).andReturn(zoneClient);
+      expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
+
+      expect(zoneClient.getZone("zone-abc1")).andReturn(zone);
+      expect(secClient.createSecurityGroup("group-1")).andReturn(group);
+      expect(secClient.authorizeIngressPortsToCIDRs("sec-1234",
+                                                    "TCP",
+                                                    22,
+                                                    22,
+                                                    ImmutableSet.of("0.0.0.0/0"))).andReturn("job-1234");
+
+      replay(client, secClient, zoneClient, zone, group);
+
+      ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
+         .zone("zone-abc1")
+         .name("group-1")
+         .ports(ImmutableSet.of(22))
+         .cidrs(ImmutableSet.<String> of()).build();
+      
+      CreateSecurityGroupIfNeeded parser = Guice.createInjector(new AbstractModule() {
+
+            @Override
+            protected void configure() {
+               bind(new TypeLiteral<Supplier<String>>() {
+                  }).toInstance(Suppliers.ofInstance("1"));
+               bind(CloudStackClient.class).toInstance(client);
+               bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
+                  to(ZoneIdToZone.class);
+               bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
+                  to(ZoneIdToZoneSupplier.class);
+               bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
+            }
+            
+            @Provides
+            @Singleton
+            protected Predicate<String> jobComplete(JobComplete jobComplete) {
+               return retry(jobComplete, 1200, 1, 5, SECONDS);
+            }
+            
+         }).getInstance(CreateSecurityGroupIfNeeded.class);
+      
+      assertEquals(parser.apply(input), group);
+
+      verify(client, secClient, zoneClient, zone, group);
+   }
+
+   
+   @Test
+   public void testApplyGroupAlreadyExists() throws UnknownHostException {
+      final CloudStackClient client = createMock(CloudStackClient.class);
+      SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
+      ZoneClient zoneClient = createMock(ZoneClient.class);
+      AsyncJobClient jobClient = createMock(AsyncJobClient.class);
+      
+      SecurityGroup group = createMock(SecurityGroup.class);
+      
+      Zone zone = createMock(Zone.class);
+
+      expect(group.getId()).andReturn("sec-1234").anyTimes();
+      expect(zone.isSecurityGroupsEnabled()).andReturn(true);
+      
+      expect(client.getSecurityGroupClient()).andReturn(secClient)
+         .anyTimes();
+      expect(client.getZoneClient()).andReturn(zoneClient);
+      expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
+
+      expect(zoneClient.getZone("zone-abc2")).andReturn(zone);
+      expect(secClient.createSecurityGroup("group-1")).andThrow(new IllegalStateException());
+      expect(secClient.getSecurityGroupByName("group-1")).andReturn(group);
+
+      replay(client, secClient, zoneClient, zone, group);
+
+      ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
+         .zone("zone-abc2")
+         .name("group-1")
+         .ports(ImmutableSet.of(22))
+         .cidrs(ImmutableSet.<String> of()).build();
+      
+      CreateSecurityGroupIfNeeded parser = Guice.createInjector(new AbstractModule() {
+
+            @Override
+            protected void configure() {
+               bind(new TypeLiteral<Supplier<String>>() {
+                  }).toInstance(Suppliers.ofInstance("1"));
+               bind(CloudStackClient.class).toInstance(client);
+               bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
+                  to(ZoneIdToZone.class);
+               bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
+                  to(ZoneIdToZoneSupplier.class);
+               bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
+            }
+            
+            @Provides
+            @Singleton
+            protected Predicate<String> jobComplete(JobComplete jobComplete) {
+               return retry(jobComplete, 1200, 1, 5, SECONDS);
+            }
+            
+         }).getInstance(CreateSecurityGroupIfNeeded.class);
+      
+      assertEquals(parser.apply(input), group);
+
+      verify(client, secClient, zoneClient, zone, group);
+   }
+
+   @Test(expectedExceptions = IllegalArgumentException.class)
+   public void testApplyZoneNoSecurityGroups() throws UnknownHostException {
+      final CloudStackClient client = createMock(CloudStackClient.class);
+      SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
+      ZoneClient zoneClient = createMock(ZoneClient.class);
+      AsyncJobClient jobClient = createMock(AsyncJobClient.class);
+      
+      SecurityGroup group = createMock(SecurityGroup.class);
+      
+      Zone zone = createMock(Zone.class);
+
+      expect(zone.isSecurityGroupsEnabled()).andReturn(false);
+      
+      expect(client.getZoneClient()).andReturn(zoneClient);
+
+      expect(zoneClient.getZone("zone-abc3")).andReturn(zone);
+
+      replay(client, zoneClient, zone);
+
+      ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
+         .zone("zone-abc3")
+         .name("group-1")
+         .ports(ImmutableSet.of(22))
+         .cidrs(ImmutableSet.<String> of()).build();
+      
+      CreateSecurityGroupIfNeeded parser = Guice.createInjector(new AbstractModule() {
+
+            @Override
+            protected void configure() {
+               bind(new TypeLiteral<Supplier<String>>() {
+                  }).toInstance(Suppliers.ofInstance("1"));
+               bind(CloudStackClient.class).toInstance(client);
+               bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
+                  to(ZoneIdToZone.class);
+               bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
+                  to(ZoneIdToZoneSupplier.class);
+               bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
+            }
+            
+            @Provides
+            @Singleton
+            protected Predicate<String> jobComplete(JobComplete jobComplete) {
+               return retry(jobComplete, 1200, 1, 5, SECONDS);
+            }
+            
+         }).getInstance(CreateSecurityGroupIfNeeded.class);
+
+      assertEquals(parser.apply(input), group);
+
+      verify(client, zoneClient, zone);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java
index 9d24257..8b8d01d 100644
--- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/internal/BaseCloudStackComputeServiceContextExpectTest.java
@@ -134,6 +134,10 @@ public abstract class BaseCloudStackComputeServiceContextExpectTest<T> extends B
       .payload(payloadFromResource("/listnetworksresponse.json"))
       .build();   
    
+   protected final HttpResponse listNetworksWithSecurityGroupsResponse = HttpResponse.builder().statusCode(200)
+      .payload(payloadFromResource("/listnetworksresponse-2.json"))
+      .build();   
+
    protected final HttpRequest getZone = HttpRequest.builder().method("GET")
       .endpoint("http://localhost:8080/client/api")
       .addQueryParam("response", "json")
@@ -148,6 +152,21 @@ public abstract class BaseCloudStackComputeServiceContextExpectTest<T> extends B
    protected final HttpResponse getZoneResponse = HttpResponse.builder().statusCode(200)
       .payload(payloadFromResource("/getzoneresponse.json"))
       .build();
+
+   protected final HttpRequest getZoneWithSecurityGroups = HttpRequest.builder().method("GET")
+      .endpoint("http://localhost:8080/client/api")
+      .addQueryParam("response", "json")
+      .addQueryParam("command", "listZones")
+      .addQueryParam("listAll", "true")
+      .addQueryParam("id", "2")
+      .addQueryParam("apiKey", "APIKEY")
+      .addQueryParam("signature", "FnYX25207fVLLRz5GhOfRrWuUek%3D")
+      .addHeader("Accept", "application/json")
+      .build();
+
+   protected final HttpResponse getZoneWithSecurityGroupsResponse = HttpResponse.builder().statusCode(200)
+      .payload(payloadFromResource("/getzoneresponse-2.json"))
+      .build();
    
    protected final HttpRequest listCapabilities = HttpRequest.builder().method("GET")
       .endpoint("http://localhost:8080/client/api")
@@ -163,4 +182,64 @@ public abstract class BaseCloudStackComputeServiceContextExpectTest<T> extends B
       .payload(payloadFromResource("/listcapabilitiesresponse.json"))
       .build();   
          
+   protected final HttpRequest getSecurityGroup = HttpRequest.builder().method("GET")
+      .endpoint("http://localhost:8080/client/api")
+      .addQueryParam("response", "json")
+      .addQueryParam("command", "listSecurityGroups")
+      .addQueryParam("listAll", "true")      
+      .addQueryParam("securitygroupname", "jclouds-test")      
+      .addQueryParam("apiKey", "APIKEY")      
+      .addQueryParam("signature", "zGp2rfHY6fBIGkgODRxyNzFfPFI%3D")
+      .addHeader("Accept", "application/json")
+      .build();
+
+   protected final HttpResponse getSecurityGroupResponse = HttpResponse.builder().statusCode(200)
+      .payload(payloadFromResource("/getsecuritygroupresponse.json"))
+      .build();   
+
+   protected final HttpRequest createSecurityGroup = HttpRequest.builder().method("GET")
+      .endpoint("http://localhost:8080/client/api")
+      .addQueryParam("response", "json")
+      .addQueryParam("command", "createSecurityGroup")
+      .addQueryParam("name", "jclouds-test")      
+      .addQueryParam("apiKey", "APIKEY")      
+      .addQueryParam("signature", "BdgmqGsvjPmP4PxsEKEpq6buwuA%3D")
+      .addHeader("Accept", "application/json")
+      .build();
+
+   protected final HttpResponse createSecurityGroupResponse = HttpResponse.builder().statusCode(200)
+      .payload(payloadFromResource("/createsecuritygroupresponse.json"))
+      .build();   
+
+   protected final HttpRequest authorizeIngress = HttpRequest.builder().method("GET")
+      .endpoint("http://localhost:8080/client/api")
+      .addQueryParam("response", "json")
+      .addQueryParam("command", "authorizeSecurityGroupIngress")
+      .addQueryParam("securitygroupid", "30")      
+      .addQueryParam("protocol", "TCP")
+      .addQueryParam("startport", "22")
+      .addQueryParam("endport", "22")
+      .addQueryParam("cidrlist", "0.0.0.0/0")
+      .addQueryParam("apiKey", "APIKEY")      
+      .addQueryParam("signature", "GVtXzAl/Q7z4wnvKEHtdV0lxv2o%3D")
+      .addHeader("Accept", "application/json")
+      .build();
+
+   protected final HttpResponse authorizeIngressResponse = HttpResponse.builder().statusCode(200)
+      .payload(payloadFromResource("/authorizesecuritygroupingressresponse.json"))
+      .build();   
+
+   protected final HttpRequest createSSHKeyPair = HttpRequest.builder().method("GET")
+      .endpoint("http://localhost:8080/client/api")
+      .addQueryParam("response", "json")
+      .addQueryParam("command", "createSSHKeyPair")
+      .addQueryParam("name", "jclouds-test")      
+      .addQueryParam("apiKey", "APIKEY")      
+      .addQueryParam("signature", "er6YjvUjPFwxy/x/aAVNW9Z8yo8%3D")
+      .addHeader("Accept", "application/json")
+      .build();
+
+   protected final HttpResponse createSSHKeyPairResponse = HttpResponse.builder().statusCode(200)
+      .payload(payloadFromResource("/createsshkeypairresponse-2.json"))
+      .build();   
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicatesTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicatesTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicatesTest.java
new file mode 100644
index 0000000..16e473a
--- /dev/null
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicatesTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.cloudstack.predicates;
+
+import static org.jclouds.cloudstack.predicates.SecurityGroupPredicates.portInRange;
+import static org.jclouds.cloudstack.predicates.SecurityGroupPredicates.hasCidr;
+import static org.jclouds.cloudstack.predicates.SecurityGroupPredicates.portInRangeForCidr;
+import static org.jclouds.cloudstack.predicates.SecurityGroupPredicates.nameEquals;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.jclouds.cloudstack.domain.IngressRule;
+import org.jclouds.cloudstack.domain.SecurityGroup;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * @author Andrew Bayer
+ */
+@Test(groups = "unit")
+public class SecurityGroupPredicatesTest {
+
+   public SecurityGroup group() {
+      return SecurityGroup
+         .builder()
+         .id("13")
+         .name("default")
+         .description("description")
+         .account("adrian")
+         .domainId("1")
+         .domain("ROOT")
+         .ingressRules(
+                       ImmutableSet.of(
+                                       
+                                       IngressRule.builder().id("5").protocol("tcp").startPort(22).endPort(22)
+                                       .securityGroupName("adriancole").account("adrian").build(),
+                                       IngressRule.builder().id("6").protocol("udp").startPort(11).endPort(11).CIDR("1.1.1.1/24").build(),
+                                       IngressRule.builder().id("7").protocol("tcp").startPort(40).endPort(50).CIDR("1.1.1.1/24").build(),
+                                       IngressRule.builder().id("8").protocol("tcp").startPort(60).endPort(60).CIDR("2.2.2.2/16").build()
+                                       )).build();
+   }
+
+   @Test
+   public void testPortInRange() {
+      assertTrue(portInRange(22).apply(group()));
+      assertTrue(portInRange(45).apply(group()));
+      assertFalse(portInRange(100).apply(group()));
+   }
+
+   @Test
+   public void testHasCidr() {
+      assertTrue(hasCidr("1.1.1.1/24").apply(group()));
+      assertFalse(hasCidr("3.3.3.3/25").apply(group()));
+   }
+
+   @Test
+   public void testPortInRangeForCidr() {
+      assertTrue(portInRangeForCidr(11, "1.1.1.1/24").apply(group()));
+      assertTrue(portInRangeForCidr(45, "1.1.1.1/24").apply(group()));
+      assertFalse(portInRangeForCidr(45, "2.2.2.2/16").apply(group()));
+      assertFalse(portInRangeForCidr(11, "2.2.2.2/16").apply(group()));
+      assertFalse(portInRangeForCidr(11, "3.3.3.3/25").apply(group()));
+   }
+   
+   @Test
+   public void testNameEquals() {
+      assertTrue(nameEquals("default").apply(group()));
+      assertFalse(nameEquals("not-default").apply(group()));
+   }
+   
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/resources/authorizesecuritygroupingressresponse.json
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/resources/authorizesecuritygroupingressresponse.json b/apis/cloudstack/src/test/resources/authorizesecuritygroupingressresponse.json
new file mode 100644
index 0000000..e0280e4
--- /dev/null
+++ b/apis/cloudstack/src/test/resources/authorizesecuritygroupingressresponse.json
@@ -0,0 +1,2 @@
+ { "authorizesecuritygroupingressresponse" :
+ {"jobid":"13330fc9-8b3e-4582-aa3e-90883c041010"} }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/resources/createsecuritygroupresponse.json
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/resources/createsecuritygroupresponse.json b/apis/cloudstack/src/test/resources/createsecuritygroupresponse.json
new file mode 100644
index 0000000..99a2ad2
--- /dev/null
+++ b/apis/cloudstack/src/test/resources/createsecuritygroupresponse.json
@@ -0,0 +1,3 @@
+{ "listsecuritygroupsresponse" : { "securitygroup" : 
+{"id":30,"name":"jclouds-test","account":"adrian","domainid":1,"domain":"ROOT"}
+ } }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/resources/createsshkeypairresponse-2.json
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/resources/createsshkeypairresponse-2.json b/apis/cloudstack/src/test/resources/createsshkeypairresponse-2.json
new file mode 100644
index 0000000..3dfb3b4
--- /dev/null
+++ b/apis/cloudstack/src/test/resources/createsshkeypairresponse-2.json
@@ -0,0 +1,4 @@
+{ "createsshkeypairresponse" :  { "keypair" : {
+   "name":"jclouds-test",
+   "fingerprint":"1c:06:74:52:3b:99:1c:95:5c:04:c2:f4:ba:77:6e:7b",
+   "privatekey":"-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDZo/EF4Ew1uEW0raz7vCs28lBwy0UKV2Xr606gaEgxO7h9mSXZ\n4x2K/KQ1NMnrbjppxGycLh9EKPWAO3ezFULAyuOZW4Fy+xRS8+3MAijxBJY/KBgl\nx5rJm2ILumRkTNkMlLGCSBb9SOqYRN1VpOy7kn3StzU9LdJ/snKVE2JLHQIDAQAB\nAoGBAMnL5okKRd9xcsBqYIAxIuiZmNhcwTErhEdRMOAukPGFbDSYsa3rldLvGdpz\njd2LoQG8rO/LHBZ429kASqZzyiV+NvcgH+tFNJSVAigjSICfhEKF9PY2TiAkrg7S\nGyJgAjpPWQc2sQh0dE8EPEtBiq4ibXfMTDmbs1d/vnfdwtQJAkEA+AX5Y+xgWj74\ndYETmNLyLhNZpftLizEfIYj7lCVhsbFwVb8jbM1m8n8bxwGjls1w/ico1CWcQna+\nUnAfA8kJvwJBAOCj0YgDKpYd0OLQhvI3212J9QcQpJEkDOTYiMwXNHCNMKRpoF47\nMPPX+GG8YzUiQAi9/OG4pDKCjzQWE/ebiiMCQQCssnQ5WICqtggIwYykr9VDseON\nSFIMpHJ5xkjumazRrqx6eDGxc8BH/6uWwRRoT7pqrVeniFyqhsX03u8pkpU/AkBj\nWfCcwBHArNUqy2EzlWKuvwogosq16oTNXbs60HR/5uIBhTnJE1K2NemDiGc0I77A\nXw6N4jS0piuhtLYGB8OTAkEA50abdbduXWcr62Z6E8G/6LNFaNg0uBuVgwSHtJMd\ndNeUtVDHQCHSf3tvxXTAtaB9PCnGOfgm/dyYWEMf3rMoHQ==\n-----END RSA PRIVATE KEY-----\n"} }  }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/resources/getsecuritygroupresponse.json
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/resources/getsecuritygroupresponse.json b/apis/cloudstack/src/test/resources/getsecuritygroupresponse.json
new file mode 100644
index 0000000..27743ed
--- /dev/null
+++ b/apis/cloudstack/src/test/resources/getsecuritygroupresponse.json
@@ -0,0 +1 @@
+{ "listsecuritygroupsresponse" : { count: 0, "securitygroup" : [ { } ] } }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/resources/getzoneresponse-2.json
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/resources/getzoneresponse-2.json b/apis/cloudstack/src/test/resources/getzoneresponse-2.json
new file mode 100644
index 0000000..7261716
--- /dev/null
+++ b/apis/cloudstack/src/test/resources/getzoneresponse-2.json
@@ -0,0 +1 @@
+{ "listzonesresponse" : { "zone" : [  {"id":2,"name":"Chicago","networktype":"Advanced","securitygroupsenabled":true} ] } }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/resources/listnetworksresponse-2.json
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/resources/listnetworksresponse-2.json b/apis/cloudstack/src/test/resources/listnetworksresponse-2.json
new file mode 100644
index 0000000..2f10824
--- /dev/null
+++ b/apis/cloudstack/src/test/resources/listnetworksresponse-2.json
@@ -0,0 +1 @@
+{ "listnetworksresponse" : { "network" : [  {"id":204,"name":"Virtual Network","displaytext":"A dedicated virtualized network for your account.  The broadcast domain is contained within a VLAN and all public network access is routed out by a virtual router.","broadcastdomaintype":"Vlan","traffictype":"Guest","zoneid":2,"networkofferingid":6,"networkofferingname":"DefaultVirtualizedNetworkOffering","networkofferingdisplaytext":"Virtual Vlan","networkofferingavailability":"Required","isshared":false,"issystem":false,"state":"Implemented","related":204,"broadcasturi":"vlan://240","dns1":"8.8.8.8","type":"Virtual","account":"adrian","domainid":1,"domain":"ROOT","isdefault":true,"service":[{"name":"Vpn","capability":[{"name":"SupportedVpnTypes","value":"pptp,l2tp,ipsec"}]},{"name":"Gateway"},{"name":"UserData"},{"name":"Dhcp"},{"name":"Firewall","capability":[{"name":"SupportedSourceNatTypes","value":"per account"},{"name":"StaticNat","value":"true"},{"name":"TrafficStatistics","value":"
 per public ip"},{"name":"PortForwarding","value":"true"},{"name":"MultipleIps","value":"true"},{"name":"SupportedProtocols","value":"tcp,udp"}]},{"name":"Dns"},{"name":"Lb","capability":[{"name":"SupportedLbAlgorithms","value":"roundrobin,leastconn,source"},{"name":"SupportedProtocols","value":"tcp, udp"}]}],"networkdomain":"cs3cloud.internal"} ] } }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/resources/queryasyncjobresultresponse-authorizeingress.json
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/resources/queryasyncjobresultresponse-authorizeingress.json b/apis/cloudstack/src/test/resources/queryasyncjobresultresponse-authorizeingress.json
new file mode 100644
index 0000000..7ee489d
--- /dev/null
+++ b/apis/cloudstack/src/test/resources/queryasyncjobresultresponse-authorizeingress.json
@@ -0,0 +1,34 @@
+{
+    "queryasyncjobresultresponse": {
+        "accountid": 3,
+        "userid": 3,
+        "cmd": "com.cloud.api.commands.AuthorizeSecurityGroupIngressCmd",
+        "jobstatus": 1,
+        "jobprocstatus": 0,
+        "jobresultcode": 0,
+        "jobresulttype": "object",
+        "jobresult": {
+            "securitygroup": {
+                "id": "30",
+                "name": "jclouds-test",
+                "account": "adrian",
+                "domainid": "1",
+                "domain": "ROOT",
+                "ingressrule": [
+                    {
+                        "ruleid": "35bb2ad4-e0cb-48a0-9534-fc4a067b5665",
+                        "protocol": "tcp",
+                        "startport": 22,
+                        "endport": 22,
+                        "cidr": "0.0.0.0/0"
+                    }
+                ],
+                "egressrule": [
+                    
+                ]
+            }
+        },
+        "jobid": "13330fc9-8b3e-4582-aa3e-90883c041010",
+        "created": "2013-06-04T14:37:26-0700"
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/resources/queryasyncjobresultresponse-virtualmachine-securitygroup.json
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/resources/queryasyncjobresultresponse-virtualmachine-securitygroup.json b/apis/cloudstack/src/test/resources/queryasyncjobresultresponse-virtualmachine-securitygroup.json
new file mode 100644
index 0000000..cc75e11
--- /dev/null
+++ b/apis/cloudstack/src/test/resources/queryasyncjobresultresponse-virtualmachine-securitygroup.json
@@ -0,0 +1,73 @@
+{
+    "queryasyncjobresultresponse": {
+            "jobid": 50006,
+            "accountid": 3,
+            "userid": 3,
+            "cmd": "com.cloud.api.commands.DeployVMCmd",
+            "jobstatus": 1,
+            "jobprocstatus": 0,
+            "jobresultcode": 0,
+            "jobresult": {
+                "virtualmachine": {
+                    "id": 1234,
+                    "name": "i-3-218-VM",
+                    "displayname": "i-3-218-VM",
+                    "account": "adrian",
+                    "domainid": 1,
+                    "domain": "ROOT",
+                    "created": "2011-02-27T08:39:10-0800",
+                    "state": "Running",
+                    "haenable": false,
+                    "zoneid": 1,
+                    "zonename": "San Jose 1",
+                    "templateid": 203,
+                    "templatename": "Centos 5.3 Password Managed",
+                    "templatedisplaytext": "Centos 5.3 Password Managed",
+                    "passwordenabled": true,
+                    "serviceofferingid": 1,
+                    "serviceofferingname": "Small Instance",
+                    "cpunumber": 1,
+                    "cpuspeed": 500,
+                    "memory": 512,
+                    "guestosid": 12,
+                    "rootdeviceid": 0,
+                    "rootdevicetype": "NetworkFilesystem",
+                    "securitygroup": [
+                        {
+                            "id": "30",
+                            "name": "jclouds-test",
+                            "account": "adrian",
+                            "domainid": "1",
+                            "domain": "ROOT",
+                            "ingressrule": [
+                                {
+                                    "ruleid": "35bb2ad4-e0cb-48a0-9534-fc4a067b5665",
+                                    "protocol": "tcp",
+                                    "startport": 22,
+                                    "endport": 22,
+                                    "cidr": "0.0.0.0/0"
+                                }
+                            ],
+                            "egressrule": [
+                                
+                            ]
+                        }
+                    ],
+                    "password": "dD7jwajkh",
+                    "nic": [{
+                        "id": 250,
+                        "networkid": 204,
+                        "netmask": "255.255.255.0",
+                        "gateway": "10.1.1.1",
+                        "ipaddress": "10.1.1.195",
+                        "traffictype": "Guest",
+                        "type": "Virtual",
+                        "isdefault": true
+                    }],
+                    "hypervisor": "XenServer"
+                }
+            },
+            "created": "2011-02-27T08:39:10-0800"
+        }
+    
+}


[3/3] git commit: JCLOUDS-106. Add proper support for generation/detection of keypair/security groups to CloudStack.

Posted by ab...@apache.org.
JCLOUDS-106. Add proper support for generation/detection of keypair/security groups to CloudStack.


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

Branch: refs/heads/master
Commit: fbe637c8bfb951532a48b6f70eff0dc739654ee9
Parents: 7651657
Author: Andrew Bayer <an...@gmail.com>
Authored: Sun Jun 2 12:23:19 2013 -0700
Committer: Andrew Bayer <an...@gmail.com>
Committed: Thu Jun 6 10:26:20 2013 -0700

----------------------------------------------------------------------
 .../jclouds/cloudstack/CloudStackApiMetadata.java  |    2 +
 .../compute/CloudStackComputeService.java          |  185 ++++++++++
 .../CloudStackComputeServiceContextModule.java     |   48 +++
 .../compute/functions/OrphanedGroupsByZoneId.java  |   77 +++++
 .../compute/loaders/CreateUniqueKeyPair.java       |   69 ++++
 .../compute/loaders/FindSecurityGroupOrCreate.java |   83 +++++
 .../compute/options/CloudStackTemplateOptions.java |   50 +++-
 .../predicates/AllNodesInGroupTerminated.java      |   56 +++
 .../strategy/CloudStackComputeServiceAdapter.java  |   59 +++-
 .../cloudstack/config/CloudStackProperties.java    |    5 +
 .../org/jclouds/cloudstack/domain/ZoneAndName.java |   97 ++++++
 .../domain/ZoneSecurityGroupNamePortsCidrs.java    |  163 +++++++++
 .../features/SecurityGroupAsyncClient.java         |   12 +
 .../cloudstack/features/SecurityGroupClient.java   |    9 +
 .../functions/CreateSecurityGroupIfNeeded.java     |  124 +++++++
 .../predicates/SecurityGroupPredicates.java        |  161 +++++++++
 .../predicates/SshKeyPairPredicates.java           |   78 +++++
 .../CloudStackComputeServiceAdapterExpectTest.java |  185 ++++++++++-
 .../compute/loaders/CreateUniqueKeyPairTest.java   |   73 ++++
 .../loaders/FindSecurityGroupOrCreateTest.java     |  261 +++++++++++++++
 .../options/CloudStackTemplateOptionsTest.java     |   44 +++
 .../features/SecurityGroupAsyncClientTest.java     |   18 +
 .../features/SecurityGroupClientLiveTest.java      |    4 +-
 .../functions/CreateSecurityGroupIfNeededTest.java |  237 +++++++++++++
 ...eCloudStackComputeServiceContextExpectTest.java |   79 +++++
 .../predicates/SecurityGroupPredicatesTest.java    |   90 +++++
 .../authorizesecuritygroupingressresponse.json     |    2 +
 .../resources/createsecuritygroupresponse.json     |    3 +
 .../test/resources/createsshkeypairresponse-2.json |    4 +
 .../test/resources/getsecuritygroupresponse.json   |    1 +
 .../src/test/resources/getzoneresponse-2.json      |    1 +
 .../src/test/resources/listnetworksresponse-2.json |    1 +
 ...eryasyncjobresultresponse-authorizeingress.json |   34 ++
 ...esultresponse-virtualmachine-securitygroup.json |   73 ++++
 34 files changed, 2369 insertions(+), 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApiMetadata.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApiMetadata.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApiMetadata.java
index 652a6f0..53b9ed2 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApiMetadata.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/CloudStackApiMetadata.java
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 package org.jclouds.cloudstack;
+import static org.jclouds.cloudstack.config.CloudStackProperties.AUTO_GENERATE_KEYPAIRS;
 import static org.jclouds.reflect.Reflection2.typeToken;
 
 import java.net.URI;
@@ -62,6 +63,7 @@ public class CloudStackApiMetadata extends BaseRestApiMetadata {
       Properties properties = BaseRestApiMetadata.defaultProperties();
       properties.setProperty("jclouds.ssh.max-retries", "7");
       properties.setProperty("jclouds.ssh.retry-auth", "true");
+      properties.setProperty(AUTO_GENERATE_KEYPAIRS, "false");
       return properties;
    }
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/CloudStackComputeService.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/CloudStackComputeService.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/CloudStackComputeService.java
new file mode 100644
index 0000000..bb17cac
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/CloudStackComputeService.java
@@ -0,0 +1,185 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds 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.cloudstack.compute;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
+import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
+import static org.jclouds.cloudstack.predicates.SshKeyPairPredicates.nameMatches;
+import static org.jclouds.cloudstack.predicates.ZonePredicates.supportsSecurityGroups;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.ComputeServiceContext;
+import org.jclouds.compute.callables.RunScriptOnNode;
+import org.jclouds.compute.domain.Hardware;
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.domain.TemplateBuilder;
+import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.functions.GroupNamingConvention;
+import org.jclouds.compute.internal.BaseComputeService;
+import org.jclouds.compute.internal.PersistNodeCredentials;
+import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
+import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
+import org.jclouds.compute.strategy.DestroyNodeStrategy;
+import org.jclouds.compute.strategy.GetImageStrategy;
+import org.jclouds.compute.strategy.GetNodeMetadataStrategy;
+import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap;
+import org.jclouds.compute.strategy.ListNodesStrategy;
+import org.jclouds.compute.strategy.RebootNodeStrategy;
+import org.jclouds.compute.strategy.ResumeNodeStrategy;
+import org.jclouds.compute.strategy.SuspendNodeStrategy;
+import org.jclouds.domain.Credentials;
+import org.jclouds.domain.Location;
+import org.jclouds.cloudstack.CloudStackClient;
+import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
+import org.jclouds.cloudstack.domain.SecurityGroup;
+import org.jclouds.cloudstack.domain.SshKeyPair;
+import org.jclouds.cloudstack.domain.Zone;
+import org.jclouds.cloudstack.domain.ZoneAndName;
+import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
+import org.jclouds.cloudstack.predicates.SecurityGroupPredicates;
+import org.jclouds.scriptbuilder.functions.InitAdminAccess;
+
+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.base.Throwables;
+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;
+
+/**
+ * @author Adrian Cole
+ */
+@Singleton
+public class CloudStackComputeService extends BaseComputeService {
+   protected final CloudStackClient client;
+   protected final LoadingCache<ZoneAndName, SecurityGroup> securityGroupMap;
+   protected final LoadingCache<String, SshKeyPair> keyPairCache;
+   protected final Function<Set<? extends NodeMetadata>, Multimap<String, String>> orphanedGroupsByZoneId;
+   protected final GroupNamingConvention.Factory namingConvention;
+   protected final Supplier<LoadingCache<String, Zone>> zoneIdToZone;
+
+   @Inject
+   protected CloudStackComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
+            @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes,
+            @Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy,
+            GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy,
+            CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy,
+            DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy,
+            SuspendNodeStrategy stopNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider,
+            @Named("DEFAULT") Provider<TemplateOptions> templateOptionsProvider,
+            @Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning,
+            @Named(TIMEOUT_NODE_TERMINATED) Predicate<AtomicReference<NodeMetadata>> nodeTerminated,
+            @Named(TIMEOUT_NODE_SUSPENDED) Predicate<AtomicReference<NodeMetadata>> nodeSuspended,
+            InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory,
+            RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
+            PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
+            @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, CloudStackClient client,
+            LoadingCache<ZoneAndName, SecurityGroup> securityGroupMap,
+            LoadingCache<String, SshKeyPair> keyPairCache,
+            Function<Set<? extends NodeMetadata>, Multimap<String, String>> orphanedGroupsByZoneId,
+            GroupNamingConvention.Factory namingConvention,
+            Supplier<LoadingCache<String, Zone>> zoneIdToZone,
+            Optional<ImageExtension> imageExtension) {
+      super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy,
+               getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
+               startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
+               nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
+               persistNodeCredentials, timeouts, userExecutor, imageExtension);
+      this.zoneIdToZone = zoneIdToZone;
+      this.client = checkNotNull(client, "client");
+      this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap");
+      this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
+      this.orphanedGroupsByZoneId = checkNotNull(orphanedGroupsByZoneId, "orphanedGroupsByZoneId");
+      this.namingConvention = checkNotNull(namingConvention, "namingConvention");
+   }
+
+   @Override
+   protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends NodeMetadata> deadNodes) {
+      Multimap<String, String> zoneToZoneAndGroupNames = orphanedGroupsByZoneId.apply(deadNodes);
+      for (String zoneId : zoneToZoneAndGroupNames.keySet()) {
+         cleanOrphanedGroupsInZone(ImmutableSet.copyOf(zoneToZoneAndGroupNames.get(zoneId)), zoneId);
+      }
+   }
+
+   protected void cleanOrphanedGroupsInZone(Set<String> groups, String zoneId) {
+      cleanupOrphanedSecurityGroupsInZone(groups, zoneId);
+      cleanupOrphanedKeyPairsInZone(groups, zoneId);
+   }
+
+   private void cleanupOrphanedSecurityGroupsInZone(Set<String> groups, String zoneId) {
+      Zone zone = zoneIdToZone.get().getUnchecked(zoneId);
+
+      if (supportsSecurityGroups().apply(zone)) {
+         for (String group : groups) {
+            for (SecurityGroup securityGroup : Iterables.filter(client.getSecurityGroupClient().listSecurityGroups(),
+                     SecurityGroupPredicates.nameMatches(namingConvention.create().containsGroup(group)))) {
+               ZoneAndName zoneAndName = ZoneAndName.fromZoneAndName(zoneId, securityGroup.getName());
+               logger.debug(">> deleting securityGroup(%s)", zoneAndName);
+               client.getSecurityGroupClient().deleteSecurityGroup(securityGroup.getId());
+               // TODO: test this clear happens
+               securityGroupMap.invalidate(zoneAndName);
+               logger.debug("<< deleted securityGroup(%s)", zoneAndName);
+            }
+         }
+      }
+   }
+
+   private void cleanupOrphanedKeyPairsInZone(Set<String> groups, String zoneId) {
+      for (String group : groups) {
+         for (SshKeyPair pair : Iterables.filter(client.getSSHKeyPairClient().listSSHKeyPairs(),
+                                                 nameMatches(namingConvention.create().containsGroup(group)))) {
+            logger.debug(">> deleting keypair(%s)", pair.getName());
+            client.getSSHKeyPairClient().deleteSSHKeyPair(pair.getName());
+            // TODO: test this clear happens
+            keyPairCache.invalidate(pair.getName());
+            logger.debug("<< deleted keypair(%s)", pair.getName());
+         }
+         keyPairCache.invalidate(namingConvention.create().sharedNameForGroup(group));
+      }
+   }
+
+   /**
+    * returns template options, except of type {@link CloudStackTemplateOptions}.
+    */
+   @Override
+   public CloudStackTemplateOptions templateOptions() {
+      return CloudStackTemplateOptions.class.cast(super.templateOptions());
+   }
+   
+   
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
index 2eb613e..b4924be 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
@@ -18,6 +18,7 @@ package org.jclouds.cloudstack.compute.config;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
+import static org.jclouds.cloudstack.config.CloudStackProperties.AUTO_GENERATE_KEYPAIRS;
 import static org.jclouds.util.Predicates2.retry;
 
 import java.util.Map;
@@ -29,11 +30,15 @@ import javax.inject.Named;
 import javax.inject.Singleton;
 
 import org.jclouds.cloudstack.CloudStackClient;
+import org.jclouds.cloudstack.compute.CloudStackComputeService;
+import org.jclouds.cloudstack.compute.functions.OrphanedGroupsByZoneId;
 import org.jclouds.cloudstack.compute.functions.ServiceOfferingToHardware;
 import org.jclouds.cloudstack.compute.functions.TemplateToImage;
 import org.jclouds.cloudstack.compute.functions.TemplateToOperatingSystem;
 import org.jclouds.cloudstack.compute.functions.VirtualMachineToNodeMetadata;
 import org.jclouds.cloudstack.compute.functions.ZoneToLocation;
+import org.jclouds.cloudstack.compute.loaders.CreateUniqueKeyPair;
+import org.jclouds.cloudstack.compute.loaders.FindSecurityGroupOrCreate;
 import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
 import org.jclouds.cloudstack.compute.strategy.AdvancedNetworkOptionsConverter;
 import org.jclouds.cloudstack.compute.strategy.BasicNetworkOptionsConverter;
@@ -44,12 +49,17 @@ import org.jclouds.cloudstack.domain.IPForwardingRule;
 import org.jclouds.cloudstack.domain.Network;
 import org.jclouds.cloudstack.domain.NetworkType;
 import org.jclouds.cloudstack.domain.OSType;
+import org.jclouds.cloudstack.domain.SecurityGroup;
 import org.jclouds.cloudstack.domain.ServiceOffering;
+import org.jclouds.cloudstack.domain.SshKeyPair;
 import org.jclouds.cloudstack.domain.Template;
 import org.jclouds.cloudstack.domain.User;
 import org.jclouds.cloudstack.domain.VirtualMachine;
 import org.jclouds.cloudstack.domain.Zone;
+import org.jclouds.cloudstack.domain.ZoneAndName;
+import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
 import org.jclouds.cloudstack.features.GuestOSClient;
+import org.jclouds.cloudstack.functions.CreateSecurityGroupIfNeeded;
 import org.jclouds.cloudstack.functions.GetFirewallRulesByVirtualMachine;
 import org.jclouds.cloudstack.functions.GetIPForwardingRulesByVirtualMachine;
 import org.jclouds.cloudstack.functions.StaticNATVirtualMachineInNetwork;
@@ -77,8 +87,12 @@ import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.inject.Injector;
+import com.google.inject.Key;
 import com.google.inject.Provides;
 import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
 
 /**
@@ -111,12 +125,32 @@ public class CloudStackComputeServiceContextModule extends
       }).to(GetFirewallRulesByVirtualMachine.class);
       bind(new TypeLiteral<CacheLoader<String, Zone>>() {
       }).to(ZoneIdToZone.class);
+      bind(new TypeLiteral<CacheLoader<String, SshKeyPair>>() {
+      }).to(CreateUniqueKeyPair.class);
       bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {
       }).to(ZoneIdToZoneSupplier.class);
+
+      bind(new TypeLiteral<Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup>>() {
+      }).to(CreateSecurityGroupIfNeeded.class);
+
+      bind(new TypeLiteral<CacheLoader<ZoneAndName, SecurityGroup>>() {
+      }).to(FindSecurityGroupOrCreate.class);
+
+      bind(new TypeLiteral<Function<Set<? extends NodeMetadata>,  Multimap<String, String>>>() {
+      }).to(OrphanedGroupsByZoneId.class);
+
       // to have the compute service adapter override default locations
       install(new LocationsFromComputeServiceAdapterModule<VirtualMachine, ServiceOffering, Template, Zone>(){});
    }
    
+
+   @Override
+   protected TemplateOptions provideTemplateOptions(Injector injector, TemplateOptions options) {
+      return options.as(CloudStackTemplateOptions.class)
+         .generateKeyPair(injector.getInstance(
+                  Key.get(boolean.class, Names.named(AUTO_GENERATE_KEYPAIRS))));
+   }
+
    @Provides
    @Singleton
    @Memoized
@@ -187,6 +221,20 @@ public class CloudStackComputeServiceContextModule extends
 
    @Provides
    @Singleton
+   protected LoadingCache<String, SshKeyPair> keyPairMap(
+         CacheLoader<String, SshKeyPair> in) {
+      return CacheBuilder.newBuilder().build(in);
+   }
+
+   @Provides
+   @Singleton
+   protected LoadingCache<ZoneAndName, SecurityGroup> securityGroupMap(
+            CacheLoader<ZoneAndName, SecurityGroup> in) {
+      return CacheBuilder.newBuilder().build(in);
+   }
+
+   @Provides
+   @Singleton
    protected LoadingCache<String, Set<IPForwardingRule>> getIPForwardingRulesByVirtualMachine(
       CacheLoader<String, Set<IPForwardingRule>> getIPForwardingRules) {
       return CacheBuilder.newBuilder().build(getIPForwardingRules);

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/functions/OrphanedGroupsByZoneId.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/functions/OrphanedGroupsByZoneId.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/functions/OrphanedGroupsByZoneId.java
new file mode 100644
index 0000000..50dbe53
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/functions/OrphanedGroupsByZoneId.java
@@ -0,0 +1,77 @@
+/*
+ * 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.cloudstack.compute.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Sets.filter;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.domain.NodeMetadata;
+import org.jclouds.compute.predicates.NodePredicates;
+import org.jclouds.domain.LocationScope;
+import org.jclouds.cloudstack.compute.predicates.AllNodesInGroupTerminated;
+import org.jclouds.cloudstack.domain.ZoneAndName;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
+
+/**
+ * 
+ * @author Adrian Cole
+ */
+public class OrphanedGroupsByZoneId implements Function<Set<? extends NodeMetadata>, Multimap<String, String>> {
+   private final Predicate<ZoneAndName> allNodesInGroupTerminated;
+
+   @Inject
+   protected OrphanedGroupsByZoneId(ComputeService computeService) {
+      this(new AllNodesInGroupTerminated(checkNotNull(computeService, "computeService")));
+   }
+
+   @VisibleForTesting
+   OrphanedGroupsByZoneId(Predicate<ZoneAndName> allNodesInGroupTerminated) {
+      this.allNodesInGroupTerminated = checkNotNull(allNodesInGroupTerminated, "allNodesInGroupTerminated");
+   }
+
+   public Multimap<String, String> apply(Set<? extends NodeMetadata> deadNodes) {
+      Iterable<? extends NodeMetadata> nodesWithGroup = filter(deadNodes, NodePredicates.hasGroup());
+      Set<ZoneAndName> zoneAndGroupNames = ImmutableSet.copyOf(filter(transform(nodesWithGroup,
+               new Function<NodeMetadata, ZoneAndName>() {
+
+                  @Override
+                  public ZoneAndName apply(NodeMetadata input) {
+                     String zoneId = input.getLocation().getScope() == LocationScope.HOST ? input.getLocation()
+                              .getParent().getId() : input.getLocation().getId();
+                     return ZoneAndName.fromZoneAndName(zoneId, input.getGroup());
+                  }
+
+               }), allNodesInGroupTerminated));
+      Multimap<String, String> zoneToZoneAndGroupNames = Multimaps.transformValues(Multimaps.index(zoneAndGroupNames,
+               ZoneAndName.ZONE_FUNCTION), ZoneAndName.NAME_FUNCTION);
+      return zoneToZoneAndGroupNames;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPair.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPair.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPair.java
new file mode 100644
index 0000000..0ae2b05
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPair.java
@@ -0,0 +1,69 @@
+/*
+ * 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.cloudstack.compute.loaders;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+import org.jclouds.cloudstack.CloudStackClient;
+import org.jclouds.cloudstack.domain.AsyncCreateResponse;
+import org.jclouds.cloudstack.domain.SshKeyPair;
+import org.jclouds.cloudstack.strategy.BlockUntilJobCompletesAndReturnResult;
+
+import com.google.common.base.Optional;
+import com.google.common.cache.CacheLoader;
+import com.google.inject.Inject;
+
+/**
+ * @author Adam Lowe
+ * @author Andrew Bayer
+ */
+@Singleton
+public class CreateUniqueKeyPair extends CacheLoader<String, SshKeyPair> {
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+   protected final CloudStackClient client;
+
+   @Inject
+   public CreateUniqueKeyPair(CloudStackClient client) {
+      this.client = checkNotNull(client, "client");
+   }
+
+   @Override
+   public SshKeyPair load(String input) {
+      SshKeyPair keyPair = null;
+      while (keyPair == null) {
+         try {
+            keyPair = client.getSSHKeyPairClient().createSSHKeyPair(input);
+            logger.debug(">> creating SSH key pair with name %s", input);
+         } catch (IllegalStateException e) {
+            
+         }
+      }
+
+      logger.debug("<< created keyPair(%s)", keyPair.getName());
+      return keyPair;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreate.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreate.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreate.java
new file mode 100644
index 0000000..6689371
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreate.java
@@ -0,0 +1,83 @@
+/*
+ * 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.cloudstack.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.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+import org.jclouds.cloudstack.CloudStackClient;
+import org.jclouds.cloudstack.domain.SecurityGroup;
+import org.jclouds.cloudstack.domain.Zone;
+import org.jclouds.cloudstack.domain.ZoneAndName;
+import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
+
+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;
+
+/**
+ * 
+ * @author Adrian Cole
+ * @author Andrew Bayer
+ */
+public class FindSecurityGroupOrCreate extends CacheLoader<ZoneAndName, SecurityGroup> {
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+   protected final CloudStackClient client;
+   protected final Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup> groupCreator;
+
+   @Inject
+   public FindSecurityGroupOrCreate(CloudStackClient client,
+                                    Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup> groupCreator) {
+      this.client = checkNotNull(client, "client");
+      this.groupCreator = checkNotNull(groupCreator, "groupCreator");
+   }
+
+   @Override
+   public SecurityGroup load(ZoneAndName in) {
+      SecurityGroup group = client.getSecurityGroupClient().getSecurityGroupByName(in.getName());
+      if (group != null) {
+         return group;
+      } else {
+         return createNewSecurityGroup(in);
+      }
+   }
+
+   private SecurityGroup createNewSecurityGroup(ZoneAndName in) {
+      checkState(
+               checkNotNull(in, "ZoneSecurityGrioupNamePortsCidrs") instanceof ZoneSecurityGroupNamePortsCidrs,
+               "programming error: when issuing get to this cacheloader, you need to pass an instance of ZoneSecurityGroupNamePortsCidrs, not %s",
+               in);
+      ZoneSecurityGroupNamePortsCidrs zoneSecurityGroupNamePortsCidrs = ZoneSecurityGroupNamePortsCidrs.class.cast(in);
+      return groupCreator.apply(zoneSecurityGroupNamePortsCidrs);
+   }
+
+   @Override
+   public String toString() {
+      return "returnExistingSecurityGroupInZoneOrCreateAsNeeded()";
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptions.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptions.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptions.java
index 7a70b5f..4b05a21 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptions.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptions.java
@@ -58,7 +58,9 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea
    protected boolean setupStaticNat = true;
    protected String account;
    protected String domainId;
-    
+   protected boolean generateKeyPair = false;
+   protected boolean generateSecurityGroup = false;
+   
    @Override
    public CloudStackTemplateOptions clone() {
       CloudStackTemplateOptions options = new CloudStackTemplateOptions();
@@ -76,6 +78,8 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea
          eTo.ipsToNetworks(this.ipsToNetworks);
          eTo.ipOnDefaultNetwork(this.ipOnDefaultNetwork);
          eTo.keyPair(this.keyPair);
+         eTo.generateKeyPair(shouldGenerateKeyPair());
+         eTo.generateSecurityGroup(shouldGenerateSecurityGroup());
          eTo.account(this.account);
          eTo.domainId(this.domainId);
          eTo.setupStaticNat(setupStaticNat);
@@ -103,6 +107,21 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea
    }
 
    /**
+    * @see #shouldGenerateKeyPair()
+    */
+   public CloudStackTemplateOptions generateSecurityGroup(boolean enable) {
+      this.generateSecurityGroup = enable;
+      return this;
+   }
+
+   /**
+    * @return true if auto generation of keypairs is enabled
+    */
+   public boolean shouldGenerateSecurityGroup() {
+      return generateSecurityGroup;
+   }
+
+   /**
     * @see DeployVirtualMachineOptions#networkId
     */
    public CloudStackTemplateOptions networkId(String networkId) {
@@ -168,6 +187,21 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea
    }
 
    /**
+    * @see #shouldGenerateKeyPair()
+    */
+   public CloudStackTemplateOptions generateKeyPair(boolean enable) {
+      this.generateKeyPair = enable;
+      return this;
+   }
+
+   /**
+    * @return true if auto generation of keypairs is enabled
+    */
+   public boolean shouldGenerateKeyPair() {
+      return generateKeyPair;
+   }
+
+   /**
     * @see DeployVirtualMachineOptions#accountInDomain(String,String)
     */
    public CloudStackTemplateOptions account(String account) {
@@ -213,6 +247,13 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea
       }
 
       /**
+       * @see CloudStackTemplateOptions#shouldGenerateSecurityGroup() 
+       */
+      public static CloudStackTemplateOptions generateSecurityGroup(boolean enable) {
+         return new CloudStackTemplateOptions().generateSecurityGroup(enable);
+      }
+
+      /**
        * @see CloudStackTemplateOptions#networkId
        */
       public static CloudStackTemplateOptions networkId(String id) {
@@ -261,6 +302,13 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea
       }
 
       /**
+       * @see CloudStackTemplateOptions#shouldGenerateKeyPair() 
+       */
+      public static CloudStackTemplateOptions generateKeyPair(boolean enable) {
+         return new CloudStackTemplateOptions().generateKeyPair(enable);
+      }
+
+      /**
        * @see CloudStackTemplateOptions#account
        */
       public static CloudStackTemplateOptions account(String account) {

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/predicates/AllNodesInGroupTerminated.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/predicates/AllNodesInGroupTerminated.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/predicates/AllNodesInGroupTerminated.java
new file mode 100644
index 0000000..d38a990
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/predicates/AllNodesInGroupTerminated.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cloudstack.compute.predicates;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.and;
+import static com.google.common.collect.Iterables.all;
+import static org.jclouds.compute.predicates.NodePredicates.TERMINATED;
+import static org.jclouds.compute.predicates.NodePredicates.inGroup;
+import static org.jclouds.compute.predicates.NodePredicates.locationId;
+import static org.jclouds.compute.predicates.NodePredicates.parentLocationId;
+
+import javax.inject.Inject;
+
+import org.jclouds.compute.ComputeService;
+import org.jclouds.compute.domain.ComputeMetadata;
+import org.jclouds.cloudstack.domain.ZoneAndName;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+/**
+ * @author Adrian Cole
+ */
+public class AllNodesInGroupTerminated implements Predicate<ZoneAndName> {
+   private final ComputeService computeService;
+
+   
+   //TODO: TESTME
+   @Inject
+   public AllNodesInGroupTerminated(ComputeService computeService) {
+      this.computeService = checkNotNull(computeService, "computeService");
+   }
+
+   @Override
+   public boolean apply(ZoneAndName input) {
+      // new nodes can have the zone as their location, existing nodes, the parent is the
+      // location
+      return all(computeService.listNodesDetailsMatching(Predicates.<ComputeMetadata> or(locationId(input.getZone()),
+               parentLocationId(input.getZone()))), and(inGroup(input.getName()), TERMINATED));
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
index e839d30..a4f7176 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java
@@ -25,6 +25,8 @@ import static com.google.common.collect.Iterables.get;
 import static org.jclouds.cloudstack.options.DeployVirtualMachineOptions.Builder.displayName;
 import static org.jclouds.cloudstack.options.ListTemplatesOptions.Builder.id;
 import static org.jclouds.cloudstack.predicates.TemplatePredicates.isReady;
+import static org.jclouds.cloudstack.predicates.ZonePredicates.supportsSecurityGroups;
+import static org.jclouds.ssh.SshKeys.fingerprintPrivateKey;
 
 import java.util.List;
 import java.util.Map;
@@ -45,10 +47,14 @@ import org.jclouds.cloudstack.domain.IPForwardingRule;
 import org.jclouds.cloudstack.domain.Network;
 import org.jclouds.cloudstack.domain.NetworkType;
 import org.jclouds.cloudstack.domain.PublicIPAddress;
+import org.jclouds.cloudstack.domain.SecurityGroup;
 import org.jclouds.cloudstack.domain.ServiceOffering;
+import org.jclouds.cloudstack.domain.SshKeyPair;
 import org.jclouds.cloudstack.domain.Template;
 import org.jclouds.cloudstack.domain.VirtualMachine;
 import org.jclouds.cloudstack.domain.Zone;
+import org.jclouds.cloudstack.domain.ZoneAndName;
+import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
 import org.jclouds.cloudstack.functions.CreateFirewallRulesForIP;
 import org.jclouds.cloudstack.functions.CreatePortForwardingRulesForIP;
 import org.jclouds.cloudstack.functions.StaticNATVirtualMachineInNetwork;
@@ -58,6 +64,7 @@ import org.jclouds.cloudstack.options.ListFirewallRulesOptions;
 import org.jclouds.cloudstack.strategy.BlockUntilJobCompletesAndReturnResult;
 import org.jclouds.collect.Memoized;
 import org.jclouds.compute.ComputeServiceAdapter;
+import org.jclouds.compute.functions.GroupNamingConvention;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.domain.Credentials;
 import org.jclouds.domain.LoginCredentials;
@@ -96,6 +103,9 @@ public class CloudStackComputeServiceAdapter implements
    private final Map<String, Credentials> credentialStore;
    private final Map<NetworkType, ? extends OptionsConverter> optionsConverters;
    private final Supplier<LoadingCache<String, Zone>> zoneIdToZone;
+   private final LoadingCache<ZoneAndName, SecurityGroup> securityGroupCache;
+   private final LoadingCache<String, SshKeyPair> keyPairCache;
+   private final GroupNamingConvention.Factory namingConvention;
 
    @Inject
    public CloudStackComputeServiceAdapter(CloudStackClient client, Predicate<String> jobComplete,
@@ -107,7 +117,10 @@ public class CloudStackComputeServiceAdapter implements
                                           LoadingCache<String, Set<IPForwardingRule>> vmToRules,
                                           Map<String, Credentials> credentialStore,
                                           Map<NetworkType, ? extends OptionsConverter> optionsConverters,
-                                          Supplier<LoadingCache<String, Zone>> zoneIdToZone) {
+                                          Supplier<LoadingCache<String, Zone>> zoneIdToZone,
+                                          LoadingCache<ZoneAndName, SecurityGroup> securityGroupCache,
+                                          LoadingCache<String, SshKeyPair> keyPairCache,
+                                          GroupNamingConvention.Factory namingConvention) {
       this.client = checkNotNull(client, "client");
       this.jobComplete = checkNotNull(jobComplete, "jobComplete");
       this.networkSupplier = checkNotNull(networkSupplier, "networkSupplier");
@@ -118,8 +131,11 @@ public class CloudStackComputeServiceAdapter implements
       this.setupFirewallRulesForIP = checkNotNull(setupFirewallRulesForIP, "setupFirewallRulesForIP");
       this.vmToRules = checkNotNull(vmToRules, "vmToRules");
       this.credentialStore = checkNotNull(credentialStore, "credentialStore");
+      this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache");
+      this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
       this.optionsConverters = optionsConverters;
       this.zoneIdToZone = zoneIdToZone;
+      this.namingConvention = namingConvention;
    }
 
    @Override
@@ -134,12 +150,7 @@ public class CloudStackComputeServiceAdapter implements
       Map<String, Network> networks = networkSupplier.get();
 
       final String zoneId = template.getLocation().getId();
-      Zone zone = null;
-      try {
-         zone = zoneIdToZone.get().get(zoneId);
-      } catch (ExecutionException e) {
-         throw Throwables.propagate(e);
-      }
+      Zone zone = zoneIdToZone.get().getUnchecked(zoneId);
 
       CloudStackTemplateOptions templateOptions = template.getOptions().as(CloudStackTemplateOptions.class);
 
@@ -163,15 +174,37 @@ public class CloudStackComputeServiceAdapter implements
       }
 
       if (templateOptions.getKeyPair() != null) {
-         options.keyPair(templateOptions.getKeyPair());
-         if (templateOptions.getRunScript() != null) {
-            checkArgument(
-               credentialStore.containsKey("keypair#" + templateOptions.getKeyPair()),
-               "no private key configured for: %s; please use options.overrideLoginCredentialWith(rsa_private_text)",
-               templateOptions.getKeyPair());
+         if (templateOptions.getLoginPrivateKey() != null) {
+            String pem = templateOptions.getLoginPrivateKey();
+            SshKeyPair keyPair = SshKeyPair.builder().name(templateOptions.getKeyPair())
+               .fingerprint(fingerprintPrivateKey(pem)).privateKey(pem).build();
+            keyPairCache.asMap().put(keyPair.getName(), keyPair);
+            options.keyPair(keyPair.getName());
          }
+      } else if (templateOptions.shouldGenerateKeyPair()) {
+         SshKeyPair keyPair = keyPairCache.getUnchecked(namingConvention.create()
+                                                        .sharedNameForGroup(group));
+         keyPairCache.asMap().put(keyPair.getName(), keyPair);
+         templateOptions.keyPair(keyPair.getName());
+         options.keyPair(keyPair.getName());
       }
 
+      if (supportsSecurityGroups().apply(zone)) {
+         List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts());
+
+         if (templateOptions.getSecurityGroupIds().size() == 0
+             && inboundPorts.size() > 0
+             && templateOptions.shouldGenerateSecurityGroup()) {
+            String securityGroupName = namingConvention.create().sharedNameForGroup(group);
+            SecurityGroup sg = securityGroupCache.getUnchecked(ZoneSecurityGroupNamePortsCidrs.builder()
+                                                               .zone(zone.getId())
+                                                               .name(securityGroupName)
+                                                               .ports(ImmutableSet.copyOf(inboundPorts))
+                                                               .cidrs(ImmutableSet.<String> of()).build());
+            options.securityGroupId(sg.getId());
+         }
+      }
+      
       String templateId = template.getImage().getId();
       String serviceOfferingId = template.getHardware().getId();
 

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackProperties.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackProperties.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackProperties.java
index 34bc8ec..ac6e034 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackProperties.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackProperties.java
@@ -42,4 +42,9 @@ public interface CloudStackProperties {
     */
    public static final String CREDENTIAL_TYPE = "jclouds.cloudstack.credential-type";
 
+   /**
+    * Whenever a node is created, automatically generate keypairs for groups, as needed, also
+    * delete the keypair(s) when the last node in the group is destroyed.
+    */
+   public static final String AUTO_GENERATE_KEYPAIRS = "jclouds.cloudstack.auto-generate-keypairs";
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneAndName.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneAndName.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneAndName.java
new file mode 100644
index 0000000..43c6ae04
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneAndName.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.cloudstack.domain;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Splitter;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.Iterables;
+
+/**
+ * Helpful when looking for resources by zone and name
+ * 
+ * @author Adrian Cole
+ */
+public class ZoneAndName {
+   
+   public static final Function<ZoneAndName, String> NAME_FUNCTION = new Function<ZoneAndName, String>(){
+
+      @Override
+      public String apply(ZoneAndName input) {
+         return input.getName();
+      }
+      
+   };
+   
+   public static final Function<ZoneAndName, String> ZONE_FUNCTION = new Function<ZoneAndName, String>(){
+
+      @Override
+      public String apply(ZoneAndName input) {
+         return input.getZone();
+      }
+      
+   };
+
+   public static ZoneAndName fromZoneAndName(String zoneId, String name) {
+      return new ZoneAndName(zoneId, name);
+   }
+
+   protected final String zoneId;
+   protected final String name;
+
+   protected ZoneAndName(String zoneId, String name) {
+      this.zoneId = checkNotNull(zoneId, "zoneId");
+      this.name = checkNotNull(name, "name");
+   }
+
+   public String getZone() {
+      return zoneId;
+   }
+
+   public String getName() {
+      return name;
+   }
+
+   @Override
+   public boolean equals(Object o) {
+      if (this == o)
+         return true;
+      if (o == null || getClass() != o.getClass())
+         return false;
+      ZoneAndName that = ZoneAndName.class.cast(o);
+      return equal(this.zoneId, that.zoneId) && equal(this.name, that.name);
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(zoneId, name);
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   protected ToStringHelper string() {
+      return Objects.toStringHelper("").add("zoneId", zoneId).add("name", name);
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneSecurityGroupNamePortsCidrs.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneSecurityGroupNamePortsCidrs.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneSecurityGroupNamePortsCidrs.java
new file mode 100644
index 0000000..415b901
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneSecurityGroupNamePortsCidrs.java
@@ -0,0 +1,163 @@
+/*
+ * 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.cloudstack.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Set;
+
+import org.jclouds.javax.annotation.Nullable;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * @author Andrew Bayer
+ */
+public class ZoneSecurityGroupNamePortsCidrs extends ZoneAndName {
+
+   public static Builder<?> builder() {
+      return new ConcreteBuilder();
+   }
+
+   public Builder<?> toBuilder() {
+      return new ConcreteBuilder().fromZoneSecurityGroupNamePortsCidrs(this);
+   }
+
+   public abstract static class Builder<T extends Builder<T>> {
+      protected abstract T self();
+
+      protected String zoneId;
+      protected String name;
+      protected Set<Integer> ports = ImmutableSet.of();
+      protected Set<String> cidrs = ImmutableSet.of();
+
+      /**
+       * @see ZoneSecurityGroupNamePortsCidrs#getZone()
+       */
+      public T zone(String zoneId) {
+         this.zoneId = zoneId;
+         return self();
+      }
+
+      /**
+       * @see ZoneSecurityGroupNamePortsCidrs#getName()
+       */
+      public T name(String name) {
+         this.name = name;
+         return self();
+      }
+      
+      /**
+       * @see ZoneSecurityGroupNamePortsCidrs#getPorts()
+       */
+      public T ports(Set<Integer> ports) {
+         this.ports = ImmutableSet.copyOf(checkNotNull(ports, "ports"));
+         return self();
+      }
+
+      public T ports(Integer... in) {
+         return ports(ImmutableSet.copyOf(in));
+      }
+      
+      /**
+       * @see ZoneSecurityGroupNamePortsCidrs#getCidrs()
+       */
+      public T cidrs(Set<String> cidrs) {
+         this.cidrs = ImmutableSet.copyOf(checkNotNull(cidrs, "cidrs"));
+         return self();
+      }
+
+      public T cidrs(String... in) {
+         return cidrs(ImmutableSet.copyOf(in));
+      }
+
+      public ZoneSecurityGroupNamePortsCidrs build() {
+         return new ZoneSecurityGroupNamePortsCidrs(zoneId, name, ports, cidrs);
+      }
+
+      public T fromZoneSecurityGroupNamePortsCidrs(ZoneSecurityGroupNamePortsCidrs in) {
+         return this.zone(in.getZone())
+            .name(in.getName())
+            .ports(in.getPorts())
+            .cidrs(in.getCidrs());
+      }
+   }
+   
+   private static class ConcreteBuilder extends Builder<ConcreteBuilder> {
+      @Override
+      protected ConcreteBuilder self() {
+         return this;
+      }
+   }
+
+   
+   private final Set<Integer> ports;
+   private final Set<String> cidrs;
+
+   protected ZoneSecurityGroupNamePortsCidrs(String zoneId, String name, Set<Integer> ports,
+                                             Set<String> cidrs) {
+      super(zoneId, name);
+      this.ports = ports == null ? ImmutableSet.<Integer>of() : ImmutableSet.copyOf(ports);
+      this.cidrs = cidrs == null ? ImmutableSet.<String>of() : ImmutableSet.copyOf(cidrs);
+   }
+
+   /**
+    *
+    * @return the set of ports to open in the security group
+    */
+   public Set<Integer> getPorts() {
+      return ports;
+   }
+   
+   /**
+    *
+    * @return the set of cidrs to give access to the open ports in the security group
+    */
+   public Set<String> getCidrs() {
+      return cidrs;
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(zoneId, name, ports, cidrs);
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null || getClass() != obj.getClass()) return false;
+      ZoneSecurityGroupNamePortsCidrs that = ZoneSecurityGroupNamePortsCidrs.class.cast(obj);
+      return Objects.equal(this.zoneId, that.zoneId)
+         && Objects.equal(this.name, that.name)
+         && Objects.equal(this.ports, that.ports)
+         && Objects.equal(this.cidrs, that.cidrs);
+   }
+
+   protected ToStringHelper string() {
+      return Objects.toStringHelper(this)
+         .add("zoneId", zoneId).add("name", name).add("ports", ports).add("cidrs", cidrs);
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+}
+
+   
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClient.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClient.java
index 31f862b..283ff5b 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClient.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupAsyncClient.java
@@ -79,6 +79,18 @@ public interface SecurityGroupAsyncClient {
    ListenableFuture<SecurityGroup> getSecurityGroup(@QueryParam("id") String id);
 
    /**
+    * @see SecurityGroupClient#getSecurityGroupByName
+    */
+   @Named("listSecurityGroups")
+   @GET
+   @QueryParams(keys = { "command", "listAll" }, values = { "listSecurityGroups", "true" })
+   @SelectJson("securitygroup")
+   @OnlyElement
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<SecurityGroup> getSecurityGroupByName(@QueryParam("securitygroupname") String securityGroupName);
+
+   /**
     * @see SecurityGroupClient#createSecurityGroup
     */
    @Named("createSecurityGroup")

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupClient.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupClient.java
index f3cb39e..ddb9422 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupClient.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SecurityGroupClient.java
@@ -132,6 +132,15 @@ public interface SecurityGroupClient {
    SecurityGroup getSecurityGroup(String id);
 
    /**
+    * get a specific security group by name
+    * 
+    * @param securityGroupName
+    *           group to get
+    * @return security group or null if not found
+    */
+   SecurityGroup getSecurityGroupByName(String securityGroupName);
+
+   /**
     * Creates a security group
     * 
     * @param name

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeeded.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeeded.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeeded.java
new file mode 100644
index 0000000..b6a2a98
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeeded.java
@@ -0,0 +1,124 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  jclouds 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.cloudstack.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.cloudstack.predicates.SecurityGroupPredicates.nameEquals;
+import static org.jclouds.cloudstack.predicates.SecurityGroupPredicates.portInRangeForCidr;
+import static org.jclouds.cloudstack.predicates.ZonePredicates.supportsSecurityGroups;
+
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.logging.Logger;
+import org.jclouds.cloudstack.CloudStackClient;
+import org.jclouds.cloudstack.domain.IngressRule;
+import org.jclouds.cloudstack.domain.SecurityGroup;
+import org.jclouds.cloudstack.domain.Zone;
+import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
+
+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.base.Throwables;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * 
+ * @author Adrian Cole
+ * @author Andrew Bayer
+ */
+@Singleton
+public class CreateSecurityGroupIfNeeded implements Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup> {
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
+   protected final CloudStackClient client;
+   protected final Supplier<LoadingCache<String, Zone>> zoneIdToZone;
+   protected final Predicate<String> jobComplete;
+
+   @Inject
+   public CreateSecurityGroupIfNeeded(CloudStackClient client,
+                                      Predicate<String> jobComplete,
+                                      Supplier<LoadingCache<String, Zone>> zoneIdToZone) {
+      this.client = checkNotNull(client, "client");
+      this.jobComplete = checkNotNull(jobComplete, "jobComplete");
+      this.zoneIdToZone = zoneIdToZone;
+   }
+
+   @Override
+   public SecurityGroup apply(ZoneSecurityGroupNamePortsCidrs input) {
+      checkNotNull(input, "input");
+
+      String zoneId = input.getZone();
+      Zone zone = zoneIdToZone.get().getUnchecked(zoneId);
+
+      checkArgument(supportsSecurityGroups().apply(zone),
+                    "Security groups are required, but the zone %s does not support security groups", zoneId);
+      logger.debug(">> creating securityGroup %s", input);
+      try {
+
+         SecurityGroup securityGroup = client.getSecurityGroupClient().createSecurityGroup(input.getName());
+
+         logger.debug("<< created securityGroup(%s)", securityGroup);
+         ImmutableSet<String> cidrs;
+         if (input.getCidrs().size() > 0) {
+            cidrs = ImmutableSet.copyOf(input.getCidrs());
+         } else {
+            cidrs = ImmutableSet.of("0.0.0.0/0");
+         }
+         for (int port : input.getPorts()) {
+            authorizeGroupToItselfAndToTCPPortAndCidr(client, securityGroup, port, cidrs);
+         }
+         return securityGroup;
+      } catch (IllegalStateException e) {
+         logger.trace("<< trying to find securityGroup(%s): %s", input, e.getMessage());
+         SecurityGroup group = client.getSecurityGroupClient().getSecurityGroupByName(input.getName());
+         logger.debug("<< reused securityGroup(%s)", group.getId());
+         return group;
+      }
+   }
+
+   private void authorizeGroupToItselfAndToTCPPortAndCidr(CloudStackClient client,
+                                                          SecurityGroup securityGroup,
+                                                          int port,
+                                                          Set<String> cidrs) {
+      for (String cidr : cidrs) {
+         logger.debug(">> authorizing securityGroup(%s) permission to %s on port %d", securityGroup, cidr, port);
+         if (!portInRangeForCidr(port, cidr).apply(securityGroup)) {
+            jobComplete.apply(client.getSecurityGroupClient().authorizeIngressPortsToCIDRs(securityGroup.getId(),
+                                                                                           "TCP",
+                                                                                           port,
+                                                                                           port,
+                                                                                           ImmutableSet.of(cidr)));
+            logger.debug("<< authorized securityGroup(%s) permission to %s on port %d", securityGroup, cidr, port);
+         }
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicates.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicates.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicates.java
new file mode 100644
index 0000000..bef1d29
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicates.java
@@ -0,0 +1,161 @@
+/*
+ * 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.cloudstack.predicates;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.alwaysTrue;
+
+import org.jclouds.cloudstack.domain.IngressRule;
+import org.jclouds.cloudstack.domain.SecurityGroup;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+/**
+ * 
+ * @author Andrew Bayer
+ */
+public class SecurityGroupPredicates {
+   /**
+    * 
+    * @return true, if the security group contains an ingress rule with the given port in the port range
+    */
+   public static Predicate<SecurityGroup> portInRange(final int port) {
+      return new Predicate<SecurityGroup>() {
+
+         @Override
+         public boolean apply(SecurityGroup group) {
+            return Iterables.any(group.getIngressRules(), new Predicate<IngressRule>() {
+                  @Override
+                  public boolean apply(IngressRule rule) {
+                     return rule.getStartPort() <= port && rule.getEndPort() >= port;
+                  }
+               });
+         }
+
+         @Override
+         public String toString() {
+            return "portInRange(" + port + ")";
+         }
+      };
+   }
+
+   /**
+    * 
+    * @return true, if the security group contains an ingress rule with the given cidr
+    */
+   public static Predicate<SecurityGroup> hasCidr(final String cidr) {
+      return new Predicate<SecurityGroup>() {
+
+         @Override
+         public boolean apply(SecurityGroup group) {
+            return Iterables.any(group.getIngressRules(), new Predicate<IngressRule>() {
+                  @Override
+                  public boolean apply(IngressRule rule) {
+                     return rule.getCIDR() != null
+                        && rule.getCIDR().equals(cidr);
+                  }
+               });
+         }
+
+         @Override
+         public String toString() {
+            return "hasCidr(" + cidr + ")";
+         }
+      };
+   }
+
+   /**
+    * 
+    * @return true, if the security group contains an ingress rule with the given cidr and the given port in range
+    */
+   public static Predicate<SecurityGroup> portInRangeForCidr(final int port, final String cidr) {
+      return new Predicate<SecurityGroup>() {
+
+         @Override
+         public boolean apply(SecurityGroup group) {
+            return Iterables.any(group.getIngressRules(), new Predicate<IngressRule>() {
+                  @Override
+                  public boolean apply(IngressRule rule) {
+                     return rule.getCIDR() != null
+                        && rule.getCIDR().equals(cidr)
+                        && rule.getStartPort() <= port
+                        && rule.getEndPort() >= port;
+                  }
+               });
+         }
+
+         @Override
+         public String toString() {
+            return "portInRangeForCidr(" + port + ", " + cidr + ")";
+         }
+      };
+   }
+
+   /**
+    * 
+    * @return always returns true.
+    */
+   public static Predicate<SecurityGroup> any() {
+      return alwaysTrue();
+   }
+
+   /**
+    * matches name of the given security group
+    * 
+    * @param name
+    * @return predicate that matches name
+    */
+   public static Predicate<SecurityGroup> nameEquals(final String name) {
+      checkNotNull(name, "name must be defined");
+
+      return new Predicate<SecurityGroup>() {
+         @Override
+         public boolean apply(SecurityGroup ext) {
+            return name.equals(ext.getName());
+         }
+
+         @Override
+         public String toString() {
+            return "nameEquals(" + name + ")";
+         }
+      };
+   }
+
+
+   /**
+    * matches name of the given security group
+    * 
+    * @param name
+    * @return predicate that matches name
+    */
+   public static Predicate<SecurityGroup> nameMatches(final Predicate<String> name) {
+      checkNotNull(name, "name must be defined");
+
+      return new Predicate<SecurityGroup>() {
+         @Override
+         public boolean apply(SecurityGroup ext) {
+            return name.apply(ext.getName());
+         }
+
+         @Override
+         public String toString() {
+            return "nameMatches(" + name + ")";
+         }
+      };
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SshKeyPairPredicates.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SshKeyPairPredicates.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SshKeyPairPredicates.java
new file mode 100644
index 0000000..3037eb1
--- /dev/null
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SshKeyPairPredicates.java
@@ -0,0 +1,78 @@
+/*
+ * 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.cloudstack.predicates;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.cloudstack.domain.SshKeyPair;
+
+import com.google.common.base.Predicate;
+
+/**
+ * Predicates handy when working with SshKeyPairs
+ * 
+ * @author Adam Lowe
+ * @author Andrew Bayer
+ */
+
+public class SshKeyPairPredicates {
+
+
+   /**
+    * matches name of the given key pair
+    * 
+    * @param name
+    * @return predicate that matches name
+    */
+   public static Predicate<SshKeyPair> nameMatches(final Predicate<String> name) {
+      checkNotNull(name, "name must be defined");
+
+      return new Predicate<SshKeyPair>() {
+         @Override
+         public boolean apply(SshKeyPair ext) {
+            return name.apply(ext.getName());
+         }
+
+         @Override
+         public String toString() {
+            return "nameMatches(" + name + ")";
+         }
+      };
+   }
+   
+   /**
+    * matches name of the given keypair starts with the specified prefix
+    *
+    * @param name the prefix you are looking for
+    * @return the predicate
+    */
+   public static Predicate<SshKeyPair> nameEquals(final String name) {
+      checkNotNull(name, "name must be defined");
+
+      return new Predicate<SshKeyPair>() {
+         @Override
+         public boolean apply(SshKeyPair ext) {
+            return ext.getName() != null && ext.getName().equals(name);
+         }
+
+         @Override
+         public String toString() {
+            return "nameEquals(" + name + ")";
+         }
+      };
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/fbe637c8/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
index bab5459..1e6ff6f 100644
--- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
+++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java
@@ -19,6 +19,7 @@ package org.jclouds.cloudstack.compute;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 
+import java.io.IOException;
 import java.util.Map;
 
 import org.jclouds.cloudstack.CloudStackContext;
@@ -33,6 +34,7 @@ import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.domain.LoginCredentials;
 import org.jclouds.http.HttpRequest;
 import org.jclouds.http.HttpResponse;
+import org.jclouds.util.Strings2;
 import org.testng.annotations.Test;
 
 import com.google.common.collect.ImmutableMap;
@@ -62,8 +64,26 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
    HttpResponse queryAsyncJobResultResponse = HttpResponse.builder().statusCode(200)
         .payload(payloadFromResource("/queryasyncjobresultresponse-virtualmachine.json"))
         .build();
+
+   HttpResponse queryAsyncJobResultSecurityGroupResponse = HttpResponse.builder().statusCode(200)
+        .payload(payloadFromResource("/queryasyncjobresultresponse-virtualmachine-securitygroup.json"))
+        .build();
+
+   HttpRequest queryAsyncJobResultAuthorizeIngress = HttpRequest.builder().method("GET")
+        .endpoint("http://localhost:8080/client/api")
+        .addQueryParam("response", "json")
+        .addQueryParam("command", "queryAsyncJobResult")
+        .addQueryParam("jobid", "13330fc9-8b3e-4582-aa3e-90883c041010")
+        .addQueryParam("apiKey", "APIKEY")
+        .addQueryParam("signature", "y4gk3ckWAMPDNZM26LUK0gAhfiE%3D")
+        .addHeader("Accept", "application/json")
+        .build();
+
+   HttpResponse queryAsyncJobResultAuthorizeIngressResponse = HttpResponse.builder().statusCode(200)
+        .payload(payloadFromResource("/queryasyncjobresultresponse-authorizeingress.json"))
+        .build();
    
-   public void testCreateNodeWithGroupEncodedIntoNameWithKeyPair() {
+   public void testCreateNodeWithGroupEncodedIntoNameWithKeyPair() throws IOException {
       HttpRequest deployVM = HttpRequest.builder().method("GET")
             .endpoint("http://localhost:8080/client/api")
             .addQueryParam("response", "json")
@@ -95,8 +115,160 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
 
       Injector forKeyPair = requestsSendResponses(requestResponseMap);
 
+      String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test"));
       Template template = forKeyPair.getInstance(TemplateBuilder.class).osFamily(OsFamily.CENTOS).build();
-      template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair").setupStaticNat(false);
+      template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair")
+         .setupStaticNat(false)
+         .overrideLoginPrivateKey(privKey);
+
+      CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
+
+      NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
+            template);
+      assertNotNull(server);
+      assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
+   }
+
+   public void testCreateNodeWithGroupEncodedIntoNameWithGenerateKeyPair() throws IOException {
+      HttpRequest deployVM = HttpRequest.builder().method("GET")
+            .endpoint("http://localhost:8080/client/api")
+            .addQueryParam("response", "json")
+            .addQueryParam("command", "deployVirtualMachine")
+            .addQueryParam("zoneid", "1")
+            .addQueryParam("serviceofferingid", "1")
+            .addQueryParam("templateid", "4")
+            .addQueryParam("displayname", "test-e92")
+            .addQueryParam("name", "test-e92")
+            .addQueryParam("networkids", "204")
+            .addQueryParam("keypair", "jclouds-test")
+            .addQueryParam("apiKey", "APIKEY")
+            .addQueryParam("signature", "4M9C8IjohDDKFMAXQSX3mjXpYvM%3D")
+            .addHeader("Accept", "application/json")
+            .build(); 
+  
+      Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
+            .put(listTemplates, listTemplatesResponse)
+            .put(listOsTypes, listOsTypesResponse)
+            .put(listOsCategories, listOsCategoriesResponse)
+            .put(listZones, listZonesResponse)
+            .put(listServiceOfferings, listServiceOfferingsResponse)
+            .put(listAccounts, listAccountsResponse)
+            .put(listNetworks, listNetworksResponse)
+            .put(getZone, getZoneResponse)
+            .put(deployVM, deployVMResponse)
+            .put(createSSHKeyPair, createSSHKeyPairResponse)
+            .put(queryAsyncJobResult, queryAsyncJobResultResponse)
+            .build();
+
+      Injector forKeyPair = requestsSendResponses(requestResponseMap);
+
+      Template template = forKeyPair.getInstance(TemplateBuilder.class).osFamily(OsFamily.CENTOS).build();
+      template.getOptions().as(CloudStackTemplateOptions.class).generateKeyPair(true)
+         .setupStaticNat(false);
+
+      CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
+
+      NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
+            template);
+      assertNotNull(server);
+      assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
+   }
+   
+   public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairDefaultSecurityGroup() throws IOException {
+      HttpRequest deployVM = HttpRequest.builder().method("GET")
+            .endpoint("http://localhost:8080/client/api")
+            .addQueryParam("response", "json")
+            .addQueryParam("command", "deployVirtualMachine")
+            .addQueryParam("zoneid", "2")
+            .addQueryParam("serviceofferingid", "1")
+            .addQueryParam("templateid", "241")
+            .addQueryParam("displayname", "test-e92")
+            .addQueryParam("name", "test-e92")
+            .addQueryParam("networkids", "204")
+            .addQueryParam("keypair", "mykeypair")
+            .addQueryParam("apiKey", "APIKEY")
+            .addQueryParam("signature", "Ar2B/ZVxMO2078cP0XliWWR4cQ0%3D")
+            .addHeader("Accept", "application/json")
+            .build(); 
+  
+      Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
+            .put(listTemplates, listTemplatesResponse)
+            .put(listOsTypes, listOsTypesResponse)
+            .put(listOsCategories, listOsCategoriesResponse)
+            .put(listZones, listZonesResponse)
+            .put(listServiceOfferings, listServiceOfferingsResponse)
+            .put(listAccounts, listAccountsResponse)
+            .put(listNetworks, listNetworksWithSecurityGroupsResponse)
+            .put(getZoneWithSecurityGroups, getZoneWithSecurityGroupsResponse)
+            .put(deployVM, deployVMResponse)
+            .put(queryAsyncJobResult, queryAsyncJobResultResponse)
+            .build();
+
+      Injector forKeyPair = requestsSendResponses(requestResponseMap);
+
+      String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test"));
+      Template template = forKeyPair.getInstance(TemplateBuilder.class)
+         .osFamily(OsFamily.CENTOS)
+         .locationId("2")
+         .build();
+      template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair")
+         .setupStaticNat(false)
+         .overrideLoginPrivateKey(privKey);
+
+      CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
+
+      NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
+            template);
+      assertNotNull(server);
+      assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
+   }
+
+   public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairGenerateSecurityGroup() throws IOException {
+      HttpRequest deployVM = HttpRequest.builder().method("GET")
+            .endpoint("http://localhost:8080/client/api")
+            .addQueryParam("response", "json")
+            .addQueryParam("command", "deployVirtualMachine")
+            .addQueryParam("zoneid", "2")
+            .addQueryParam("serviceofferingid", "1")
+            .addQueryParam("templateid", "241")
+            .addQueryParam("displayname", "test-e92")
+            .addQueryParam("name", "test-e92")
+            .addQueryParam("networkids", "204")
+            .addQueryParam("keypair", "mykeypair")
+            .addQueryParam("securitygroupids", "30")
+            .addQueryParam("apiKey", "APIKEY")
+            .addQueryParam("signature", "yNAiMYw3RstNj979udttALOHxfU%3D")
+            .addHeader("Accept", "application/json")
+            .build(); 
+  
+      Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
+            .put(listTemplates, listTemplatesResponse)
+            .put(listOsTypes, listOsTypesResponse)
+            .put(listOsCategories, listOsCategoriesResponse)
+            .put(listZones, listZonesResponse)
+            .put(listServiceOfferings, listServiceOfferingsResponse)
+            .put(listAccounts, listAccountsResponse)
+            .put(listNetworks, listNetworksWithSecurityGroupsResponse)
+            .put(getZoneWithSecurityGroups, getZoneWithSecurityGroupsResponse)
+            .put(deployVM, deployVMResponse)
+            .put(queryAsyncJobResult, queryAsyncJobResultSecurityGroupResponse)
+            .put(queryAsyncJobResultAuthorizeIngress, queryAsyncJobResultAuthorizeIngressResponse)
+            .put(getSecurityGroup, getSecurityGroupResponse)
+            .put(createSecurityGroup, createSecurityGroupResponse)
+            .put(authorizeIngress, authorizeIngressResponse)
+            .build();
+
+      Injector forKeyPair = requestsSendResponses(requestResponseMap);
+
+      String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test"));
+      Template template = forKeyPair.getInstance(TemplateBuilder.class)
+         .osFamily(OsFamily.CENTOS)
+         .locationId("2")
+         .build();
+      template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair")
+         .setupStaticNat(false)
+         .generateSecurityGroup(true)
+         .overrideLoginPrivateKey(privKey);
 
       CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
 
@@ -106,7 +278,7 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
       assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
    }
 
-   public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairAssignedToAccountAndDomain() {
+   public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairAssignedToAccountAndDomain() throws IOException {
       HttpRequest deployVM = HttpRequest.builder().method("GET")
             .endpoint("http://localhost:8080/client/api")
             .addQueryParam("response", "json")
@@ -140,8 +312,13 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
 
       Injector forKeyPair = requestsSendResponses(requestResponseMap);
 
+      String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test"));
       Template template = forKeyPair.getInstance(TemplateBuilder.class).osFamily(OsFamily.CENTOS).build();
-      template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair").account("account").domainId("domainId").setupStaticNat(false);
+      template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair")
+         .account("account")
+         .domainId("domainId")
+         .setupStaticNat(false)
+         .overrideLoginPrivateKey(privKey);
 
       CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);