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

This commit is contained in:
Andrew Bayer 2013-06-23 12:15:36 -07:00
parent 6f53e2b70b
commit ca369df7cc
35 changed files with 2400 additions and 86 deletions

View File

@ -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;
}

View File

@ -0,0 +1,185 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.cloudstack.compute;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_SUSPENDED;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_TERMINATED;
import static org.jclouds.cloudstack.predicates.SshKeyPairPredicates.nameMatches;
import static org.jclouds.cloudstack.predicates.ZonePredicates.supportsSecurityGroups;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.jclouds.Constants;
import org.jclouds.collect.Memoized;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.callables.RunScriptOnNode;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.compute.extensions.ImageExtension;
import org.jclouds.compute.functions.GroupNamingConvention;
import org.jclouds.compute.internal.BaseComputeService;
import org.jclouds.compute.internal.PersistNodeCredentials;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.compute.reference.ComputeServiceConstants.Timeouts;
import org.jclouds.compute.strategy.CreateNodesInGroupThenAddToSet;
import org.jclouds.compute.strategy.DestroyNodeStrategy;
import org.jclouds.compute.strategy.GetImageStrategy;
import org.jclouds.compute.strategy.GetNodeMetadataStrategy;
import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap;
import org.jclouds.compute.strategy.ListNodesStrategy;
import org.jclouds.compute.strategy.RebootNodeStrategy;
import org.jclouds.compute.strategy.ResumeNodeStrategy;
import org.jclouds.compute.strategy.SuspendNodeStrategy;
import org.jclouds.domain.Credentials;
import org.jclouds.domain.Location;
import org.jclouds.cloudstack.CloudStackClient;
import org.jclouds.cloudstack.compute.options.CloudStackTemplateOptions;
import org.jclouds.cloudstack.domain.SecurityGroup;
import org.jclouds.cloudstack.domain.SshKeyPair;
import org.jclouds.cloudstack.domain.Zone;
import org.jclouds.cloudstack.domain.ZoneAndName;
import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
import org.jclouds.cloudstack.predicates.SecurityGroupPredicates;
import org.jclouds.scriptbuilder.functions.InitAdminAccess;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.ListeningExecutorService;
/**
* @author Adrian Cole
*/
@Singleton
public class CloudStackComputeService extends BaseComputeService {
protected final CloudStackClient client;
protected final LoadingCache<ZoneAndName, SecurityGroup> securityGroupMap;
protected final LoadingCache<String, SshKeyPair> keyPairCache;
protected final Function<Set<? extends NodeMetadata>, Multimap<String, String>> orphanedGroupsByZoneId;
protected final GroupNamingConvention.Factory namingConvention;
protected final Supplier<LoadingCache<String, Zone>> zoneIdToZone;
@Inject
protected CloudStackComputeService(ComputeServiceContext context, Map<String, Credentials> credentialStore,
@Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes,
@Memoized Supplier<Set<? extends Location>> locations, ListNodesStrategy listNodesStrategy,
GetImageStrategy getImageStrategy, GetNodeMetadataStrategy getNodeMetadataStrategy,
CreateNodesInGroupThenAddToSet runNodesAndAddToSetStrategy, RebootNodeStrategy rebootNodeStrategy,
DestroyNodeStrategy destroyNodeStrategy, ResumeNodeStrategy startNodeStrategy,
SuspendNodeStrategy stopNodeStrategy, Provider<TemplateBuilder> templateBuilderProvider,
@Named("DEFAULT") Provider<TemplateOptions> templateOptionsProvider,
@Named(TIMEOUT_NODE_RUNNING) Predicate<AtomicReference<NodeMetadata>> nodeRunning,
@Named(TIMEOUT_NODE_TERMINATED) Predicate<AtomicReference<NodeMetadata>> nodeTerminated,
@Named(TIMEOUT_NODE_SUSPENDED) Predicate<AtomicReference<NodeMetadata>> nodeSuspended,
InitializeRunScriptOnNodeOrPlaceInBadMap.Factory initScriptRunnerFactory,
RunScriptOnNode.Factory runScriptOnNodeFactory, InitAdminAccess initAdminAccess,
PersistNodeCredentials persistNodeCredentials, Timeouts timeouts,
@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, CloudStackClient client,
LoadingCache<ZoneAndName, SecurityGroup> securityGroupMap,
LoadingCache<String, SshKeyPair> keyPairCache,
Function<Set<? extends NodeMetadata>, Multimap<String, String>> orphanedGroupsByZoneId,
GroupNamingConvention.Factory namingConvention,
Supplier<LoadingCache<String, Zone>> zoneIdToZone,
Optional<ImageExtension> imageExtension) {
super(context, credentialStore, images, sizes, locations, listNodesStrategy, getImageStrategy,
getNodeMetadataStrategy, runNodesAndAddToSetStrategy, rebootNodeStrategy, destroyNodeStrategy,
startNodeStrategy, stopNodeStrategy, templateBuilderProvider, templateOptionsProvider, nodeRunning,
nodeTerminated, nodeSuspended, initScriptRunnerFactory, initAdminAccess, runScriptOnNodeFactory,
persistNodeCredentials, timeouts, userExecutor, imageExtension);
this.zoneIdToZone = zoneIdToZone;
this.client = checkNotNull(client, "client");
this.securityGroupMap = checkNotNull(securityGroupMap, "securityGroupMap");
this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
this.orphanedGroupsByZoneId = checkNotNull(orphanedGroupsByZoneId, "orphanedGroupsByZoneId");
this.namingConvention = checkNotNull(namingConvention, "namingConvention");
}
@Override
protected void cleanUpIncidentalResourcesOfDeadNodes(Set<? extends NodeMetadata> deadNodes) {
Multimap<String, String> zoneToZoneAndGroupNames = orphanedGroupsByZoneId.apply(deadNodes);
for (String zoneId : zoneToZoneAndGroupNames.keySet()) {
cleanOrphanedGroupsInZone(ImmutableSet.copyOf(zoneToZoneAndGroupNames.get(zoneId)), zoneId);
}
}
protected void cleanOrphanedGroupsInZone(Set<String> groups, String zoneId) {
cleanupOrphanedSecurityGroupsInZone(groups, zoneId);
cleanupOrphanedKeyPairsInZone(groups, zoneId);
}
private void cleanupOrphanedSecurityGroupsInZone(Set<String> groups, String zoneId) {
Zone zone = zoneIdToZone.get().getUnchecked(zoneId);
if (supportsSecurityGroups().apply(zone)) {
for (String group : groups) {
for (SecurityGroup securityGroup : Iterables.filter(client.getSecurityGroupClient().listSecurityGroups(),
SecurityGroupPredicates.nameMatches(namingConvention.create().containsGroup(group)))) {
ZoneAndName zoneAndName = ZoneAndName.fromZoneAndName(zoneId, securityGroup.getName());
logger.debug(">> deleting securityGroup(%s)", zoneAndName);
client.getSecurityGroupClient().deleteSecurityGroup(securityGroup.getId());
// TODO: test this clear happens
securityGroupMap.invalidate(zoneAndName);
logger.debug("<< deleted securityGroup(%s)", zoneAndName);
}
}
}
}
private void cleanupOrphanedKeyPairsInZone(Set<String> groups, String zoneId) {
for (String group : groups) {
for (SshKeyPair pair : Iterables.filter(client.getSSHKeyPairClient().listSSHKeyPairs(),
nameMatches(namingConvention.create().containsGroup(group)))) {
logger.debug(">> deleting keypair(%s)", pair.getName());
client.getSSHKeyPairClient().deleteSSHKeyPair(pair.getName());
// TODO: test this clear happens
keyPairCache.invalidate(pair.getName());
logger.debug("<< deleted keypair(%s)", pair.getName());
}
keyPairCache.invalidate(namingConvention.create().sharedNameForGroup(group));
}
}
/**
* returns template options, except of type {@link CloudStackTemplateOptions}.
*/
@Override
public CloudStackTemplateOptions templateOptions() {
return CloudStackTemplateOptions.class.cast(super.templateOptions());
}
}

