| /** |
| * 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.vcloud.compute.strategy; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Predicates.not; |
| import static com.google.common.collect.Iterables.find; |
| import static com.google.common.collect.Iterables.get; |
| import static org.jclouds.Constants.PROPERTY_BUILD_VERSION; |
| import static org.jclouds.compute.util.ComputeServiceUtils.getCores; |
| import static org.jclouds.vcloud.compute.util.VCloudComputeUtils.getCredentialsFrom; |
| import static org.jclouds.vcloud.options.InstantiateVAppTemplateOptions.Builder.addNetworkConfig; |
| |
| import java.net.URI; |
| |
| import javax.annotation.Resource; |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Singleton; |
| |
| import org.jclouds.compute.ComputeServiceAdapter.NodeAndInitialCredentials; |
| import org.jclouds.compute.domain.Template; |
| import org.jclouds.compute.reference.ComputeServiceConstants; |
| import org.jclouds.javax.annotation.Nullable; |
| import org.jclouds.logging.Logger; |
| import org.jclouds.ovf.Network; |
| import org.jclouds.predicates.validators.DnsNameValidator; |
| import org.jclouds.vcloud.TaskStillRunningException; |
| import org.jclouds.vcloud.VCloudClient; |
| import org.jclouds.vcloud.compute.options.VCloudTemplateOptions; |
| import org.jclouds.vcloud.domain.GuestCustomizationSection; |
| import org.jclouds.vcloud.domain.NetworkConnection; |
| import org.jclouds.vcloud.domain.NetworkConnectionSection; |
| import org.jclouds.vcloud.domain.Task; |
| import org.jclouds.vcloud.domain.VApp; |
| import org.jclouds.vcloud.domain.VAppTemplate; |
| import org.jclouds.vcloud.domain.Vm; |
| import org.jclouds.vcloud.domain.NetworkConnectionSection.Builder; |
| import org.jclouds.vcloud.domain.network.IpAddressAllocationMode; |
| import org.jclouds.vcloud.domain.network.NetworkConfig; |
| import org.jclouds.vcloud.options.InstantiateVAppTemplateOptions; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.cache.LoadingCache; |
| import com.google.common.collect.ImmutableSet; |
| |
| /** |
| * @author Adrian Cole |
| */ |
| @Singleton |
| public class InstantiateVAppTemplateWithGroupEncodedIntoNameThenCustomizeDeployAndPowerOn { |
| @Resource |
| @Named(ComputeServiceConstants.COMPUTE_LOGGER) |
| protected Logger logger = Logger.NULL; |
| |
| protected final VCloudClient client; |
| protected final Predicate<URI> successTester; |
| protected final LoadingCache<URI, VAppTemplate> vAppTemplates; |
| protected final NetworkConfig defaultNetworkConfig; |
| protected final String buildVersion; |
| |
| @Inject |
| protected InstantiateVAppTemplateWithGroupEncodedIntoNameThenCustomizeDeployAndPowerOn(VCloudClient client, |
| Predicate<URI> successTester, LoadingCache<URI, VAppTemplate> vAppTemplates, |
| NetworkConfig defaultNetworkConfig, @Named(PROPERTY_BUILD_VERSION) String buildVersion) { |
| this.client = client; |
| this.successTester = successTester; |
| this.vAppTemplates = vAppTemplates; |
| this.defaultNetworkConfig = defaultNetworkConfig; |
| this.buildVersion = buildVersion; |
| } |
| |
| // TODO: filtering on "none" is a hack until we can filter on |
| // vAppTemplate.getNetworkConfigSection().getNetworkConfigs() where |
| // name = getChildren().NetworkConnectionSection.connection where ipallocationmode == none |
| Predicate<Network> networkWithNoIpAllocation = new Predicate<Network>() { |
| |
| @Override |
| public boolean apply(Network input) { |
| return "none".equals(input.getName()); |
| } |
| |
| }; |
| |
| /** |
| * per john ellis at bluelock, vCloud Director 1.5 is more strict than earlier versions. |
| * <p/> |
| * It appears to be 15 characters to match Windows' hostname limitation. Must be alphanumeric, at |
| * least one non-number character and hyphens and underscores are the only non-alpha character |
| * allowed. |
| */ |
| public static enum ComputerNameValidator { |
| INSTANCE; |
| |
| private DnsNameValidator validator; |
| |
| ComputerNameValidator(){ |
| this.validator = new DnsNameValidator(3, 15); |
| } |
| |
| public void validate(@Nullable String t) throws IllegalArgumentException { |
| this.validator.validate(t); |
| } |
| |
| } |
| |
| public NodeAndInitialCredentials<VApp> createNodeWithGroupEncodedIntoName(String group, String name, Template template) { |
| // no sense waiting until failures occur later |
| ComputerNameValidator.INSTANCE.validate(name); |
| |
| URI templateId = URI.create(template.getImage().getId()); |
| |
| VAppTemplate vAppTemplate = vAppTemplates.getUnchecked(templateId); |
| |
| if (vAppTemplate.getChildren().size() > 1) |
| throw new UnsupportedOperationException("we currently do not support multiple vms in a vAppTemplate " |
| + vAppTemplate); |
| |
| if (vAppTemplate.getNetworkSection().getNetworks().size() > 1) |
| throw new UnsupportedOperationException( |
| "we currently do not support multiple network connections in a vAppTemplate " + vAppTemplate); |
| |
| Network networkToConnect = get(vAppTemplate.getNetworkSection().getNetworks(), 0); |
| |
| NetworkConfig config; |
| // if we only have a disconnected network, let's add a new section for the upstream |
| // TODO: remove the disconnected entry |
| if (networkWithNoIpAllocation.apply(networkToConnect)) |
| config = defaultNetworkConfig; |
| else |
| config = defaultNetworkConfig.toBuilder().networkName(networkToConnect.getName()).build(); |
| |
| // note that in VCD 1.5, the network name after instantiation will be the same as the parent |
| InstantiateVAppTemplateOptions options = addNetworkConfig(config); |
| |
| // TODO make disk size specifiable |
| // disk((long) ((template.getHardware().getVolumes().get(0).getSize()) * |
| // 1024 * 1024l)); |
| |
| String customizationScript = VCloudTemplateOptions.class.cast(template.getOptions()).getCustomizationScript(); |
| IpAddressAllocationMode ipAllocationMode = VCloudTemplateOptions.class.cast(template.getOptions()) |
| .getIpAddressAllocationMode(); |
| |
| String description = VCloudTemplateOptions.class.cast(template.getOptions()).getDescription(); |
| if (description == null) |
| description = vAppTemplate.getName(); |
| |
| options.description(description); |
| options.deploy(false); |
| options.powerOn(false); |
| |
| URI VDC = URI.create(template.getLocation().getId()); |
| |
| logger.debug(">> instantiating vApp vDC(%s) template(%s) name(%s) options(%s) ", VDC, templateId, name, options); |
| |
| VApp vAppResponse = client.getVAppTemplateClient().createVAppInVDCByInstantiatingTemplate(name, VDC, templateId, |
| options); |
| waitForTask(vAppResponse.getTasks().get(0)); |
| logger.debug("<< instantiated VApp(%s)", vAppResponse.getName()); |
| |
| // vm data is available after instantiate completes |
| vAppResponse = client.getVAppClient().getVApp(vAppResponse.getHref()); |
| |
| // per above check, we know there is only a single VM |
| Vm vm = get(vAppResponse.getChildren(), 0); |
| |
| // note we cannot do tasks in parallel or VCD will throw "is busy" errors |
| |
| // note we must do this before any other customizations as there is a dependency on |
| // valid naming conventions before you can perform commands such as updateCPUCount |
| logger.trace(">> updating customization vm(%s) name->(%s)", vm.getName(), name); |
| waitForTask(updateVmWithNameAndCustomizationScript(vm, name, customizationScript)); |
| logger.trace("<< updated customization vm(%s)", name); |
| |
| ensureVmHasAllocationModeOrPooled(vAppResponse, ipAllocationMode); |
| |
| int cpuCount = new Double(getCores(template.getHardware())).intValue(); |
| logger.trace(">> updating cpuCount(%d) vm(%s)", cpuCount, vm.getName()); |
| waitForTask(updateCPUCountOfVm(vm, cpuCount)); |
| logger.trace("<< updated cpuCount vm(%s)", vm.getName()); |
| int memoryMB = template.getHardware().getRam(); |
| logger.trace(">> updating memoryMB(%d) vm(%s)", memoryMB, vm.getName()); |
| waitForTask(updateMemoryMBOfVm(vm, memoryMB)); |
| logger.trace("<< updated memoryMB vm(%s)", vm.getName()); |
| logger.trace(">> deploying vApp(%s)", vAppResponse.getName()); |
| waitForTask(client.getVAppClient().deployVApp(vAppResponse.getHref())); |
| logger.trace("<< deployed vApp(%s)", vAppResponse.getName()); |
| |
| // only after deploy is the password valid |
| vAppResponse = client.getVAppClient().getVApp(vAppResponse.getHref()); |
| |
| logger.trace(">> powering on vApp(%s)", vAppResponse.getName()); |
| client.getVAppClient().powerOnVApp(vAppResponse.getHref()); |
| |
| return new NodeAndInitialCredentials<VApp>(vAppResponse, vAppResponse.getHref().toASCIIString(), |
| getCredentialsFrom(vAppResponse)); |
| |
| } |
| |
| public void waitForTask(Task task) { |
| if (!successTester.apply(task.getHref())) { |
| throw new TaskStillRunningException(task); |
| } |
| } |
| |
| /** |
| * Naming constraints modifying a VM on a VApp in vCloud Director (at least v1.5) can be more |
| * strict than those in a vAppTemplate. For example, while it is possible to instantiate a |
| * vAppTemplate with a VM named (incorrectly) {@code Ubuntu_10.04}, you must change the name to a |
| * valid (alphanumeric underscore) name before you can update it. |
| */ |
| public Task updateVmWithNameAndCustomizationScript(Vm vm, String name, @Nullable String customizationScript) { |
| GuestCustomizationSection guestConfiguration = vm.getGuestCustomizationSection(); |
| guestConfiguration.setComputerName(name); |
| if (customizationScript != null) { |
| // In version 1.0.0, the api returns a script that loses newlines, so we cannot append to a |
| // customization script. |
| // TODO: parameterize whether to overwrite or append existing customization |
| if (!buildVersion.startsWith("1.0.0") && !"".endsWith(buildVersion) |
| && guestConfiguration.getCustomizationScript() != null) |
| customizationScript = guestConfiguration.getCustomizationScript() + "\n" + customizationScript; |
| |
| guestConfiguration.setCustomizationScript(customizationScript); |
| } |
| return client.getVmClient().updateGuestCustomizationOfVm(guestConfiguration, vm.getHref()); |
| } |
| |
| public void ensureVmHasAllocationModeOrPooled(VApp vApp, @Nullable IpAddressAllocationMode ipAllocationMode) { |
| Network networkToConnect = find(vApp.getNetworkSection().getNetworks(), not(networkWithNoIpAllocation)); |
| |
| Vm vm = get(vApp.getChildren(), 0); |
| |
| NetworkConnectionSection net = vm.getNetworkConnectionSection(); |
| checkArgument(net.getConnections().size() > 0, "no connections on vm %s", vm); |
| |
| NetworkConnection toConnect = findWithPoolAllocationOrFirst(net); |
| |
| if (ipAllocationMode == null) |
| ipAllocationMode = toConnect.getIpAddressAllocationMode(); |
| |
| // make sure that we are in fact allocating ips |
| if (ipAllocationMode == IpAddressAllocationMode.NONE) |
| ipAllocationMode = IpAddressAllocationMode.POOL; |
| |
| if (toConnect.isConnected() && toConnect.getIpAddressAllocationMode() == ipAllocationMode |
| && toConnect.getNetwork().equals(networkToConnect.getName())) { |
| // then we don't need to change the network settings, and can save a call |
| } else { |
| Builder builder = net.toBuilder(); |
| builder.connections(ImmutableSet.of(toConnect.toBuilder().network(networkToConnect.getName()).connected(true) |
| .ipAddressAllocationMode(ipAllocationMode).build())); |
| logger.trace(">> updating networkConnection vm(%s)", vm.getName()); |
| |
| waitForTask(client.getVmClient().updateNetworkConnectionOfVm(builder.build(), vm.getHref())); |
| logger.trace("<< updated networkConnection vm(%s)", vm.getName()); |
| |
| } |
| |
| } |
| |
| private NetworkConnection findWithPoolAllocationOrFirst(NetworkConnectionSection net) { |
| return find(net.getConnections(), new Predicate<NetworkConnection>() { |
| |
| @Override |
| public boolean apply(NetworkConnection input) { |
| return input.getIpAddressAllocationMode() == IpAddressAllocationMode.POOL; |
| } |
| |
| }, get(net.getConnections(), 0)); |
| } |
| |
| public Task updateCPUCountOfVm(Vm vm, int cpuCount) { |
| return client.getVmClient().updateCPUCountOfVm(cpuCount, vm.getHref()); |
| } |
| |
| public Task updateMemoryMBOfVm(Vm vm, int memoryInMB) { |
| return client.getVmClient().updateMemoryMBOfVm(memoryInMB, vm.getHref()); |
| } |
| } |