| /* |
| * 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.googlecomputeengine.compute.strategy; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.google.common.collect.ImmutableList.of; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import javax.annotation.Resource; |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| |
| import org.jclouds.Constants; |
| import org.jclouds.compute.config.CustomizationResponse; |
| import org.jclouds.compute.domain.NodeMetadata; |
| import org.jclouds.compute.domain.Template; |
| import org.jclouds.compute.functions.GroupNamingConvention; |
| import org.jclouds.compute.reference.ComputeServiceConstants; |
| import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName; |
| import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap; |
| import org.jclouds.compute.strategy.ListNodesStrategy; |
| import org.jclouds.googlecomputeengine.GoogleComputeEngineApi; |
| import org.jclouds.googlecomputeengine.compute.functions.FirewallTagNamingConvention; |
| import org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions; |
| import org.jclouds.googlecomputeengine.domain.Firewall; |
| import org.jclouds.googlecomputeengine.domain.Firewall.Rule; |
| import org.jclouds.googlecomputeengine.domain.Network; |
| import org.jclouds.googlecomputeengine.domain.Operation; |
| import org.jclouds.googlecomputeengine.features.FirewallApi; |
| import org.jclouds.googlecomputeengine.options.FirewallOptions; |
| import org.jclouds.logging.Logger; |
| import org.jclouds.ssh.SshKeyPairGenerator; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.Sets; |
| import com.google.common.util.concurrent.Atomics; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.common.util.concurrent.ListeningExecutorService; |
| |
| public final class CreateNodesWithGroupEncodedIntoNameThenAddToSet extends |
| org.jclouds.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet { |
| |
| public static final String EXTERIOR_RANGE = "0.0.0.0/0"; |
| public static final String DEFAULT_INTERNAL_NETWORK_RANGE = "10.0.0.0/8"; |
| |
| public static final String DEFAULT_NETWORK_NAME = "default"; |
| |
| private final GoogleComputeEngineApi api; |
| private final Predicate<AtomicReference<Operation>> operationDone; |
| private final FirewallTagNamingConvention.Factory firewallTagNamingConvention; |
| private final SshKeyPairGenerator keyGenerator; |
| |
| @Resource |
| @Named(ComputeServiceConstants.COMPUTE_LOGGER) |
| protected Logger logger = Logger.NULL; |
| |
| @Inject |
| CreateNodesWithGroupEncodedIntoNameThenAddToSet( |
| CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy, |
| ListNodesStrategy listNodesStrategy, |
| GroupNamingConvention.Factory namingConvention, |
| @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, |
| CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, |
| GoogleComputeEngineApi api, Predicate<AtomicReference<Operation>> operationDone, |
| FirewallTagNamingConvention.Factory firewallTagNamingConvention, SshKeyPairGenerator keyGenerator) { |
| super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor, |
| customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory); |
| this.api = api; |
| this.operationDone = operationDone; |
| this.firewallTagNamingConvention = firewallTagNamingConvention; |
| this.keyGenerator = keyGenerator; |
| } |
| |
| @Override |
| public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template, |
| Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes, |
| Multimap<NodeMetadata, CustomizationResponse> customizationResponses) { |
| |
| Template mutableTemplate = template.clone(); |
| GoogleComputeEngineTemplateOptions templateOptions = GoogleComputeEngineTemplateOptions.class |
| .cast(mutableTemplate.getOptions()); |
| assert template.getOptions().equals(templateOptions) : "options didn't clone properly"; |
| |
| // Get Network |
| Network network = getNetwork(templateOptions.getNetworks()); |
| // Setup Firewall rules |
| getOrCreateFirewalls(templateOptions, network, firewallTagNamingConvention.get(group)); |
| templateOptions.networks(ImmutableSet.of(network.selfLink().toString())); |
| templateOptions.userMetadata(ComputeServiceConstants.NODE_GROUP_KEY, group); |
| |
| // Configure the default credentials, if needed |
| if (templateOptions.autoCreateKeyPair() && Strings.isNullOrEmpty(templateOptions.getPublicKey())) { |
| logger.debug(">> creating default keypair..."); |
| Map<String, String> defaultKeys = keyGenerator.get(); |
| templateOptions.authorizePublicKey(defaultKeys.get("public")); |
| templateOptions.overrideLoginPrivateKey(defaultKeys.get("private")); |
| } |
| |
| if (templateOptions.getRunScript() != null && templateOptions.getLoginPrivateKey() == null) { |
| logger.warn(">> A runScript has been configured but no SSH key has been provided." |
| + " Authentication will delegate to the ssh-agent"); |
| } |
| |
| return super.execute(group, count, mutableTemplate, goodNodes, badNodes, customizationResponses); |
| } |
| |
| /** |
| * Try and find a network previously created by the user. |
| */ |
| private Network getNetwork(Set<String> networks) { |
| String networkName; |
| if (networks == null || networks.isEmpty()){ |
| networkName = DEFAULT_NETWORK_NAME; |
| } |
| else { |
| Iterator<String> iterator = networks.iterator(); |
| networkName = nameFromNetworkString(iterator.next()); |
| checkArgument(!iterator.hasNext(), "Error: Please specify only one network in TemplateOptions when using GCE."); |
| |
| } |
| Network network = api.networks().get(networkName); |
| checkArgument(network != null, "Error: no network with name %s was found", networkName); |
| return network; |
| } |
| |
| /** |
| * Ensures that a firewall exists for every inbound port that the instance |
| * requests. |
| * <p> |
| * For each port, there must be a firewall with a name following the |
| * {@link FirewallTagNamingConvention}, with a target tag also following the |
| * {@link FirewallTagNamingConvention}, which opens the requested port for |
| * all sources on both TCP and UDP protocols. |
| * |
| * @see org.jclouds.googlecomputeengine.features.FirewallApi#patch(String, |
| * org.jclouds.googlecomputeengine.options.FirewallOptions) |
| */ |
| private void getOrCreateFirewalls(GoogleComputeEngineTemplateOptions templateOptions, Network network, |
| FirewallTagNamingConvention naming) { |
| |
| Set<String> tags = Sets.newHashSet(templateOptions.getTags()); |
| |
| FirewallApi firewallApi = api.firewalls(); |
| |
| if (!templateOptions.getGroups().isEmpty()) { |
| for (String firewallName : templateOptions.getGroups()) { |
| Firewall firewall = firewallApi.get(firewallName); |
| validateFirewall(firewall, network); |
| if (!firewall.targetTags().isEmpty()) { |
| // Add tags coming from firewalls |
| tags.addAll(firewall.targetTags()); |
| } |
| } |
| } |
| |
| int[] inboundPorts = templateOptions.getInboundPorts(); |
| if ((inboundPorts == null) || inboundPorts.length == 0){ |
| return; |
| } |
| |
| List<String> ports = simplifyPorts(inboundPorts); |
| String name = naming.name(ports); |
| Firewall firewall = firewallApi.get(name); |
| AtomicReference<Operation> operation = null; |
| if (firewall == null) { |
| List<Rule> rules = ImmutableList.of(Rule.create("tcp", ports), Rule.create("udp", ports)); |
| FirewallOptions firewallOptions = new FirewallOptions().name(name).network(network.selfLink()) |
| .allowedRules(rules).sourceTags(templateOptions.getTags()) |
| .sourceRanges(of(DEFAULT_INTERNAL_NETWORK_RANGE, EXTERIOR_RANGE)) |
| .targetTags(ImmutableList.of(name)); |
| |
| operation = Atomics.newReference(firewallApi |
| .createInNetwork(firewallOptions.name(), network.selfLink(), firewallOptions)); |
| |
| operationDone.apply(operation); |
| checkState(operation.get().httpErrorStatusCode() == null, "Could not insert firewall, operation failed %s", |
| operation); |
| |
| // Add tags for firewalls |
| tags.add(name); |
| } |
| templateOptions.tags(tags); |
| } |
| |
| private void validateFirewall(Firewall firewall, Network network) { |
| if (firewall == null || !firewall.network().equals(network.selfLink())) { |
| throw new IllegalArgumentException(String.format("Can't find firewall %s in network %s.", firewall.name(), network)); |
| } |
| } |
| |
| // Helper function for simplifying an array of ports to a list of ranges FirewallOptions expects. |
| public static List<String> simplifyPorts(int[] ports){ |
| if ((ports == null) || (ports.length == 0)) { |
| return null; |
| } |
| ArrayList<String> output = new ArrayList<String>(); |
| Arrays.sort(ports); |
| |
| int range_start = ports[0]; |
| int range_end = ports[0]; |
| for (int i = 1; i < ports.length; i++) { |
| if ((ports[i - 1] == ports[i] - 1) || (ports[i - 1] == ports[i])){ |
| // Range continues. |
| range_end = ports[i]; |
| } |
| else { |
| // Range ends. |
| output.add(formatRange(range_start, range_end)); |
| range_start = ports[i]; |
| range_end = ports[i]; |
| } |
| } |
| // Make sure we get the last range. |
| output.add(formatRange(range_start, range_end)); |
| return output; |
| } |
| |
| // Helper function for simplifyPorts. Formats port range strings. |
| private static String formatRange(int start, int finish){ |
| if (start == finish){ |
| return Integer.toString(start); |
| } |
| else { |
| return String.format("%s-%s", Integer.toString(start), Integer.toString(finish)); |
| } |
| } |
| |
| // Helper function for getting the network name from the full URI. |
| public static String nameFromNetworkString(String networkString) { |
| return networkString.substring(networkString.lastIndexOf('/') + 1); |
| } |
| } |