View File

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

View File

@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.compute.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Sets.filter;
import java.util.Set;
import javax.inject.Inject;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.predicates.NodePredicates;
import org.jclouds.domain.LocationScope;
import org.jclouds.cloudstack.compute.predicates.AllNodesInGroupTerminated;
import org.jclouds.cloudstack.domain.ZoneAndName;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
/**
*
* @author Adrian Cole
*/
public class OrphanedGroupsByZoneId implements Function<Set<? extends NodeMetadata>, Multimap<String, String>> {
private final Predicate<ZoneAndName> allNodesInGroupTerminated;
@Inject
protected OrphanedGroupsByZoneId(ComputeService computeService) {
this(new AllNodesInGroupTerminated(checkNotNull(computeService, "computeService")));
}
@VisibleForTesting
OrphanedGroupsByZoneId(Predicate<ZoneAndName> allNodesInGroupTerminated) {
this.allNodesInGroupTerminated = checkNotNull(allNodesInGroupTerminated, "allNodesInGroupTerminated");
}
public Multimap<String, String> apply(Set<? extends NodeMetadata> deadNodes) {
Iterable<? extends NodeMetadata> nodesWithGroup = filter(deadNodes, NodePredicates.hasGroup());
Set<ZoneAndName> zoneAndGroupNames = ImmutableSet.copyOf(filter(transform(nodesWithGroup,
new Function<NodeMetadata, ZoneAndName>() {
@Override
public ZoneAndName apply(NodeMetadata input) {
String zoneId = input.getLocation().getScope() == LocationScope.HOST ? input.getLocation()
.getParent().getId() : input.getLocation().getId();
return ZoneAndName.fromZoneAndName(zoneId, input.getGroup());
}
}), allNodesInGroupTerminated));
Multimap<String, String> zoneToZoneAndGroupNames = Multimaps.transformValues(Multimaps.index(zoneAndGroupNames,
ZoneAndName.ZONE_FUNCTION), ZoneAndName.NAME_FUNCTION);
return zoneToZoneAndGroupNames;
}
}

View File

@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.compute.loaders;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.cloudstack.CloudStackClient;
import org.jclouds.cloudstack.domain.AsyncCreateResponse;
import org.jclouds.cloudstack.domain.SshKeyPair;
import org.jclouds.cloudstack.strategy.BlockUntilJobCompletesAndReturnResult;
import com.google.common.base.Optional;
import com.google.common.cache.CacheLoader;
import com.google.inject.Inject;
/**
* @author Adam Lowe
* @author Andrew Bayer
*/
@Singleton
public class CreateUniqueKeyPair extends CacheLoader<String, SshKeyPair> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
protected final CloudStackClient client;
@Inject
public CreateUniqueKeyPair(CloudStackClient client) {
this.client = checkNotNull(client, "client");
}
@Override
public SshKeyPair load(String input) {
SshKeyPair keyPair = null;
while (keyPair == null) {
try {
keyPair = client.getSSHKeyPairClient().createSSHKeyPair(input);
logger.debug(">> creating SSH key pair with name %s", input);
} catch (IllegalStateException e) {
}
}
logger.debug("<< created keyPair(%s)", keyPair.getName());
return keyPair;
}
}

View File

@ -0,0 +1,83 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.compute.loaders;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.cloudstack.CloudStackClient;
import org.jclouds.cloudstack.domain.SecurityGroup;
import org.jclouds.cloudstack.domain.Zone;
import org.jclouds.cloudstack.domain.ZoneAndName;
import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.Atomics;
/**
*
* @author Adrian Cole
* @author Andrew Bayer
*/
public class FindSecurityGroupOrCreate extends CacheLoader<ZoneAndName, SecurityGroup> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
protected final CloudStackClient client;
protected final Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup> groupCreator;
@Inject
public FindSecurityGroupOrCreate(CloudStackClient client,
Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup> groupCreator) {
this.client = checkNotNull(client, "client");
this.groupCreator = checkNotNull(groupCreator, "groupCreator");
}
@Override
public SecurityGroup load(ZoneAndName in) {
SecurityGroup group = client.getSecurityGroupClient().getSecurityGroupByName(in.getName());
if (group != null) {
return group;
} else {
return createNewSecurityGroup(in);
}
}
private SecurityGroup createNewSecurityGroup(ZoneAndName in) {
checkState(
checkNotNull(in, "ZoneSecurityGrioupNamePortsCidrs") instanceof ZoneSecurityGroupNamePortsCidrs,
"programming error: when issuing get to this cacheloader, you need to pass an instance of ZoneSecurityGroupNamePortsCidrs, not %s",
in);
ZoneSecurityGroupNamePortsCidrs zoneSecurityGroupNamePortsCidrs = ZoneSecurityGroupNamePortsCidrs.class.cast(in);
return groupCreator.apply(zoneSecurityGroupNamePortsCidrs);
}
@Override
public String toString() {
return "returnExistingSecurityGroupInZoneOrCreateAsNeeded()";
}
}

View File

@ -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
*/

View File

@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.compute.predicates;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.and;
import static com.google.common.collect.Iterables.all;
import static org.jclouds.compute.predicates.NodePredicates.TERMINATED;
import static org.jclouds.compute.predicates.NodePredicates.inGroup;
import static org.jclouds.compute.predicates.NodePredicates.locationId;
import static org.jclouds.compute.predicates.NodePredicates.parentLocationId;
import javax.inject.Inject;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.cloudstack.domain.ZoneAndName;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
/**
* @author Adrian Cole
*/
public class AllNodesInGroupTerminated implements Predicate<ZoneAndName> {
private final ComputeService computeService;
//TODO: TESTME
@Inject
public AllNodesInGroupTerminated(ComputeService computeService) {
this.computeService = checkNotNull(computeService, "computeService");
}
@Override
public boolean apply(ZoneAndName input) {
// new nodes can have the zone as their location, existing nodes, the parent is the
// location
return all(computeService.listNodesDetailsMatching(Predicates.<ComputeMetadata> or(locationId(input.getZone()),
parentLocationId(input.getZone()))), and(inGroup(input.getName()), TERMINATED));
}
}

View File

