blob: 823bd76aedddefc8a87aa0a2a987b92bdeeaeb61 [file] [log] [blame]
/**
* 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());
}
}