org.jclouds.azurecompute.arm.compute.strategy.CreateResourcesThenCreateNodes.java Source code

Java tutorial

Introduction

Here is the source code for org.jclouds.azurecompute.arm.compute.strategy.CreateResourcesThenCreateNodes.java

Source

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