@ -25,6 +25,8 @@ import static com.google.common.collect.Iterables.get;
import static org.jclouds.cloudstack.options.DeployVirtualMachineOptions.Builder.displayName;
import static org.jclouds.cloudstack.options.ListTemplatesOptions.Builder.id;
import static org.jclouds.cloudstack.predicates.TemplatePredicates.isReady;
import static org.jclouds.cloudstack.predicates.ZonePredicates.supportsSecurityGroups;
import static org.jclouds.ssh.SshKeys.fingerprintPrivateKey;
import java.util.List;
import java.util.Map;
@ -45,10 +47,14 @@ import org.jclouds.cloudstack.domain.IPForwardingRule;
import org.jclouds.cloudstack.domain.Network;
import org.jclouds.cloudstack.domain.NetworkType;
import org.jclouds.cloudstack.domain.PublicIPAddress;
import org.jclouds.cloudstack.domain.SecurityGroup;
import org.jclouds.cloudstack.domain.ServiceOffering;
import org.jclouds.cloudstack.domain.SshKeyPair;
import org.jclouds.cloudstack.domain.Template;
import org.jclouds.cloudstack.domain.VirtualMachine;
import org.jclouds.cloudstack.domain.Zone;
import org.jclouds.cloudstack.domain.ZoneAndName;
import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
import org.jclouds.cloudstack.functions.CreateFirewallRulesForIP;
import org.jclouds.cloudstack.functions.CreatePortForwardingRulesForIP;
import org.jclouds.cloudstack.functions.StaticNATVirtualMachineInNetwork;
@ -58,6 +64,7 @@ import org.jclouds.cloudstack.options.ListFirewallRulesOptions;
import org.jclouds.cloudstack.strategy.BlockUntilJobCompletesAndReturnResult;
import org.jclouds.collect.Memoized;
import org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.functions.GroupNamingConvention;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.domain.Credentials;
import org.jclouds.domain.LoginCredentials;
@ -96,6 +103,9 @@ public class CloudStackComputeServiceAdapter implements
private final Map<String, Credentials> credentialStore;
private final Map<NetworkType, ? extends OptionsConverter> optionsConverters;
private final Supplier<LoadingCache<String, Zone>> zoneIdToZone;
private final LoadingCache<ZoneAndName, SecurityGroup> securityGroupCache;
private final LoadingCache<String, SshKeyPair> keyPairCache;
private final GroupNamingConvention.Factory namingConvention;
@Inject
public CloudStackComputeServiceAdapter(CloudStackClient client, Predicate<String> jobComplete,
@ -107,7 +117,10 @@ public class CloudStackComputeServiceAdapter implements
LoadingCache<String, Set<IPForwardingRule>> vmToRules,
Map<String, Credentials> credentialStore,
Map<NetworkType, ? extends OptionsConverter> optionsConverters,
Supplier<LoadingCache<String, Zone>> zoneIdToZone) {
Supplier<LoadingCache<String, Zone>> zoneIdToZone,
LoadingCache<ZoneAndName, SecurityGroup> securityGroupCache,
LoadingCache<String, SshKeyPair> keyPairCache,
GroupNamingConvention.Factory namingConvention) {
this.client = checkNotNull(client, "client");
this.jobComplete = checkNotNull(jobComplete, "jobComplete");
this.networkSupplier = checkNotNull(networkSupplier, "networkSupplier");
@ -118,8 +131,11 @@ public class CloudStackComputeServiceAdapter implements
this.setupFirewallRulesForIP = checkNotNull(setupFirewallRulesForIP, "setupFirewallRulesForIP");
this.vmToRules = checkNotNull(vmToRules, "vmToRules");
this.credentialStore = checkNotNull(credentialStore, "credentialStore");
this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache");
this.keyPairCache = checkNotNull(keyPairCache, "keyPairCache");
this.optionsConverters = optionsConverters;
this.zoneIdToZone = zoneIdToZone;
this.namingConvention = namingConvention;
}
@Override
@ -134,12 +150,7 @@ public class CloudStackComputeServiceAdapter implements
Map<String, Network> networks = networkSupplier.get();
final String zoneId = template.getLocation().getId();
Zone zone = null;
try {
zone = zoneIdToZone.get().get(zoneId);
} catch (ExecutionException e) {
throw Throwables.propagate(e);
}
Zone zone = zoneIdToZone.get().getUnchecked(zoneId);
CloudStackTemplateOptions templateOptions = template.getOptions().as(CloudStackTemplateOptions.class);
@ -163,15 +174,44 @@ 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());
SshKeyPair keyPair = null;
if (templateOptions.getLoginPrivateKey() != null) {
String pem = templateOptions.getLoginPrivateKey();
keyPair = SshKeyPair.builder().name(templateOptions.getKeyPair())
.fingerprint(fingerprintPrivateKey(pem)).privateKey(pem).build();
keyPairCache.asMap().put(keyPair.getName(), keyPair);
options.keyPair(keyPair.getName());
} else if (client.getSSHKeyPairClient().getSSHKeyPair(templateOptions.getKeyPair()) != null) {
keyPair = client.getSSHKeyPairClient().getSSHKeyPair(templateOptions.getKeyPair());
}
if (keyPair != null) {
keyPairCache.asMap().put(keyPair.getName(), keyPair);
options.keyPair(keyPair.getName());
}
} else if (templateOptions.shouldGenerateKeyPair()) {
SshKeyPair keyPair = keyPairCache.getUnchecked(namingConvention.create()
.sharedNameForGroup(group));
keyPairCache.asMap().put(keyPair.getName(), keyPair);
templateOptions.keyPair(keyPair.getName());
options.keyPair(keyPair.getName());
}
if (supportsSecurityGroups().apply(zone)) {
List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts());
if (templateOptions.getSecurityGroupIds().size() == 0
&& inboundPorts.size() > 0
&& templateOptions.shouldGenerateSecurityGroup()) {
String securityGroupName = namingConvention.create().sharedNameForGroup(group);
SecurityGroup sg = securityGroupCache.getUnchecked(ZoneSecurityGroupNamePortsCidrs.builder()
.zone(zone.getId())
.name(securityGroupName)
.ports(ImmutableSet.copyOf(inboundPorts))
.cidrs(ImmutableSet.<String> of()).build());
options.securityGroupId(sg.getId());
}
}
String templateId = template.getImage().getId();
String serviceOfferingId = template.getHardware().getId();
@ -181,13 +221,15 @@ public class CloudStackComputeServiceAdapter implements
templateId, options);
VirtualMachine vm = blockUntilJobCompletesAndReturnResult.<VirtualMachine>apply(job);
logger.debug("--- virtualmachine: %s", vm);
LoginCredentials credentials = null;
LoginCredentials.Builder credentialsBuilder = LoginCredentials.builder();
if (!vm.isPasswordEnabled() || templateOptions.getKeyPair() != null) {
credentials = LoginCredentials.fromCredentials(credentialStore.get("keypair#" + templateOptions.getKeyPair()));
SshKeyPair keyPair = keyPairCache.getUnchecked(templateOptions.getKeyPair());
credentialsBuilder.privateKey(keyPair.getPrivateKey());
} else {
assert vm.getPassword() != null : vm;
credentials = LoginCredentials.builder().password(vm.getPassword()).build();
credentialsBuilder.password(vm.getPassword());
}
if (templateOptions.shouldSetupStaticNat()) {
Capabilities capabilities = client.getConfigurationClient().listCapabilities();
// TODO: possibly not all network ids, do we want to do this
@ -208,7 +250,7 @@ public class CloudStackComputeServiceAdapter implements
}
}
}
return new NodeAndInitialCredentials<VirtualMachine>(vm, vm.getId() + "", credentials);
return new NodeAndInitialCredentials<VirtualMachine>(vm, vm.getId() + "", credentialsBuilder.build());
}
@Override

View File

@ -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";
}

View File

@ -0,0 +1,97 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.domain;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.collect.Iterables;
/**
* Helpful when looking for resources by zone and name
*
* @author Adrian Cole
*/
public class ZoneAndName {
public static final Function<ZoneAndName, String> NAME_FUNCTION = new Function<ZoneAndName, String>(){
@Override
public String apply(ZoneAndName input) {
return input.getName();
}
};
public static final Function<ZoneAndName, String> ZONE_FUNCTION = new Function<ZoneAndName, String>(){
@Override
public String apply(ZoneAndName input) {
return input.getZone();
}
};
public static ZoneAndName fromZoneAndName(String zoneId, String name) {
return new ZoneAndName(zoneId, name);
}
protected final String zoneId;
protected final String name;
protected ZoneAndName(String zoneId, String name) {
this.zoneId = checkNotNull(zoneId, "zoneId");
this.name = checkNotNull(name, "name");
}
public String getZone() {
return zoneId;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ZoneAndName that = ZoneAndName.class.cast(o);
return equal(this.zoneId, that.zoneId) && equal(this.name, that.name);
}
@Override
public int hashCode() {
return Objects.hashCode(zoneId, name);
}
@Override
public String toString() {
return string().toString();
}
protected ToStringHelper string() {
return Objects.toStringHelper("").add("zoneId", zoneId).add("name", name);
}
}

