| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jclouds.openstack.nova.v2_0.compute.strategy; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Singleton; |
| |
| import org.jclouds.Constants; |
| import org.jclouds.compute.config.CustomizationResponse; |
| import org.jclouds.compute.domain.NodeMetadata; |
| import org.jclouds.compute.domain.SecurityGroup; |
| import org.jclouds.compute.domain.Template; |
| import org.jclouds.compute.extensions.SecurityGroupExtension; |
| import org.jclouds.compute.functions.GroupNamingConvention; |
| import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName; |
| import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap; |
| import org.jclouds.compute.strategy.ListNodesStrategy; |
| import org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet; |
| import org.jclouds.javax.annotation.Nullable; |
| import org.jclouds.openstack.nova.v2_0.NovaApi; |
| import org.jclouds.openstack.nova.v2_0.compute.functions.AllocateAndAddFloatingIpToNode; |
| import org.jclouds.openstack.nova.v2_0.compute.options.NodeAndNovaTemplateOptions; |
| import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions; |
| import org.jclouds.openstack.nova.v2_0.domain.KeyPair; |
| import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionAndName; |
| import org.jclouds.openstack.nova.v2_0.domain.regionscoped.RegionSecurityGroupNameAndPorts; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Strings; |
| import com.google.common.cache.LoadingCache; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Multimap; |
| import com.google.common.primitives.Ints; |
| import com.google.common.util.concurrent.Atomics; |
| import com.google.common.util.concurrent.FutureCallback; |
| import com.google.common.util.concurrent.Futures; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.common.util.concurrent.ListeningExecutorService; |
| |
| @Singleton |
| public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet extends |
| CreateNodesWithGroupEncodedIntoNameThenAddToSet { |
| |
| public static final String JCLOUDS_SG_PREFIX = "jclouds_sg"; |
| |
| private final AllocateAndAddFloatingIpToNode createAndAddFloatingIpToNode; |
| private final LoadingCache<RegionAndName, SecurityGroup> securityGroupCache; |
| private final NovaApi novaApi; |
| private final SecurityGroupExtension securityGroupExtension; |
| |
| @Inject |
| protected ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet( |
| CreateNodeWithGroupEncodedIntoName addNodeWithTagStrategy, |
| ListNodesStrategy listNodesStrategy, |
| GroupNamingConvention.Factory namingConvention, |
| CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, |
| @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, |
| AllocateAndAddFloatingIpToNode createAndAddFloatingIpToNode, |
| LoadingCache<RegionAndName, SecurityGroup> securityGroupCache, |
| NovaApi novaApi, |
| SecurityGroupExtension securityGroupExtension) { |
| super(addNodeWithTagStrategy, listNodesStrategy, namingConvention, userExecutor, |
| customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory); |
| this.securityGroupCache = checkNotNull(securityGroupCache, "securityGroupCache"); |
| this.createAndAddFloatingIpToNode = checkNotNull(createAndAddFloatingIpToNode, |
| "createAndAddFloatingIpToNode"); |
| this.novaApi = checkNotNull(novaApi, "novaApi"); |
| this.securityGroupExtension = securityGroupExtension; |
| } |
| |
| @Override |
| public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes, |
| Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) { |
| |
| NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(template.getOptions()); |
| final String region = template.getLocation().getId(); |
| |
| if (templateOptions.shouldAutoAssignFloatingIp()) { |
| checkArgument(novaApi.getFloatingIPApi(region).isPresent(), |
| "Floating IPs are required by options, but the extension is not available! options: %s", |
| templateOptions); |
| } |
| if (templateOptions.shouldGenerateKeyPair() || templateOptions.getKeyPairName() != null) { |
| checkArgument(novaApi.getKeyPairApi(region).isPresent(), |
| "Key Pairs are required by options, but the extension is not available! options: %s", templateOptions); |
| } |
| final List<Integer> inboundPorts = Ints.asList(templateOptions.getInboundPorts()); |
| |
| KeyPair keyPair = null; |
| if (templateOptions.shouldGenerateKeyPair()) { |
| keyPair = generateKeyPair(region, namingConvention.create().sharedNameForGroup(group)); |
| // If a private key has not been explicitly set, configure the auto-generated one |
| if (Strings.isNullOrEmpty(templateOptions.getLoginPrivateKey())) { |
| templateOptions.overrideLoginPrivateKey(keyPair.getPrivateKey()); |
| } |
| } else if (templateOptions.getKeyPairName() != null) { |
| keyPair = checkNotNull(novaApi.getKeyPairApi(region).get().get(templateOptions.getKeyPairName()), |
| "keypair %s doesn't exist", templateOptions.getKeyPairName()); |
| } |
| if (keyPair != null) { |
| templateOptions.keyPairName(keyPair.getName()); |
| } |
| |
| ImmutableList.Builder<String> tagsBuilder = ImmutableList.builder(); |
| |
| if (!templateOptions.getGroups().isEmpty()) { |
| Iterable<String> securityGroupNames = Iterables.transform(securityGroupExtension.listSecurityGroups(), new Function<org.jclouds.compute.domain.SecurityGroup, String>() { |
| @Override |
| public String apply(@Nullable org.jclouds.compute.domain.SecurityGroup input) { |
| return input.getName(); |
| } |
| }); |
| for (String securityGroupName : templateOptions.getGroups()) { |
| checkState(Iterables.contains(securityGroupNames, securityGroupName), "Cannot find security group with name " + securityGroupName + ". \nSecurity groups available are: \n" + Iterables.toString(securityGroupNames)); // { |
| } |
| |
| } else if (!inboundPorts.isEmpty()) { |
| String securityGroupName = namingConvention.create().sharedNameForGroup(group); |
| |
| // populate the security group cache with existing security groups |
| for (SecurityGroup existingSecurityGroup : securityGroupExtension.listSecurityGroupsInLocation(template.getLocation())) { |
| securityGroupCache.put(new RegionSecurityGroupNameAndPorts(region, existingSecurityGroup.getName(), inboundPorts), existingSecurityGroup); |
| } |
| |
| SecurityGroup securityGroup = securityGroupCache.getUnchecked(new RegionSecurityGroupNameAndPorts(region, securityGroupName, inboundPorts)); |
| templateOptions.securityGroups(securityGroup.getName()); |
| tagsBuilder.add(String.format("%s-%s", JCLOUDS_SG_PREFIX, securityGroup.getId())); |
| } |
| |
| templateOptions.tags(tagsBuilder.build()); |
| |
| Map<?, ListenableFuture<Void>> responses = super.execute(group, count, template, goodNodes, badNodes, |
| customizationResponses); |
| |
| // Key pairs in Openstack are only required to create the Server. They aren't used anymore so it is better |
| // to delete the auto-generated key pairs at this point where we know exactly which ones have been |
| // auto-generated by jclouds. |
| if (templateOptions.shouldGenerateKeyPair() && keyPair != null) { |
| registerAutoGeneratedKeyPairCleanupCallbacks(responses, region, keyPair.getName()); |
| } |
| return responses; |
| } |
| |
| private KeyPair generateKeyPair(String region, String prefix) { |
| logger.debug(">> creating default keypair for node..."); |
| KeyPair keyPair = novaApi.getKeyPairApi(region).get().create(namingConvention.createWithoutPrefix().uniqueNameForGroup(prefix)); |
| logger.debug(">> keypair created! %s", keyPair.getName()); |
| return keyPair; |
| } |
| |
| @Override |
| protected ListenableFuture<AtomicReference<NodeMetadata>> createNodeInGroupWithNameAndTemplate(String group, |
| final String name, Template template) { |
| |
| ListenableFuture<AtomicReference<NodeMetadata>> future = super.createNodeInGroupWithNameAndTemplate(group, name, template); |
| final NovaTemplateOptions templateOptions = NovaTemplateOptions.class.cast(template.getOptions()); |
| if (templateOptions.shouldAutoAssignFloatingIp()) { |
| |
| ListenableFuture<AtomicReference<NodeAndNovaTemplateOptions>> nodeAndNovaTemplateOptions = Futures.transform(future, |
| new Function<AtomicReference<NodeMetadata>, AtomicReference<NodeAndNovaTemplateOptions>>() { |
| |
| @Override |
| public AtomicReference<NodeAndNovaTemplateOptions> apply(AtomicReference<NodeMetadata> input) { |
| return NodeAndNovaTemplateOptions.newAtomicReference(input, Atomics.newReference(templateOptions)); |
| } |
| } |
| ); |
| return Futures.transform(nodeAndNovaTemplateOptions, createAndAddFloatingIpToNode, userExecutor); |
| } else { |
| return future; |
| } |
| } |
| |
| private void registerAutoGeneratedKeyPairCleanupCallbacks(final Map<?, ListenableFuture<Void>> responses, |
| final String region, final String generatedKeyPairName) { |
| // The Futures.allAsList fails immediately if some of the futures fail. The Futures.successfulAsList, however, |
| // returns a list containing the results or 'null' for those futures that failed. We want to wait for all them |
| // (even if they fail), so better use the latter form. |
| ListenableFuture<List<Void>> aggregatedResponses = Futures.successfulAsList(responses.values()); |
| |
| // Key pairs must be cleaned up after all futures completed (even if some failed). |
| Futures.addCallback(aggregatedResponses, new FutureCallback<List<Void>>() { |
| @Override |
| public void onSuccess(List<Void> result) { |
| cleanupAutoGeneratedKeyPair(generatedKeyPairName); |
| } |
| |
| @Override |
| public void onFailure(Throwable t) { |
| cleanupAutoGeneratedKeyPair(generatedKeyPairName); |
| } |
| |
| private void cleanupAutoGeneratedKeyPair(String keyPairName) { |
| logger.debug(">> cleaning up auto-generated key pairs..."); |
| try { |
| novaApi.getKeyPairApi(region).get().delete(keyPairName); |
| } catch (Exception ex) { |
| logger.warn(">> could not delete key pair %s: %s", keyPairName, ex.getMessage()); |
| } |
| } |
| |
| }, userExecutor); |
| } |
| |
| } |