Java tutorial
/* * 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.azurecompute.arm.compute.strategy; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_SUBNET_ADDRESS_PREFIX; import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.DEFAULT_VNET_ADDRESS_SPACE_PREFIX; import static org.jclouds.azurecompute.arm.domain.IdReference.extractName; import static org.jclouds.azurecompute.arm.domain.IdReference.extractResourceGroup; import static org.jclouds.azurecompute.arm.domain.Subnet.extractVirtualNetwork; import java.util.Arrays; import java.util.Map; import java.util.Set; import javax.annotation.Resource; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.jclouds.Constants; import org.jclouds.azurecompute.arm.AzureComputeApi; import org.jclouds.azurecompute.arm.compute.config.AzurePredicatesModule.NetworkAvailablePredicateFactory; import org.jclouds.azurecompute.arm.compute.domain.ResourceGroupAndName; import org.jclouds.azurecompute.arm.compute.domain.ResourceGroupAndNameAndIngressRules; import org.jclouds.azurecompute.arm.compute.functions.TemplateToAvailabilitySet; import org.jclouds.azurecompute.arm.compute.options.AzureTemplateOptions; import org.jclouds.azurecompute.arm.compute.options.IpOptions; import org.jclouds.azurecompute.arm.domain.AddressSpace; import org.jclouds.azurecompute.arm.domain.AvailabilitySet; import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup; import org.jclouds.azurecompute.arm.domain.PublicIPAddress; import org.jclouds.azurecompute.arm.domain.ResourceGroup; import org.jclouds.azurecompute.arm.domain.Subnet; import org.jclouds.azurecompute.arm.domain.Subnet.SubnetProperties; import org.jclouds.azurecompute.arm.domain.VirtualNetwork.VirtualNetworkProperties; 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.options.TemplateOptions; 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.compute.strategy.impl.CreateNodesWithGroupEncodedIntoNameThenAddToSet; import org.jclouds.domain.Location; import org.jclouds.logging.Logger; import org.jclouds.util.PasswordGenerator; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; @Singleton public class CreateResourcesThenCreateNodes extends CreateNodesWithGroupEncodedIntoNameThenAddToSet { @Resource @Named(ComputeServiceConstants.COMPUTE_LOGGER) protected Logger logger = Logger.NULL; private final AzureComputeApi api; private final LoadingCache<ResourceGroupAndNameAndIngressRules, String> securityGroupMap; private final String defaultVnetAddressPrefix; private final String defaultSubnetAddressPrefix; private final TemplateToAvailabilitySet templateToAvailabilitySet; private final PasswordGenerator.Config passwordGenerator; private final NetworkAvailablePredicateFactory networkAvailable; @Inject protected CreateResourcesThenCreateNodes(CreateNodeWithGroupEncodedIntoName addNodeWithGroupStrategy, ListNodesStrategy listNodesStrategy, GroupNamingConvention.Factory namingConvention, @Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap.Factory customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory, AzureComputeApi api, @Named(DEFAULT_VNET_ADDRESS_SPACE_PREFIX) String defaultVnetAddressPrefix, @Named(DEFAULT_SUBNET_ADDRESS_PREFIX) String defaultSubnetAddressPrefix, LoadingCache<ResourceGroupAndNameAndIngressRules, String> securityGroupMap, TemplateToAvailabilitySet templateToAvailabilitySet, PasswordGenerator.Config passwordGenerator, NetworkAvailablePredicateFactory networkAvailable) { super(addNodeWithGroupStrategy, listNodesStrategy, namingConvention, userExecutor, customizeNodeAndAddToGoodMapOrPutExceptionIntoBadMapFactory); this.api = api; this.securityGroupMap = securityGroupMap; this.defaultVnetAddressPrefix = defaultVnetAddressPrefix; this.defaultSubnetAddressPrefix = defaultSubnetAddressPrefix; this.templateToAvailabilitySet = templateToAvailabilitySet; this.passwordGenerator = passwordGenerator; this.networkAvailable = networkAvailable; } @Override public Map<?, ListenableFuture<Void>> execute(String group, int count, Template template, Set<NodeMetadata> goodNodes, Map<NodeMetadata, Exception> badNodes, Multimap<NodeMetadata, CustomizationResponse> customizationResponses) { AzureTemplateOptions options = template.getOptions().as(AzureTemplateOptions.class); // TODO Generate a private key instead. Also no need to use AUTHENTICATE_SUDO in this case. generatePasswordIfNoneProvided(template); // If there is a script to be run on the node and public key // authentication has been configured, warn users if the private key // is not present if (hasRunScriptWithKeyAuthAndNoPrivateKey(template)) { logger.warn(">> a runScript was configured but no SSH key has been provided. " + "Authentication will delegate to the ssh-agent"); } String location = template.getLocation().getId(); createResourceGroupIfNeeded(group, location, options); normalizeNetworkOptions(options); createDefaultNetworkIfNeeded(group, location, options); configureSecurityGroupForOptions(group, template.getLocation(), options); configureAvailabilitySetForTemplate(template); return super.execute(group, count, template, goodNodes, badNodes, customizationResponses); } // Azure requires that we pass it the VM password. Need to generate one if not overridden by the user. private void generatePasswordIfNoneProvided(Template template) { TemplateOptions options = template.getOptions(); if (options.getLoginPassword() == null) { Optional<String> passwordOptional = template.getImage().getDefaultCredentials().getOptionalPassword(); options.overrideLoginPassword(passwordOptional.or(passwordGenerator.generate())); } } protected synchronized void createDefaultNetworkIfNeeded(String group, String location, AzureTemplateOptions options) { if (options.getIpOptions().isEmpty()) { String name = namingConvention.create().sharedNameForGroup(group); Subnet subnet = Subnet.builder().name(name) .properties(SubnetProperties.builder().addressPrefix(defaultSubnetAddressPrefix).build()) .build(); VirtualNetworkProperties properties = VirtualNetworkProperties.builder() .addressSpace(AddressSpace.create(Arrays.asList(defaultVnetAddressPrefix))) .subnets(Arrays.asList(subnet)).build(); logger.debug(">> network options have not been configured. Creating network %s(%s) and subnet %s(%s)", name, defaultVnetAddressPrefix, name, defaultSubnetAddressPrefix); api.getVirtualNetworkApi(options.getResourceGroup()).createOrUpdate(name, location, null, properties); checkState(networkAvailable.create(options.getResourceGroup()).apply(name), "Network/Subnet was not created in the configured timeout"); Subnet createdSubnet = api.getSubnetApi(options.getResourceGroup(), name).get(name); options.ipOptions(IpOptions.builder().subnet(createdSubnet.id()).allocateNewPublicIp(true).build()); } } private static boolean hasRunScriptWithKeyAuthAndNoPrivateKey(Template template) { return template.getOptions().getRunScript() != null && template.getOptions().getPublicKey() != null && !template.getOptions().hasLoginPrivateKeyOption(); } private void configureSecurityGroupForOptions(String group, Location location, AzureTemplateOptions options) { checkArgument(options.getGroups().size() <= 1, "Only one security group can be configured for each network interface"); if (!options.getGroups().isEmpty()) { ResourceGroupAndName securityGroupId = ResourceGroupAndName .fromSlashEncoded(getOnlyElement(options.getGroups())); NetworkSecurityGroup securityGroup = api.getNetworkSecurityGroupApi(securityGroupId.resourceGroup()) .get(securityGroupId.name()); checkArgument(securityGroup != null, "Security group %s was not found", securityGroupId.slashEncode()); options.securityGroups(securityGroup.id()); } else if (options.getInboundPorts().length > 0) { String name = namingConvention.create().sharedNameForGroup(group); ResourceGroupAndNameAndIngressRules regionAndIdAndIngressRules = ResourceGroupAndNameAndIngressRules .create(options.getResourceGroup(), location.getId(), name, options.getInboundPorts()); // this will create if not yet exists. String securityGroupId = securityGroupMap.getUnchecked(regionAndIdAndIngressRules); options.securityGroups(securityGroupId); } } private void configureAvailabilitySetForTemplate(Template template) { AvailabilitySet availabilitySet = templateToAvailabilitySet.apply(template); if (availabilitySet != null) { logger.debug(">> configuring nodes in availability set [%s]", availabilitySet.name()); template.getOptions().as(AzureTemplateOptions.class).availabilitySet(availabilitySet); } } private void createResourceGroupIfNeeded(String group, String location, AzureTemplateOptions options) { if (options.getResourceGroup() == null) { options.resourceGroup(group); } logger.debug(">> using resource group [%s]", options.getResourceGroup()); ResourceGroup rg = api.getResourceGroupApi().get(options.getResourceGroup()); if (rg == null) { logger.debug(">> resource group [%s] does not exist. Creating!", options.getResourceGroup()); api.getResourceGroupApi().create(options.getResourceGroup(), location, ImmutableMap.of("description", "jclouds default resource group")); } } @VisibleForTesting void normalizeNetworkOptions(AzureTemplateOptions options) { if (!options.getNetworks().isEmpty() && !options.getIpOptions().isEmpty()) { throw new IllegalArgumentException("The options.networks and options.ipOptions are exclusive"); } if (!options.getNetworks().isEmpty()) { // The portable interface allows to configure network IDs (subnet IDs), // but we don't know the type of the IP configurations to be applied // when attaching nodes to those networks. We'll assume private IPs // with Dynamic allocation and new public ip address allocated. ImmutableList.Builder<IpOptions> ipOptions = ImmutableList.builder(); for (String subnetId : options.getNetworks()) { ipOptions.add(IpOptions.builder().subnet(subnetId).allocateNewPublicIp(true).build()); } options.ipOptions(ipOptions.build()); } if (!options.getIpOptions().isEmpty()) { // Eagerly validate that all configured subnets exist. for (IpOptions ipConfig : options.getIpOptions()) { if (ipConfig.allocateNewPublicIp() && ipConfig.publicIpId() != null) { throw new IllegalArgumentException("The allocateNewPublicIps and publicIpId are exclusive"); } String resourceGroup = extractResourceGroup(ipConfig.subnet()); String networkName = extractVirtualNetwork(ipConfig.subnet()); String subnetName = extractName(ipConfig.subnet()); Subnet subnet = api.getSubnetApi(resourceGroup, networkName).get(subnetName); checkState(subnet != null, "Configured subnet %s does not exist", ipConfig.subnet()); if (ipConfig.publicIpId() != null) { PublicIPAddress publicIp = api .getPublicIPAddressApi(extractResourceGroup(ipConfig.publicIpId())) .get(extractName(ipConfig.publicIpId())); checkState(publicIp != null, "Configured public ip %s does not exist", ipConfig.publicIpId()); } } } } }