View File

@ -0,0 +1,163 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Set;
import org.jclouds.javax.annotation.Nullable;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.collect.ImmutableSet;
/**
* @author Andrew Bayer
*/
public class ZoneSecurityGroupNamePortsCidrs extends ZoneAndName {
public static Builder<?> builder() {
return new ConcreteBuilder();
}
public Builder<?> toBuilder() {
return new ConcreteBuilder().fromZoneSecurityGroupNamePortsCidrs(this);
}
public abstract static class Builder<T extends Builder<T>> {
protected abstract T self();
protected String zoneId;
protected String name;
protected Set<Integer> ports = ImmutableSet.of();
protected Set<String> cidrs = ImmutableSet.of();
/**
* @see ZoneSecurityGroupNamePortsCidrs#getZone()
*/
public T zone(String zoneId) {
this.zoneId = zoneId;
return self();
}
/**
* @see ZoneSecurityGroupNamePortsCidrs#getName()
*/
public T name(String name) {
this.name = name;
return self();
}
/**
* @see ZoneSecurityGroupNamePortsCidrs#getPorts()
*/
public T ports(Set<Integer> ports) {
this.ports = ImmutableSet.copyOf(checkNotNull(ports, "ports"));
return self();
}
public T ports(Integer... in) {
return ports(ImmutableSet.copyOf(in));
}
/**
* @see ZoneSecurityGroupNamePortsCidrs#getCidrs()
*/
public T cidrs(Set<String> cidrs) {
this.cidrs = ImmutableSet.copyOf(checkNotNull(cidrs, "cidrs"));
return self();
}
public T cidrs(String... in) {
return cidrs(ImmutableSet.copyOf(in));
}
public ZoneSecurityGroupNamePortsCidrs build() {
return new ZoneSecurityGroupNamePortsCidrs(zoneId, name, ports, cidrs);
}
public T fromZoneSecurityGroupNamePortsCidrs(ZoneSecurityGroupNamePortsCidrs in) {
return this.zone(in.getZone())
.name(in.getName())
.ports(in.getPorts())
.cidrs(in.getCidrs());
}
}
private static class ConcreteBuilder extends Builder<ConcreteBuilder> {
@Override
protected ConcreteBuilder self() {
return this;
}
}
private final Set<Integer> ports;
private final Set<String> cidrs;
protected ZoneSecurityGroupNamePortsCidrs(String zoneId, String name, Set<Integer> ports,
Set<String> cidrs) {
super(zoneId, name);
this.ports = ports == null ? ImmutableSet.<Integer>of() : ImmutableSet.copyOf(ports);
this.cidrs = cidrs == null ? ImmutableSet.<String>of() : ImmutableSet.copyOf(cidrs);
}
/**
*
* @return the set of ports to open in the security group
*/
public Set<Integer> getPorts() {
return ports;
}
/**
*
* @return the set of cidrs to give access to the open ports in the security group
*/
public Set<String> getCidrs() {
return cidrs;
}
@Override
public int hashCode() {
return Objects.hashCode(zoneId, name, ports, cidrs);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
ZoneSecurityGroupNamePortsCidrs that = ZoneSecurityGroupNamePortsCidrs.class.cast(obj);
return Objects.equal(this.zoneId, that.zoneId)
&& Objects.equal(this.name, that.name)
&& Objects.equal(this.ports, that.ports)
&& Objects.equal(this.cidrs, that.cidrs);
}
protected ToStringHelper string() {
return Objects.toStringHelper(this)
.add("zoneId", zoneId).add("name", name).add("ports", ports).add("cidrs", cidrs);
}
@Override
public String toString() {
return string().toString();
}
}

View File

