From fbe637c8bfb951532a48b6f70eff0dc739654ee9 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Sun, 2 Jun 2013 12:23:19 -0700 Subject: [PATCH] JCLOUDS-106. Add proper support for generation/detection of keypair/security groups to CloudStack. --- .../cloudstack/CloudStackApiMetadata.java | 2 + .../compute/CloudStackComputeService.java | 185 +++++++++++++ ...CloudStackComputeServiceContextModule.java | 48 ++++ .../functions/OrphanedGroupsByZoneId.java | 77 ++++++ .../compute/loaders/CreateUniqueKeyPair.java | 69 +++++ .../loaders/FindSecurityGroupOrCreate.java | 83 ++++++ .../options/CloudStackTemplateOptions.java | 50 +++- .../predicates/AllNodesInGroupTerminated.java | 56 ++++ .../CloudStackComputeServiceAdapter.java | 59 +++- .../config/CloudStackProperties.java | 5 + .../cloudstack/domain/ZoneAndName.java | 97 +++++++ .../ZoneSecurityGroupNamePortsCidrs.java | 163 +++++++++++ .../features/SecurityGroupAsyncClient.java | 12 + .../features/SecurityGroupClient.java | 9 + .../CreateSecurityGroupIfNeeded.java | 124 +++++++++ .../predicates/SecurityGroupPredicates.java | 161 +++++++++++ .../predicates/SshKeyPairPredicates.java | 78 ++++++ ...dStackComputeServiceAdapterExpectTest.java | 185 ++++++++++++- .../loaders/CreateUniqueKeyPairTest.java | 73 +++++ .../FindSecurityGroupOrCreateTest.java | 261 ++++++++++++++++++ .../CloudStackTemplateOptionsTest.java | 44 +++ .../SecurityGroupAsyncClientTest.java | 18 ++ .../features/SecurityGroupClientLiveTest.java | 4 +- .../CreateSecurityGroupIfNeededTest.java | 237 ++++++++++++++++ ...dStackComputeServiceContextExpectTest.java | 79 ++++++ .../SecurityGroupPredicatesTest.java | 90 ++++++ ...authorizesecuritygroupingressresponse.json | 2 + .../createsecuritygroupresponse.json | 3 + .../resources/createsshkeypairresponse-2.json | 4 + .../resources/getsecuritygroupresponse.json | 1 + .../src/test/resources/getzoneresponse-2.json | 1 + .../resources/listnetworksresponse-2.json | 1 + ...yncjobresultresponse-authorizeingress.json | 34 +++ ...response-virtualmachine-securitygroup.json | 73 +++++ 34 files changed, 2369 insertions(+), 19 deletions(-) create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/CloudStackComputeService.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/functions/OrphanedGroupsByZoneId.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPair.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreate.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/predicates/AllNodesInGroupTerminated.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneAndName.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/domain/ZoneSecurityGroupNamePortsCidrs.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeeded.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicates.java create mode 100644 apis/cloudstack/src/main/java/org/jclouds/cloudstack/predicates/SshKeyPairPredicates.java create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/CreateUniqueKeyPairTest.java create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/loaders/FindSecurityGroupOrCreateTest.java create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/functions/CreateSecurityGroupIfNeededTest.java create mode 100644 apis/cloudstack/src/test/java/org/jclouds/cloudstack/predicates/SecurityGroupPredicatesTest.java create mode 100644 apis/cloudstack/src/test/resources/authorizesecuritygroupingressresponse.json create mode 100644 apis/cloudstack/src/test/resources/createsecuritygroupresponse.json create mode 100644 apis/cloudstack/src/test/resources/createsshkeypairresponse-2.json create mode 100644 apis/cloudstack/src/test/resources/getsecuritygroupresponse.json create mode 100644 apis/cloudstack/src/test/resources/getzoneresponse-2.json create mode 100644 apis/cloudstack/src/test/resources/listnetworksresponse-2.json create mode 100644 apis/cloudstack/src/test/resources/queryasyncjobresultresponse-authorizeingress.json create mode 100644 apis/cloudstack/src/test/resources/queryasyncjobresultresponse-virtualmachine-securitygroup.json 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 652a6f0230..53b9ed2dfc 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; } 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 0000000000..bb17cac860 --- /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 securityGroupMap; + protected final LoadingCache keyPairCache; + protected final Function, Multimap> orphanedGroupsByZoneId; + protected final GroupNamingConvention.Factory namingConvention; + protected final Supplier> zoneIdToZone; + + @Inject + protected CloudStackComputeService(ComputeServiceContext context, Map credentialStore, + @Memoized Supplier> images, @Memoized Supplier> sizes, + @Memoized Supplier> locations, ListNodesStrategy listNodesStrategy, + GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy, + CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy, + DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy, + SuspendNodeStrategy stopNodeStrategy, Provider templateBuilderProvider, + @Named("DEFAULT") Provider templateOptionsProvider, + @Named(TIMEOUT_NODE_RUNNING) Predicate> nodeRunning, + @Named(TIMEOUT_NODE_TERMINATED) Predicate> nodeTerminated, + @Named(TIMEOUT_NODE_SUSPENDED) Predicate> nodeSuspended, + InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory, + RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess, + PersistNodeCredentials persistNodeCredentials, Timeouts timeouts, + @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, CloudStackClient client, + LoadingCache securityGroupMap, + LoadingCache keyPairCache, + Function, Multimap> orphanedGroupsByZoneId, + GroupNamingConvention.Factory namingConvention, + Supplier> zoneIdToZone, + Optional 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 deadNodes) { + Multimap zoneToZoneAndGroupNames = orphanedGroupsByZoneId.apply(deadNodes); + for (String zoneId : zoneToZoneAndGroupNames.keySet()) { + cleanOrphanedGroupsInZone(ImmutableSet.copyOf(zoneToZoneAndGroupNames.get(zoneId)), zoneId); + } + } + + protected void cleanOrphanedGroupsInZone(Set groups, String zoneId) { + cleanupOrphanedSecurityGroupsInZone(groups, zoneId); + cleanupOrphanedKeyPairsInZone(groups, zoneId); + } + + private void cleanupOrphanedSecurityGroupsInZone(Set 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 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()); + } + + + +} 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 2eb613eb04..b4924bebd5 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>() { }).to(ZoneIdToZone.class); + bind(new TypeLiteral>() { + }).to(CreateUniqueKeyPair.class); bind(new TypeLiteral>>() { }).to(ZoneIdToZoneSupplier.class); + + bind(new TypeLiteral>() { + }).to(CreateSecurityGroupIfNeeded.class); + + bind(new TypeLiteral>() { + }).to(FindSecurityGroupOrCreate.class); + + bind(new TypeLiteral, Multimap>>() { + }).to(OrphanedGroupsByZoneId.class); + // to have the compute service adapter override default locations install(new LocationsFromComputeServiceAdapterModule(){}); } + + @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 @@ -185,6 +219,20 @@ public class CloudStackComputeServiceContextModule extends return retry(jobComplete, 1200, 1, 5, SECONDS); } + @Provides + @Singleton + protected LoadingCache keyPairMap( + CacheLoader in) { + return CacheBuilder.newBuilder().build(in); + } + + @Provides + @Singleton + protected LoadingCache securityGroupMap( + CacheLoader in) { + return CacheBuilder.newBuilder().build(in); + } + @Provides @Singleton protected LoadingCache> getIPForwardingRulesByVirtualMachine( 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 0000000000..50dbe5314d --- /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, Multimap> { + private final Predicate allNodesInGroupTerminated; + + @Inject + protected OrphanedGroupsByZoneId(ComputeService computeService) { + this(new AllNodesInGroupTerminated(checkNotNull(computeService, "computeService"))); + } + + @VisibleForTesting + OrphanedGroupsByZoneId(Predicate allNodesInGroupTerminated) { + this.allNodesInGroupTerminated = checkNotNull(allNodesInGroupTerminated, "allNodesInGroupTerminated"); + } + + public Multimap apply(Set deadNodes) { + Iterable nodesWithGroup = filter(deadNodes, NodePredicates.hasGroup()); + Set zoneAndGroupNames = ImmutableSet.copyOf(filter(transform(nodesWithGroup, + new Function() { + + @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 zoneToZoneAndGroupNames = Multimaps.transformValues(Multimaps.index(zoneAndGroupNames, + ZoneAndName.ZONE_FUNCTION), ZoneAndName.NAME_FUNCTION); + return zoneToZoneAndGroupNames; + } + +} 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 0000000000..0ae2b0501e --- /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 { + @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; + } + +} 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 0000000000..66893711cf --- /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 { + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + protected final CloudStackClient client; + protected final Function groupCreator; + + @Inject + public FindSecurityGroupOrCreate(CloudStackClient client, + Function 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()"; + } + +} 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 7a70b5f4c0..4b05a2116c 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); @@ -102,6 +106,21 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea return securityGroupIds; } + /** + * @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 */ @@ -167,6 +186,21 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea return keyPair; } + /** + * @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) */ @@ -212,6 +246,13 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea return options.securityGroupIds(securityGroupIds); } + /** + * @see CloudStackTemplateOptions#shouldGenerateSecurityGroup() + */ + public static CloudStackTemplateOptions generateSecurityGroup(boolean enable) { + return new CloudStackTemplateOptions().generateSecurityGroup(enable); + } + /** * @see CloudStackTemplateOptions#networkId */ @@ -260,6 +301,13 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea return options.keyPair(keyPair); } + /** + * @see CloudStackTemplateOptions#shouldGenerateKeyPair() + */ + public static CloudStackTemplateOptions generateKeyPair(boolean enable) { + return new CloudStackTemplateOptions().generateKeyPair(enable); + } + /** * @see CloudStackTemplateOptions#account */ 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 0000000000..d38a990705 --- /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 { + 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. or(locationId(input.getZone()), + parentLocationId(input.getZone()))), and(inGroup(input.getName()), TERMINATED)); + } +} 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 e839d30463..a4f71761db 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 credentialStore; private final Map optionsConverters; private final Supplier> zoneIdToZone; + private final LoadingCache securityGroupCache; + private final LoadingCache keyPairCache; + private final GroupNamingConvention.Factory namingConvention; @Inject public CloudStackComputeServiceAdapter(CloudStackClient client, Predicate jobComplete, @@ -107,7 +117,10 @@ public class CloudStackComputeServiceAdapter implements LoadingCache> vmToRules, Map credentialStore, Map optionsConverters, - Supplier> zoneIdToZone) { + Supplier> zoneIdToZone, + LoadingCache securityGroupCache, + LoadingCache 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 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 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. of()).build()); + options.securityGroupId(sg.getId()); + } + } + String templateId = template.getImage().getId(); String serviceOfferingId = template.getHardware().getId(); 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 34bc8ec810..ac6e0342c4 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"; } 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 0000000000..43c6ae04ad --- /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 NAME_FUNCTION = new Function(){ + + @Override + public String apply(ZoneAndName input) { + return input.getName(); + } + + }; + + public static final Function ZONE_FUNCTION = new Function(){ + + @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); + } +} 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 0000000000..415b90109f --- /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> { + protected abstract T self(); + + protected String zoneId; + protected String name; + protected Set ports = ImmutableSet.of(); + protected Set 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 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 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 { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + + private final Set ports; + private final Set cidrs; + + protected ZoneSecurityGroupNamePortsCidrs(String zoneId, String name, Set ports, + Set cidrs) { + super(zoneId, name); + this.ports = ports == null ? ImmutableSet.of() : ImmutableSet.copyOf(ports); + this.cidrs = cidrs == null ? ImmutableSet.of() : ImmutableSet.copyOf(cidrs); + } + + /** + * + * @return the set of ports to open in the security group + */ + public Set getPorts() { + return ports; + } + + /** + * + * @return the set of cidrs to give access to the open ports in the security group + */ + public Set 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 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 31f862b841..283ff5bad1 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 @@ -78,6 +78,18 @@ public interface SecurityGroupAsyncClient { @Fallback(NullOnNotFoundOr404.class) ListenableFuture 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 getSecurityGroupByName(@QueryParam("securitygroupname") String securityGroupName); + /** * @see SecurityGroupClient#createSecurityGroup */ 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 f3cb39ed82..ddb9422822 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 @@ -131,6 +131,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 * 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 0000000000..b6a2a98b88 --- /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 { + @Resource + @Named(ComputeServiceConstants.COMPUTE_LOGGER) + protected Logger logger = Logger.NULL; + protected final CloudStackClient client; + protected final Supplier> zoneIdToZone; + protected final Predicate jobComplete; + + @Inject + public CreateSecurityGroupIfNeeded(CloudStackClient client, + Predicate jobComplete, + Supplier> 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 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 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); + } + } + } +} 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 0000000000..bef1d29009 --- /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 portInRange(final int port) { + return new Predicate() { + + @Override + public boolean apply(SecurityGroup group) { + return Iterables.any(group.getIngressRules(), new Predicate() { + @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 hasCidr(final String cidr) { + return new Predicate() { + + @Override + public boolean apply(SecurityGroup group) { + return Iterables.any(group.getIngressRules(), new Predicate() { + @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 portInRangeForCidr(final int port, final String cidr) { + return new Predicate() { + + @Override + public boolean apply(SecurityGroup group) { + return Iterables.any(group.getIngressRules(), new Predicate() { + @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 any() { + return alwaysTrue(); + } + + /** + * matches name of the given security group + * + * @param name + * @return predicate that matches name + */ + public static Predicate nameEquals(final String name) { + checkNotNull(name, "name must be defined"); + + return new Predicate() { + @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 nameMatches(final Predicate name) { + checkNotNull(name, "name must be defined"); + + return new Predicate() { + @Override + public boolean apply(SecurityGroup ext) { + return name.apply(ext.getName()); + } + + @Override + public String toString() { + return "nameMatches(" + name + ")"; + } + }; + } +} 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 0000000000..3037eb1357 --- /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 nameMatches(final Predicate name) { + checkNotNull(name, "name must be defined"); + + return new Predicate() { + @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 nameEquals(final String name) { + checkNotNull(name, "name must be defined"); + + return new Predicate() { + @Override + public boolean apply(SshKeyPair ext) { + return ext.getName() != null && ext.getName().equals(name); + } + + @Override + public String toString() { + return "nameEquals(" + name + ")"; + } + }; + } +} 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 bab545927d..1e6ff6f846 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,11 @@ 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); @@ -106,7 +129,156 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build()); } - public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairAssignedToAccountAndDomain() { + 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 requestResponseMap = ImmutableMap. 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 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 requestResponseMap = ImmutableMap. 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 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 requestResponseMap = ImmutableMap. 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); + + NodeAndInitialCredentials server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92", + template); + assertNotNull(server); + assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build()); + } + + 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); 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 0000000000..1bb09e6899 --- /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>() { + }).toInstance(Suppliers.ofInstance("1")); + bind(CloudStackClient.class).toInstance(client); + } + + }).getInstance(CreateUniqueKeyPair.class); + + assertEquals(parser.load("group-1"), pair); + + verify(client, keyClient); + } + +} 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 0000000000..fa6b7499f5 --- /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. 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. of()).build(); + + FindSecurityGroupOrCreate parser = Guice.createInjector(new AbstractModule() { + + @Override + protected void configure() { + bind(new TypeLiteral>() { + }).toInstance(Suppliers.ofInstance("1")); + bind(CloudStackClient.class).toInstance(client); + bind(new TypeLiteral>() {}). + to(ZoneIdToZone.class); + bind(new TypeLiteral>>() {}). + to(ZoneIdToZoneSupplier.class); + bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60"); + + bind(new TypeLiteral>() { + }).to(CreateSecurityGroupIfNeeded.class); + + bind(new TypeLiteral>() { + }).to(FindSecurityGroupOrCreate.class); + } + + @Provides + @Singleton + protected Predicate 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. of()).build(); + + FindSecurityGroupOrCreate parser = Guice.createInjector(new AbstractModule() { + + @Override + protected void configure() { + bind(new TypeLiteral>() { + }).toInstance(Suppliers.ofInstance("1")); + bind(CloudStackClient.class).toInstance(client); + bind(new TypeLiteral>() {}). + to(ZoneIdToZone.class); + bind(new TypeLiteral>>() {}). + to(ZoneIdToZoneSupplier.class); + bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60"); + + bind(new TypeLiteral>() { + }).to(CreateSecurityGroupIfNeeded.class); + + bind(new TypeLiteral>() { + }).to(FindSecurityGroupOrCreate.class); + } + + @Provides + @Singleton + protected Predicate 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. of()).build(); + + FindSecurityGroupOrCreate parser = Guice.createInjector(new AbstractModule() { + + @Override + protected void configure() { + bind(new TypeLiteral>() { + }).toInstance(Suppliers.ofInstance("1")); + bind(CloudStackClient.class).toInstance(client); + bind(new TypeLiteral>() {}). + to(ZoneIdToZone.class); + bind(new TypeLiteral>>() {}). + to(ZoneIdToZoneSupplier.class); + bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60"); + + bind(new TypeLiteral>() { + }).to(CreateSecurityGroupIfNeeded.class); + + bind(new TypeLiteral>() { + }).to(FindSecurityGroupOrCreate.class); + } + + @Provides + @Singleton + protected Predicate jobComplete(JobComplete jobComplete) { + return retry(jobComplete, 1200, 1, 5, SECONDS); + } + + }).getInstance(FindSecurityGroupOrCreate.class); + + assertEquals(parser.load(input), group); + + verify(client, secClient, zoneClient, zone, group); + } + +} 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 33a66fc892..7bb81f568d 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; @@ -87,6 +89,27 @@ public class CloudStackTemplateOptionsTest { assertEquals(options.as(CloudStackTemplateOptions.class).getSecurityGroupIds(), ImmutableSet.of("3")); } + @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(); @@ -170,6 +193,27 @@ public class CloudStackTemplateOptionsTest { .shouldSetupStaticNat()); } + @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"); 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 516d8f1b51..4240530e67 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. 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. of("goo")); 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 f2d9d3f014..f5cb8ed9aa 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; 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 0000000000..e1319110b6 --- /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. 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. of()).build(); + + CreateSecurityGroupIfNeeded parser = Guice.createInjector(new AbstractModule() { + + @Override + protected void configure() { + bind(new TypeLiteral>() { + }).toInstance(Suppliers.ofInstance("1")); + bind(CloudStackClient.class).toInstance(client); + bind(new TypeLiteral>() {}). + to(ZoneIdToZone.class); + bind(new TypeLiteral>>() {}). + to(ZoneIdToZoneSupplier.class); + bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60"); + } + + @Provides + @Singleton + protected Predicate 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. of()).build(); + + CreateSecurityGroupIfNeeded parser = Guice.createInjector(new AbstractModule() { + + @Override + protected void configure() { + bind(new TypeLiteral>() { + }).toInstance(Suppliers.ofInstance("1")); + bind(CloudStackClient.class).toInstance(client); + bind(new TypeLiteral>() {}). + to(ZoneIdToZone.class); + bind(new TypeLiteral>>() {}). + to(ZoneIdToZoneSupplier.class); + bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60"); + } + + @Provides + @Singleton + protected Predicate 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. of()).build(); + + CreateSecurityGroupIfNeeded parser = Guice.createInjector(new AbstractModule() { + + @Override + protected void configure() { + bind(new TypeLiteral>() { + }).toInstance(Suppliers.ofInstance("1")); + bind(CloudStackClient.class).toInstance(client); + bind(new TypeLiteral>() {}). + to(ZoneIdToZone.class); + bind(new TypeLiteral>>() {}). + to(ZoneIdToZoneSupplier.class); + bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60"); + } + + @Provides + @Singleton + protected Predicate jobComplete(JobComplete jobComplete) { + return retry(jobComplete, 1200, 1, 5, SECONDS); + } + + }).getInstance(CreateSecurityGroupIfNeeded.class); + + assertEquals(parser.apply(input), group); + + verify(client, zoneClient, zone); + } + +} 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 9d24257f5d..8b8d01d156 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 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 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 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(); } 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 0000000000..16e473a4f9 --- /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())); + } + +} diff --git a/apis/cloudstack/src/test/resources/authorizesecuritygroupingressresponse.json b/apis/cloudstack/src/test/resources/authorizesecuritygroupingressresponse.json new file mode 100644 index 0000000000..e0280e4e0d --- /dev/null +++ b/apis/cloudstack/src/test/resources/authorizesecuritygroupingressresponse.json @@ -0,0 +1,2 @@ + { "authorizesecuritygroupingressresponse" : + {"jobid":"13330fc9-8b3e-4582-aa3e-90883c041010"} } diff --git a/apis/cloudstack/src/test/resources/createsecuritygroupresponse.json b/apis/cloudstack/src/test/resources/createsecuritygroupresponse.json new file mode 100644 index 0000000000..99a2ad20fd --- /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"} + } } 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 0000000000..3dfb3b4d51 --- /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"} } } diff --git a/apis/cloudstack/src/test/resources/getsecuritygroupresponse.json b/apis/cloudstack/src/test/resources/getsecuritygroupresponse.json new file mode 100644 index 0000000000..27743eda6a --- /dev/null +++ b/apis/cloudstack/src/test/resources/getsecuritygroupresponse.json @@ -0,0 +1 @@ +{ "listsecuritygroupsresponse" : { count: 0, "securitygroup" : [ { } ] } } 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 0000000000..72617165e2 --- /dev/null +++ b/apis/cloudstack/src/test/resources/getzoneresponse-2.json @@ -0,0 +1 @@ +{ "listzonesresponse" : { "zone" : [ {"id":2,"name":"Chicago","networktype":"Advanced","securitygroupsenabled":true} ] } } 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 0000000000..2f10824a98 --- /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"} ] } } 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 0000000000..7ee489d244 --- /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" + } +} 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 0000000000..cc75e11e71 --- /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" + } + +}