@ -78,6 +78,18 @@ public interface SecurityGroupAsyncClient {
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<SecurityGroup> getSecurityGroup(@QueryParam("id") String id);
/**
* @see SecurityGroupClient#getSecurityGroupByName
*/
@Named("listSecurityGroups")
@GET
@QueryParams(keys = { "command", "listAll" }, values = { "listSecurityGroups", "true" })
@SelectJson("securitygroup")
@OnlyElement
@Consumes(MediaType.APPLICATION_JSON)
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<SecurityGroup> getSecurityGroupByName(@QueryParam("securitygroupname") String securityGroupName);
/**
* @see SecurityGroupClient#createSecurityGroup
*/

View File

@ -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
*

View File

@ -0,0 +1,124 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.cloudstack.functions;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.find;
import static org.jclouds.cloudstack.predicates.SecurityGroupPredicates.nameEquals;
import static org.jclouds.cloudstack.predicates.SecurityGroupPredicates.portInRangeForCidr;
import static org.jclouds.cloudstack.predicates.ZonePredicates.supportsSecurityGroups;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.cloudstack.CloudStackClient;
import org.jclouds.cloudstack.domain.IngressRule;
import org.jclouds.cloudstack.domain.SecurityGroup;
import org.jclouds.cloudstack.domain.Zone;
import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
/**
*
* @author Adrian Cole
* @author Andrew Bayer
*/
@Singleton
public class CreateSecurityGroupIfNeeded implements Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup> {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
protected final CloudStackClient client;
protected final Supplier<LoadingCache<String, Zone>> zoneIdToZone;
protected final Predicate<String> jobComplete;
@Inject
public CreateSecurityGroupIfNeeded(CloudStackClient client,
Predicate<String> jobComplete,
Supplier<LoadingCache<String, Zone>> zoneIdToZone) {
this.client = checkNotNull(client, "client");
this.jobComplete = checkNotNull(jobComplete, "jobComplete");
this.zoneIdToZone = zoneIdToZone;
}
@Override
public SecurityGroup apply(ZoneSecurityGroupNamePortsCidrs input) {
checkNotNull(input, "input");
String zoneId = input.getZone();
Zone zone = zoneIdToZone.get().getUnchecked(zoneId);
checkArgument(supportsSecurityGroups().apply(zone),
"Security groups are required, but the zone %s does not support security groups", zoneId);
logger.debug(">> creating securityGroup %s", input);
try {
SecurityGroup securityGroup = client.getSecurityGroupClient().createSecurityGroup(input.getName());
logger.debug("<< created securityGroup(%s)", securityGroup);
ImmutableSet<String> cidrs;
if (input.getCidrs().size() > 0) {
cidrs = ImmutableSet.copyOf(input.getCidrs());
} else {
cidrs = ImmutableSet.of("0.0.0.0/0");
}
for (int port : input.getPorts()) {
authorizeGroupToItselfAndToTCPPortAndCidr(client, securityGroup, port, cidrs);
}
return securityGroup;
} catch (IllegalStateException e) {
logger.trace("<< trying to find securityGroup(%s): %s", input, e.getMessage());
SecurityGroup group = client.getSecurityGroupClient().getSecurityGroupByName(input.getName());
logger.debug("<< reused securityGroup(%s)", group.getId());
return group;
}
}
private void authorizeGroupToItselfAndToTCPPortAndCidr(CloudStackClient client,
SecurityGroup securityGroup,
int port,
Set<String> cidrs) {
for (String cidr : cidrs) {
logger.debug(">> authorizing securityGroup(%s) permission to %s on port %d", securityGroup, cidr, port);
if (!portInRangeForCidr(port, cidr).apply(securityGroup)) {
jobComplete.apply(client.getSecurityGroupClient().authorizeIngressPortsToCIDRs(securityGroup.getId(),
"TCP",
port,
port,
ImmutableSet.of(cidr)));
logger.debug("<< authorized securityGroup(%s) permission to %s on port %d", securityGroup, cidr, port);
}
}
}
}

View File

@ -0,0 +1,161 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.predicates;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.alwaysTrue;
import org.jclouds.cloudstack.domain.IngressRule;
import org.jclouds.cloudstack.domain.SecurityGroup;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
/**
*
* @author Andrew Bayer
*/
public class SecurityGroupPredicates {
/**
*
* @return true, if the security group contains an ingress rule with the given port in the port range
*/
public static Predicate<SecurityGroup> portInRange(final int port) {
return new Predicate<SecurityGroup>() {
@Override
public boolean apply(SecurityGroup group) {
return Iterables.any(group.getIngressRules(), new Predicate<IngressRule>() {
@Override
public boolean apply(IngressRule rule) {
return rule.getStartPort() <= port && rule.getEndPort() >= port;
}
});
}
@Override
public String toString() {
return "portInRange(" + port + ")";
}
};
}
/**
*
* @return true, if the security group contains an ingress rule with the given cidr
*/
public static Predicate<SecurityGroup> hasCidr(final String cidr) {
return new Predicate<SecurityGroup>() {
@Override
public boolean apply(SecurityGroup group) {
return Iterables.any(group.getIngressRules(), new Predicate<IngressRule>() {
@Override
public boolean apply(IngressRule rule) {
return rule.getCIDR() != null
&& rule.getCIDR().equals(cidr);
}
});
}
@Override
public String toString() {
return "hasCidr(" + cidr + ")";
}
};
}
/**
*
* @return true, if the security group contains an ingress rule with the given cidr and the given port in range
*/
public static Predicate<SecurityGroup> portInRangeForCidr(final int port, final String cidr) {
return new Predicate<SecurityGroup>() {
@Override
public boolean apply(SecurityGroup group) {
return Iterables.any(group.getIngressRules(), new Predicate<IngressRule>() {
@Override
public boolean apply(IngressRule rule) {
return rule.getCIDR() != null
&& rule.getCIDR().equals(cidr)
&& rule.getStartPort() <= port
&& rule.getEndPort() >= port;
}
});
}
@Override
public String toString() {
return "portInRangeForCidr(" + port + ", " + cidr + ")";
}
};
}
/**
*
* @return always returns true.
*/
public static Predicate<SecurityGroup> any() {
return alwaysTrue();
}
/**
* matches name of the given security group
*
* @param name
* @return predicate that matches name
*/
public static Predicate<SecurityGroup> nameEquals(final String name) {
checkNotNull(name, "name must be defined");
return new Predicate<SecurityGroup>() {
@Override
public boolean apply(SecurityGroup ext) {
return name.equals(ext.getName());
}
@Override
public String toString() {
return "nameEquals(" + name + ")";
}
};
}
/**
* matches name of the given security group
*
* @param name
* @return predicate that matches name
*/
public static Predicate<SecurityGroup> nameMatches(final Predicate<String> name) {
checkNotNull(name, "name must be defined");
return new Predicate<SecurityGroup>() {
@Override
public boolean apply(SecurityGroup ext) {
return name.apply(ext.getName());
}
@Override
public String toString() {
return "nameMatches(" + name + ")";
}
};
}
}

View File

@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.predicates;
import static com.google.common.base.Preconditions.checkNotNull;
import org.jclouds.cloudstack.domain.SshKeyPair;
import com.google.common.base.Predicate;
/**
* Predicates handy when working with SshKeyPairs
*
* @author Adam Lowe
* @author Andrew Bayer
*/
public class SshKeyPairPredicates {
/**
* matches name of the given key pair
*
* @param name
* @return predicate that matches name
*/
public static Predicate<SshKeyPair> nameMatches(final Predicate<String> name) {
checkNotNull(name, "name must be defined");
return new Predicate<SshKeyPair>() {
@Override
public boolean apply(SshKeyPair ext) {
return name.apply(ext.getName());
}
@Override
public String toString() {
return "nameMatches(" + name + ")";
}
};
}
/**
* matches name of the given keypair starts with the specified prefix
*
* @param name the prefix you are looking for
* @return the predicate
*/
public static Predicate<SshKeyPair> nameEquals(final String name) {
checkNotNull(name, "name must be defined");
return new Predicate<SshKeyPair>() {
@Override
public boolean apply(SshKeyPair ext) {
return ext.getName() != null && ext.getName().equals(name);
}
@Override
public String toString() {
return "nameEquals(" + name + ")";
}
};
}
}

View File

@ -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;
@ -63,6 +65,24 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
.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();
HttpRequest listCapabilitiesNotListAll = HttpRequest.builder().method("GET")
.endpoint("http://localhost:8080/client/api")
.addQueryParam("response", "json")
@ -114,7 +134,7 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
assertEquals(server.getCredentials(), LoginCredentials.builder().password("dD7jwajkh").build());
}
public void testCreateNodeWithGroupEncodedIntoNameWithKeyPair() {
public void testCreateNodeWithGroupEncodedIntoNameWithKeyPair() throws IOException {
HttpRequest deployVM = HttpRequest.builder().method("GET")
.endpoint("http://localhost:8080/client/api")
.addQueryParam("response", "json")
@ -146,8 +166,160 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
Injector forKeyPair = requestsSendResponses(requestResponseMap);
String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test"));
Template template = forKeyPair.getInstance(TemplateBuilder.class).osFamily(OsFamily.CENTOS).build();
template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair").setupStaticNat(false);
template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair")
.setupStaticNat(false)
.overrideLoginPrivateKey(privKey);
CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
template);
assertNotNull(server);
assertEquals(server.getCredentials().getPrivateKey(), privKey);
}
public void testCreateNodeWithGroupEncodedIntoNameWithGenerateKeyPair() throws IOException {
HttpRequest deployVM = HttpRequest.builder().method("GET")
.endpoint("http://localhost:8080/client/api")
.addQueryParam("response", "json")
.addQueryParam("command", "deployVirtualMachine")
.addQueryParam("zoneid", "1")
.addQueryParam("serviceofferingid", "1")
.addQueryParam("templateid", "4")
.addQueryParam("displayname", "test-e92")
.addQueryParam("name", "test-e92")
.addQueryParam("networkids", "204")
.addQueryParam("keypair", "jclouds-test")
.addQueryParam("apiKey", "APIKEY")
.addQueryParam("signature", "4M9C8IjohDDKFMAXQSX3mjXpYvM%3D")
.addHeader("Accept", "application/json")
.build();
Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(listTemplates, listTemplatesResponse)
.put(listOsTypes, listOsTypesResponse)
.put(listOsCategories, listOsCategoriesResponse)
.put(listZones, listZonesResponse)
.put(listServiceOfferings, listServiceOfferingsResponse)
.put(listAccounts, listAccountsResponse)
.put(listNetworks, listNetworksResponse)
.put(getZone, getZoneResponse)
.put(deployVM, deployVMResponse)
.put(createSSHKeyPair, createSSHKeyPairResponse)
.put(queryAsyncJobResult, queryAsyncJobResultResponse)
.build();
Injector forKeyPair = requestsSendResponses(requestResponseMap);
Template template = forKeyPair.getInstance(TemplateBuilder.class).osFamily(OsFamily.CENTOS).build();
template.getOptions().as(CloudStackTemplateOptions.class).generateKeyPair(true)
.setupStaticNat(false);
CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
template);
assertNotNull(server);
assertNotNull(server.getCredentials().getPrivateKey());
}
public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairDefaultSecurityGroup() throws IOException {
HttpRequest deployVM = HttpRequest.builder().method("GET")
.endpoint("http://localhost:8080/client/api")
.addQueryParam("response", "json")
.addQueryParam("command", "deployVirtualMachine")
.addQueryParam("zoneid", "2")
.addQueryParam("serviceofferingid", "1")
.addQueryParam("templateid", "241")
.addQueryParam("displayname", "test-e92")
.addQueryParam("name", "test-e92")
.addQueryParam("networkids", "204")
.addQueryParam("keypair", "mykeypair")
.addQueryParam("apiKey", "APIKEY")
.addQueryParam("signature", "Ar2B/ZVxMO2078cP0XliWWR4cQ0%3D")
.addHeader("Accept", "application/json")
.build();
Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(listTemplates, listTemplatesResponse)
.put(listOsTypes, listOsTypesResponse)
.put(listOsCategories, listOsCategoriesResponse)
.put(listZones, listZonesResponse)
.put(listServiceOfferings, listServiceOfferingsResponse)
.put(listAccounts, listAccountsResponse)
.put(listNetworks, listNetworksWithSecurityGroupsResponse)
.put(getZoneWithSecurityGroups, getZoneWithSecurityGroupsResponse)
.put(deployVM, deployVMResponse)
.put(queryAsyncJobResult, queryAsyncJobResultResponse)
.build();
Injector forKeyPair = requestsSendResponses(requestResponseMap);
String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test"));
Template template = forKeyPair.getInstance(TemplateBuilder.class)
.osFamily(OsFamily.CENTOS)
.locationId("2")
.build();
template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair")
.setupStaticNat(false)
.overrideLoginPrivateKey(privKey);
CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
template);
assertNotNull(server);
assertEquals(server.getCredentials().getPrivateKey(), privKey);
}
public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairGenerateSecurityGroup() throws IOException {
HttpRequest deployVM = HttpRequest.builder().method("GET")
.endpoint("http://localhost:8080/client/api")
.addQueryParam("response", "json")
.addQueryParam("command", "deployVirtualMachine")
.addQueryParam("zoneid", "2")
.addQueryParam("serviceofferingid", "1")
.addQueryParam("templateid", "241")
.addQueryParam("displayname", "test-e92")
.addQueryParam("name", "test-e92")
.addQueryParam("networkids", "204")
.addQueryParam("keypair", "mykeypair")
.addQueryParam("securitygroupids", "30")
.addQueryParam("apiKey", "APIKEY")
.addQueryParam("signature", "yNAiMYw3RstNj979udttALOHxfU%3D")
.addHeader("Accept", "application/json")
.build();
Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(listTemplates, listTemplatesResponse)
.put(listOsTypes, listOsTypesResponse)
.put(listOsCategories, listOsCategoriesResponse)
.put(listZones, listZonesResponse)
.put(listServiceOfferings, listServiceOfferingsResponse)
.put(listAccounts, listAccountsResponse)
.put(listNetworks, listNetworksWithSecurityGroupsResponse)
.put(getZoneWithSecurityGroups, getZoneWithSecurityGroupsResponse)
.put(deployVM, deployVMResponse)
.put(queryAsyncJobResult, queryAsyncJobResultSecurityGroupResponse)
.put(queryAsyncJobResultAuthorizeIngress, queryAsyncJobResultAuthorizeIngressResponse)
.put(getSecurityGroup, getSecurityGroupResponse)
.put(createSecurityGroup, createSecurityGroupResponse)
.put(authorizeIngress, authorizeIngressResponse)
.build();
Injector forKeyPair = requestsSendResponses(requestResponseMap);
String privKey = Strings2.toStringAndClose(getClass().getResourceAsStream("/test"));
Template template = forKeyPair.getInstance(TemplateBuilder.class)
.osFamily(OsFamily.CENTOS)
.locationId("2")
.build();
template.getOptions().as(CloudStackTemplateOptions.class).keyPair("mykeypair")
.setupStaticNat(false)
.generateSecurityGroup(true)
.overrideLoginPrivateKey(privKey);
CloudStackComputeServiceAdapter adapter = forKeyPair.getInstance(CloudStackComputeServiceAdapter.class);
CloudStackContext context = forKeyPair.getInstance(CloudStackContext.class);
@ -155,14 +327,10 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
template);
assertNotNull(server);
assertEquals(server.getCredentials(),
LoginCredentials.fromCredentials(context.utils().getCredentialStore().get("keypair#" +
template.getOptions().
as(CloudStackTemplateOptions.class).
getKeyPair())));
assertEquals(server.getCredentials().getPrivateKey(), privKey);
}
public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairAssignedToAccountAndDomain() {
public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairAssignedToAccountAndDomain() throws IOException {
HttpRequest deployVM = HttpRequest.builder().method("GET")
.endpoint("http://localhost:8080/client/api")
.addQueryParam("response", "json")
@ -196,8 +364,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);
CloudStackContext context = forKeyPair.getInstance(CloudStackContext.class);
@ -205,11 +378,7 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom
NodeAndInitialCredentials<VirtualMachine> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92",
template);
assertNotNull(server);
assertEquals(server.getCredentials(),
LoginCredentials.fromCredentials(context.utils().getCredentialStore().get("keypair#" +
template.getOptions().
as(CloudStackTemplateOptions.class).
getKeyPair())));
assertEquals(server.getCredentials().getPrivateKey(), privKey);
}
@Override

View File

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

View File

@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.compute.loaders;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.testng.Assert.assertEquals;
import java.net.UnknownHostException;
import org.jclouds.cloudstack.CloudStackClient;
import org.jclouds.cloudstack.domain.SshKeyPair;
import org.jclouds.cloudstack.features.SSHKeyPairClient;
import org.testng.annotations.Test;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.TypeLiteral;
/**
* @author Adam Lowe
* @author Andrew Bayer
*/
@Test(groups = "unit", testName = "CreateUniqueKeyPairTest")
public class CreateUniqueKeyPairTest {
@Test
public void testLoad() throws UnknownHostException {
final CloudStackClient client = createMock(CloudStackClient.class);
SSHKeyPairClient keyClient = createMock(SSHKeyPairClient.class);
SshKeyPair pair = createMock(SshKeyPair.class);
expect(client.getSSHKeyPairClient()).andReturn(keyClient);
expect(keyClient.createSSHKeyPair("group-1")).andReturn(pair);
replay(client, keyClient);
CreateUniqueKeyPair parser = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Supplier<String>>() {
}).toInstance(Suppliers.ofInstance("1"));
bind(CloudStackClient.class).toInstance(client);
}
}).getInstance(CreateUniqueKeyPair.class);
assertEquals(parser.load("group-1"), pair);
verify(client, keyClient);
}
}

View File

@ -0,0 +1,261 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.compute.loaders;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.util.Predicates2.retry;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.testng.Assert.assertEquals;
import java.net.UnknownHostException;
import javax.inject.Singleton;
import org.jclouds.cloudstack.CloudStackClient;
import org.jclouds.cloudstack.domain.IngressRule;
import org.jclouds.cloudstack.domain.SecurityGroup;
import org.jclouds.cloudstack.domain.Zone;
import org.jclouds.cloudstack.domain.ZoneAndName;
import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
import org.jclouds.cloudstack.features.AsyncJobClient;
import org.jclouds.cloudstack.features.SecurityGroupClient;
import org.jclouds.cloudstack.features.ZoneClient;
import org.jclouds.cloudstack.functions.CreateSecurityGroupIfNeeded;
import org.jclouds.cloudstack.functions.ZoneIdToZone;
import org.jclouds.cloudstack.predicates.JobComplete;
import org.jclouds.cloudstack.suppliers.ZoneIdToZoneSupplier;
import org.testng.annotations.Test;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
/**
* @author Adam Lowe
* @author Andrew Bayer
*/
@Test(groups = "unit", testName = "FindSecurityGroupOrCreateTest")
public class FindSecurityGroupOrCreateTest {
@Test
public void testLoad() throws UnknownHostException {
final CloudStackClient client = createMock(CloudStackClient.class);
SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
ZoneClient zoneClient = createMock(ZoneClient.class);
AsyncJobClient jobClient = createMock(AsyncJobClient.class);
SecurityGroup group = createMock(SecurityGroup.class);
Zone zone = createMock(Zone.class);
expect(group.getIngressRules()).andReturn(ImmutableSet.<IngressRule> of());
expect(group.getId()).andReturn("sec-1234").anyTimes();
expect(zone.isSecurityGroupsEnabled()).andReturn(true);
expect(client.getSecurityGroupClient()).andReturn(secClient)
.anyTimes();
expect(client.getZoneClient()).andReturn(zoneClient);
expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
expect(zoneClient.getZone("zone-1")).andReturn(zone);
expect(secClient.getSecurityGroupByName("group-1")).andReturn(null);
expect(secClient.createSecurityGroup("group-1")).andReturn(group);
expect(secClient.authorizeIngressPortsToCIDRs("sec-1234",
"TCP",
22,
22,
ImmutableSet.of("0.0.0.0/0"))).andReturn("job-1234");
replay(client, secClient, zoneClient, zone, group);
ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
.zone("zone-1")
.name("group-1")
.ports(ImmutableSet.of(22))
.cidrs(ImmutableSet.<String> of()).build();
FindSecurityGroupOrCreate parser = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Supplier<String>>() {
}).toInstance(Suppliers.ofInstance("1"));
bind(CloudStackClient.class).toInstance(client);
bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
to(ZoneIdToZone.class);
bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
to(ZoneIdToZoneSupplier.class);
bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
bind(new TypeLiteral<Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup>>() {
}).to(CreateSecurityGroupIfNeeded.class);
bind(new TypeLiteral<CacheLoader<ZoneAndName, SecurityGroup>>() {
}).to(FindSecurityGroupOrCreate.class);
}
@Provides
@Singleton
protected Predicate<String> jobComplete(JobComplete jobComplete) {
return retry(jobComplete, 1200, 1, 5, SECONDS);
}
}).getInstance(FindSecurityGroupOrCreate.class);
assertEquals(parser.load(input), group);
verify(client, secClient, zoneClient, zone, group);
}
@Test
public void testLoadAlreadyExists() throws UnknownHostException {
final CloudStackClient client = createMock(CloudStackClient.class);
SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
ZoneClient zoneClient = createMock(ZoneClient.class);
AsyncJobClient jobClient = createMock(AsyncJobClient.class);
SecurityGroup group = createMock(SecurityGroup.class);
Zone zone = createMock(Zone.class);
expect(group.getId()).andReturn("sec-1234").anyTimes();
expect(client.getSecurityGroupClient()).andReturn(secClient)
.anyTimes();
expect(client.getZoneClient()).andReturn(zoneClient);
expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
expect(secClient.getSecurityGroupByName("group-1")).andReturn(group);
replay(client, secClient, zoneClient, zone, group);
ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
.zone("zone-1")
.name("group-1")
.ports(ImmutableSet.of(22))
.cidrs(ImmutableSet.<String> of()).build();
FindSecurityGroupOrCreate parser = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Supplier<String>>() {
}).toInstance(Suppliers.ofInstance("1"));
bind(CloudStackClient.class).toInstance(client);
bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
to(ZoneIdToZone.class);
bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
to(ZoneIdToZoneSupplier.class);
bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
bind(new TypeLiteral<Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup>>() {
}).to(CreateSecurityGroupIfNeeded.class);
bind(new TypeLiteral<CacheLoader<ZoneAndName, SecurityGroup>>() {
}).to(FindSecurityGroupOrCreate.class);
}
@Provides
@Singleton
protected Predicate<String> jobComplete(JobComplete jobComplete) {
return retry(jobComplete, 1200, 1, 5, SECONDS);
}
}).getInstance(FindSecurityGroupOrCreate.class);
assertEquals(parser.load(input), group);
verify(client, secClient, zoneClient, zone, group);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testLoadZoneNoSecurityGroups() throws UnknownHostException {
final CloudStackClient client = createMock(CloudStackClient.class);
SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
ZoneClient zoneClient = createMock(ZoneClient.class);
AsyncJobClient jobClient = createMock(AsyncJobClient.class);
SecurityGroup group = createMock(SecurityGroup.class);
Zone zone = createMock(Zone.class);
expect(zone.isSecurityGroupsEnabled()).andReturn(false);
expect(client.getSecurityGroupClient()).andReturn(secClient)
.anyTimes();
expect(client.getZoneClient()).andReturn(zoneClient);
expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
expect(zoneClient.getZone("zone-1")).andReturn(zone);
expect(secClient.getSecurityGroupByName("group-1")).andReturn(null);
replay(client, secClient, zoneClient, zone, group);
ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
.zone("zone-1")
.name("group-1")
.ports(ImmutableSet.of(22))
.cidrs(ImmutableSet.<String> of()).build();
FindSecurityGroupOrCreate parser = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Supplier<String>>() {
}).toInstance(Suppliers.ofInstance("1"));
bind(CloudStackClient.class).toInstance(client);
bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
to(ZoneIdToZone.class);
bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
to(ZoneIdToZoneSupplier.class);
bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
bind(new TypeLiteral<Function<ZoneSecurityGroupNamePortsCidrs, SecurityGroup>>() {
}).to(CreateSecurityGroupIfNeeded.class);
bind(new TypeLiteral<CacheLoader<ZoneAndName, SecurityGroup>>() {
}).to(FindSecurityGroupOrCreate.class);
}
@Provides
@Singleton
protected Predicate<String> jobComplete(JobComplete jobComplete) {
return retry(jobComplete, 1200, 1, 5, SECONDS);
}
}).getInstance(FindSecurityGroupOrCreate.class);
assertEquals(parser.load(input), group);
verify(client, secClient, zoneClient, zone, group);
}
}

View File

@ -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");

View File

@ -104,6 +104,24 @@ public class SecurityGroupAsyncClientTest extends BaseCloudStackAsyncClientTest<
}
public void testGetSecurityGroupByName() throws SecurityException, NoSuchMethodException, IOException {
Invokable<?, ?> method = method(SecurityGroupAsyncClient.class, "getSecurityGroupByName", String.class);
GeneratedHttpRequest httpRequest = processor.createRequest(method, ImmutableList.<Object> of("some-name"));
assertRequestLineEquals(httpRequest,
"GET http://localhost:8080/client/api?response=json&command=listSecurityGroups&listAll=true&securitygroupname=some-name HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\n");
assertPayloadEquals(httpRequest, null, null, false);
assertResponseParserClassEquals(method, httpRequest,
Functions.compose(IdentityFunction.INSTANCE, IdentityFunction.INSTANCE).getClass());
assertSaxResponseParserClassEquals(method, null);
assertFallbackClassEquals(method, NullOnNotFoundOr404.class);
checkFilters(httpRequest);
}
public void testCreateSecurityGroup() throws SecurityException, NoSuchMethodException, IOException {
Invokable<?, ?> method = method(SecurityGroupAsyncClient.class, "createSecurityGroup", String.class);
GeneratedHttpRequest httpRequest = processor.createRequest(method, ImmutableList.<Object> of("goo"));

View File

@ -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;

View File

@ -0,0 +1,237 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.cloudstack.functions;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
import static org.jclouds.util.Predicates2.retry;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.testng.Assert.assertEquals;
import java.net.UnknownHostException;
import javax.inject.Singleton;
import org.jclouds.cloudstack.CloudStackClient;
import org.jclouds.cloudstack.domain.IngressRule;
import org.jclouds.cloudstack.domain.SecurityGroup;
import org.jclouds.cloudstack.domain.Zone;
import org.jclouds.cloudstack.domain.ZoneSecurityGroupNamePortsCidrs;
import org.jclouds.cloudstack.features.AsyncJobClient;
import org.jclouds.cloudstack.features.SecurityGroupClient;
import org.jclouds.cloudstack.features.ZoneClient;
import org.jclouds.cloudstack.functions.ZoneIdToZone;
import org.jclouds.cloudstack.predicates.JobComplete;
import org.jclouds.cloudstack.suppliers.ZoneIdToZoneSupplier;
import org.testng.annotations.Test;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
/**
* @author Andrew Bayer
*/
@Test(groups = "unit", testName = "CreateSecurityGroupIfNeededTest")
public class CreateSecurityGroupIfNeededTest {
@Test
public void testApply() throws UnknownHostException {
final CloudStackClient client = createMock(CloudStackClient.class);
SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
ZoneClient zoneClient = createMock(ZoneClient.class);
AsyncJobClient jobClient = createMock(AsyncJobClient.class);
SecurityGroup group = createMock(SecurityGroup.class);
Zone zone = createMock(Zone.class);
expect(group.getIngressRules()).andReturn(ImmutableSet.<IngressRule> of());
expect(group.getId()).andReturn("sec-1234").anyTimes();
expect(zone.isSecurityGroupsEnabled()).andReturn(true);
expect(client.getSecurityGroupClient()).andReturn(secClient)
.anyTimes();
expect(client.getZoneClient()).andReturn(zoneClient);
expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
expect(zoneClient.getZone("zone-abc1")).andReturn(zone);
expect(secClient.createSecurityGroup("group-1")).andReturn(group);
expect(secClient.authorizeIngressPortsToCIDRs("sec-1234",
"TCP",
22,
22,
ImmutableSet.of("0.0.0.0/0"))).andReturn("job-1234");
replay(client, secClient, zoneClient, zone, group);
ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
.zone("zone-abc1")
.name("group-1")
.ports(ImmutableSet.of(22))
.cidrs(ImmutableSet.<String> of()).build();
CreateSecurityGroupIfNeeded parser = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Supplier<String>>() {
}).toInstance(Suppliers.ofInstance("1"));
bind(CloudStackClient.class).toInstance(client);
bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
to(ZoneIdToZone.class);
bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
to(ZoneIdToZoneSupplier.class);
bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
}
@Provides
@Singleton
protected Predicate<String> jobComplete(JobComplete jobComplete) {
return retry(jobComplete, 1200, 1, 5, SECONDS);
}
}).getInstance(CreateSecurityGroupIfNeeded.class);
assertEquals(parser.apply(input), group);
verify(client, secClient, zoneClient, zone, group);
}
@Test
public void testApplyGroupAlreadyExists() throws UnknownHostException {
final CloudStackClient client = createMock(CloudStackClient.class);
SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
ZoneClient zoneClient = createMock(ZoneClient.class);
AsyncJobClient jobClient = createMock(AsyncJobClient.class);
SecurityGroup group = createMock(SecurityGroup.class);
Zone zone = createMock(Zone.class);
expect(group.getId()).andReturn("sec-1234").anyTimes();
expect(zone.isSecurityGroupsEnabled()).andReturn(true);
expect(client.getSecurityGroupClient()).andReturn(secClient)
.anyTimes();
expect(client.getZoneClient()).andReturn(zoneClient);
expect(client.getAsyncJobClient()).andReturn(jobClient).anyTimes();
expect(zoneClient.getZone("zone-abc2")).andReturn(zone);
expect(secClient.createSecurityGroup("group-1")).andThrow(new IllegalStateException());
expect(secClient.getSecurityGroupByName("group-1")).andReturn(group);
replay(client, secClient, zoneClient, zone, group);
ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
.zone("zone-abc2")
.name("group-1")
.ports(ImmutableSet.of(22))
.cidrs(ImmutableSet.<String> of()).build();
CreateSecurityGroupIfNeeded parser = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Supplier<String>>() {
}).toInstance(Suppliers.ofInstance("1"));
bind(CloudStackClient.class).toInstance(client);
bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
to(ZoneIdToZone.class);
bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
to(ZoneIdToZoneSupplier.class);
bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
}
@Provides
@Singleton
protected Predicate<String> jobComplete(JobComplete jobComplete) {
return retry(jobComplete, 1200, 1, 5, SECONDS);
}
}).getInstance(CreateSecurityGroupIfNeeded.class);
assertEquals(parser.apply(input), group);
verify(client, secClient, zoneClient, zone, group);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testApplyZoneNoSecurityGroups() throws UnknownHostException {
final CloudStackClient client = createMock(CloudStackClient.class);
SecurityGroupClient secClient = createMock(SecurityGroupClient.class);
ZoneClient zoneClient = createMock(ZoneClient.class);
AsyncJobClient jobClient = createMock(AsyncJobClient.class);
SecurityGroup group = createMock(SecurityGroup.class);
Zone zone = createMock(Zone.class);
expect(zone.isSecurityGroupsEnabled()).andReturn(false);
expect(client.getZoneClient()).andReturn(zoneClient);
expect(zoneClient.getZone("zone-abc3")).andReturn(zone);
replay(client, zoneClient, zone);
ZoneSecurityGroupNamePortsCidrs input = ZoneSecurityGroupNamePortsCidrs.builder()
.zone("zone-abc3")
.name("group-1")
.ports(ImmutableSet.of(22))
.cidrs(ImmutableSet.<String> of()).build();
CreateSecurityGroupIfNeeded parser = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(new TypeLiteral<Supplier<String>>() {
}).toInstance(Suppliers.ofInstance("1"));
bind(CloudStackClient.class).toInstance(client);
bind(new TypeLiteral<CacheLoader<String, Zone>>() {}).
to(ZoneIdToZone.class);
bind(new TypeLiteral<Supplier<LoadingCache<String, Zone>>>() {}).
to(ZoneIdToZoneSupplier.class);
bind(String.class).annotatedWith(Names.named(PROPERTY_SESSION_INTERVAL)).toInstance("60");
}
@Provides
@Singleton
protected Predicate<String> jobComplete(JobComplete jobComplete) {
return retry(jobComplete, 1200, 1, 5, SECONDS);
}
}).getInstance(CreateSecurityGroupIfNeeded.class);
assertEquals(parser.apply(input), group);
verify(client, zoneClient, zone);
}
}

View File

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

View File

@ -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()));
}
}

View File

@ -0,0 +1,2 @@
{ "authorizesecuritygroupingressresponse" :
{"jobid":"13330fc9-8b3e-4582-aa3e-90883c041010"} }

View File

@ -0,0 +1,3 @@
{ "listsecuritygroupsresponse" : { "securitygroup" :
{"id":30,"name":"jclouds-test","account":"adrian","domainid":1,"domain":"ROOT"}
} }

View File

@ -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"} } }

View File

@ -0,0 +1 @@
{ "listsecuritygroupsresponse" : { count: 0, "securitygroup" : [ { } ] } }

View File

@ -0,0 +1 @@
{ "listzonesresponse" : { "zone" : [ {"id":2,"name":"Chicago","networktype":"Advanced","securitygroupsenabled":true} ] } }

View File

@ -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"} ] } }

View File

@ -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"
}
}

View File

@ -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"
}
}