mirror of https://github.com/apache/jclouds.git
Issue 494:vCloud director 1.0.1 fixes guest customization bug
This commit is contained in:
parent
59c0dbb6cd
commit
d494c8a9f3
|
@ -56,9 +56,9 @@ public class GuestCustomizationSectionHandler extends ParseSax.HandlerWithResult
|
||||||
|
|
||||||
public GuestCustomizationSection getResult() {
|
public GuestCustomizationSection getResult() {
|
||||||
GuestCustomizationSection system = new GuestCustomizationSection(guest.getType(), guest.getHref(), info, enabled,
|
GuestCustomizationSection system = new GuestCustomizationSection(guest.getType(), guest.getHref(), info, enabled,
|
||||||
changeSid, virtualMachineId, joinDomainEnabled, useOrgSettings, domainName, domainUserName,
|
changeSid, virtualMachineId, joinDomainEnabled, useOrgSettings, domainName, domainUserName,
|
||||||
domainUserPassword, adminPasswordEnabled, adminPasswordAuto, adminPassword, resetPasswordRequired,
|
domainUserPassword, adminPasswordEnabled, adminPasswordAuto, adminPassword, resetPasswordRequired,
|
||||||
customizationScript, computerName, edit);
|
customizationScript, computerName, edit);
|
||||||
this.guest = null;
|
this.guest = null;
|
||||||
this.info = null;
|
this.info = null;
|
||||||
this.edit = null;
|
this.edit = null;
|
||||||
|
@ -81,6 +81,7 @@ public class GuestCustomizationSectionHandler extends ParseSax.HandlerWithResult
|
||||||
|
|
||||||
public void startElement(String uri, String localName, String qName, Attributes attrs) {
|
public void startElement(String uri, String localName, String qName, Attributes attrs) {
|
||||||
Map<String, String> attributes = cleanseAttributes(attrs);
|
Map<String, String> attributes = cleanseAttributes(attrs);
|
||||||
|
this.currentText = new StringBuilder();
|
||||||
if (qName.endsWith("GuestCustomizationSection")) {
|
if (qName.endsWith("GuestCustomizationSection")) {
|
||||||
guest = newReferenceType(attributes);
|
guest = newReferenceType(attributes);
|
||||||
} else if (qName.endsWith("Link") && "edit".equals(attributes.get("rel"))) {
|
} else if (qName.endsWith("Link") && "edit".equals(attributes.get("rel"))) {
|
||||||
|
@ -119,8 +120,7 @@ public class GuestCustomizationSectionHandler extends ParseSax.HandlerWithResult
|
||||||
} else if (qName.endsWith("CustomizationScript")) {
|
} else if (qName.endsWith("CustomizationScript")) {
|
||||||
this.customizationScript = currentOrNull();
|
this.customizationScript = currentOrNull();
|
||||||
if (this.customizationScript != null)
|
if (this.customizationScript != null)
|
||||||
customizationScript = customizationScript.replace("<", "<").replace(">", ">").replace(""", "\"")
|
customizationScript = customizationScript.replace(">", ">");
|
||||||
.replace("'", "'").replace(" ", "\r\n").replace(" ", "\n").replace("&", "&");
|
|
||||||
} else if (qName.endsWith("ComputerName")) {
|
} else if (qName.endsWith("ComputerName")) {
|
||||||
this.computerName = currentOrNull();
|
this.computerName = currentOrNull();
|
||||||
} else if (qName.endsWith("Name")) {
|
} else if (qName.endsWith("Name")) {
|
||||||
|
@ -134,7 +134,7 @@ public class GuestCustomizationSectionHandler extends ParseSax.HandlerWithResult
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String currentOrNull() {
|
protected String currentOrNull() {
|
||||||
String returnVal = currentText.toString().trim();
|
String returnVal = currentText.toString();
|
||||||
return returnVal.equals("") ? null : returnVal;
|
return returnVal.equals("") ? null : returnVal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ package org.jclouds.vcloud;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.collect.Iterables.get;
|
import static com.google.common.collect.Iterables.get;
|
||||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||||
|
import static org.jclouds.crypto.CryptoStreams.base64;
|
||||||
import static org.testng.Assert.assertEquals;
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
@ -31,26 +32,31 @@ import org.jclouds.Constants;
|
||||||
import org.jclouds.compute.ComputeService;
|
import org.jclouds.compute.ComputeService;
|
||||||
import org.jclouds.compute.ComputeServiceContext;
|
import org.jclouds.compute.ComputeServiceContext;
|
||||||
import org.jclouds.compute.ComputeServiceContextFactory;
|
import org.jclouds.compute.ComputeServiceContextFactory;
|
||||||
|
import org.jclouds.compute.domain.ExecResponse;
|
||||||
import org.jclouds.compute.domain.NodeMetadata;
|
import org.jclouds.compute.domain.NodeMetadata;
|
||||||
import org.jclouds.compute.options.TemplateOptions;
|
import org.jclouds.compute.options.TemplateOptions;
|
||||||
import org.jclouds.logging.log4j.config.Log4JLoggingModule;
|
import org.jclouds.logging.log4j.config.Log4JLoggingModule;
|
||||||
import org.jclouds.net.IPSocket;
|
import org.jclouds.net.IPSocket;
|
||||||
import org.jclouds.predicates.InetSocketAddressConnect;
|
import org.jclouds.predicates.InetSocketAddressConnect;
|
||||||
import org.jclouds.predicates.RetryablePredicate;
|
import org.jclouds.predicates.RetryablePredicate;
|
||||||
|
import org.jclouds.rest.RestContextFactory;
|
||||||
import org.jclouds.ssh.SshClient;
|
import org.jclouds.ssh.SshClient;
|
||||||
import org.jclouds.ssh.SshClient.Factory;
|
import org.jclouds.ssh.SshClient.Factory;
|
||||||
import org.jclouds.ssh.jsch.config.JschSshClientModule;
|
import org.jclouds.ssh.jsch.config.JschSshClientModule;
|
||||||
import org.jclouds.vcloud.compute.options.VCloudTemplateOptions;
|
import org.jclouds.vcloud.compute.options.VCloudTemplateOptions;
|
||||||
|
import org.jclouds.vcloud.domain.Vm;
|
||||||
|
import org.testng.annotations.AfterTest;
|
||||||
import org.testng.annotations.BeforeGroups;
|
import org.testng.annotations.BeforeGroups;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This tests that we can use guest customization as an alternative to bootstrapping with ssh. There
|
* This tests that we can use guest customization as an alternative to bootstrapping with ssh. There
|
||||||
* are a few advangroupes to this, including the fact that it can work inside google appengine where
|
* are a few advantages to this, including the fact that it can work inside google appengine where
|
||||||
* network sockets (ssh:22) are prohibited.
|
* network sockets (ssh:22) are prohibited.
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
|
@ -58,7 +64,7 @@ import com.google.inject.Module;
|
||||||
@Test(groups = "live", enabled = true, sequential = true)
|
@Test(groups = "live", enabled = true, sequential = true)
|
||||||
public class VCloudGuestCustomizationLiveTest {
|
public class VCloudGuestCustomizationLiveTest {
|
||||||
|
|
||||||
public static final String PARSE_VMTOOLSD = "vmtoolsd --cmd=\"info-get guestinfo.ovfenv\" |grep vCloud_CustomizationInfo|sed 's/.*value=\"\\(.*\\)\".*/\\1/g'|base64 -d";
|
public static final String PARSE_VMTOOLSD = "vmtoolsd --cmd=\"info-get guestinfo.ovfenv\" |grep vCloud_CustomizationInfo|sed 's/.*value=\"\\(.*\\)\".*/\\1/g'";
|
||||||
|
|
||||||
protected ComputeServiceContext context;
|
protected ComputeServiceContext context;
|
||||||
protected ComputeService client;
|
protected ComputeService client;
|
||||||
|
@ -71,6 +77,8 @@ public class VCloudGuestCustomizationLiveTest {
|
||||||
protected String endpoint;
|
protected String endpoint;
|
||||||
protected String apiversion;
|
protected String apiversion;
|
||||||
|
|
||||||
|
protected NodeMetadata node;
|
||||||
|
|
||||||
protected void setupCredentials() {
|
protected void setupCredentials() {
|
||||||
identity = checkNotNull(System.getProperty("test." + provider + ".identity"), "test." + provider + ".identity");
|
identity = checkNotNull(System.getProperty("test." + provider + ".identity"), "test." + provider + ".identity");
|
||||||
credential = System.getProperty("test." + provider + ".credential");
|
credential = System.getProperty("test." + provider + ".credential");
|
||||||
|
@ -96,51 +104,89 @@ public class VCloudGuestCustomizationLiveTest {
|
||||||
public void setupClient() {
|
public void setupClient() {
|
||||||
setupCredentials();
|
setupCredentials();
|
||||||
Properties overrides = setupProperties();
|
Properties overrides = setupProperties();
|
||||||
|
client = new ComputeServiceContextFactory(setupRestProperties()).createContext(provider,
|
||||||
client = new ComputeServiceContextFactory().createContext(provider,
|
|
||||||
ImmutableSet.<Module> of(new Log4JLoggingModule()), overrides).getComputeService();
|
ImmutableSet.<Module> of(new Log4JLoggingModule()), overrides).getComputeService();
|
||||||
socketTester = new RetryablePredicate<IPSocket>(new InetSocketAddressConnect(), 60, 1, TimeUnit.SECONDS);
|
socketTester = new RetryablePredicate<IPSocket>(new InetSocketAddressConnect(), 300, 1, TimeUnit.SECONDS);
|
||||||
sshFactory = Guice.createInjector(getSshModule()).getInstance(Factory.class);
|
sshFactory = Guice.createInjector(getSshModule()).getInstance(Factory.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Properties setupRestProperties() {
|
||||||
|
return RestContextFactory.getPropertiesFromResource("/rest.properties");
|
||||||
|
}
|
||||||
|
|
||||||
protected JschSshClientModule getSshModule() {
|
protected JschSshClientModule getSshModule() {
|
||||||
return new JschSshClientModule();
|
return new JschSshClientModule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure the script has a lot of screwy characters, knowing our parser throws-out \r
|
||||||
|
private String iLoveAscii = "I '\"love\"' {asc|!}*&";
|
||||||
|
|
||||||
|
String script = "cat > /root/foo.txt<<EOF\n" + iLoveAscii + "\nEOF\n";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExtendedOptionsWithCustomizationScript() throws Exception {
|
public void testExtendedOptionsWithCustomizationScript() throws Exception {
|
||||||
|
|
||||||
String group = "customize";
|
String group = "customize";
|
||||||
String script = "cat > /root/foo.txt<<EOF\nI love candy\nEOF\n";
|
|
||||||
|
|
||||||
TemplateOptions options = client.templateOptions();
|
TemplateOptions options = client.templateOptions();
|
||||||
options.as(VCloudTemplateOptions.class).customizationScript(script);
|
options.as(VCloudTemplateOptions.class).customizationScript(script);
|
||||||
|
node = getOnlyElement(client.createNodesInGroup(group, 1, options));
|
||||||
|
|
||||||
NodeMetadata node = null;
|
Vm vm = Iterables.get(
|
||||||
|
((VCloudClient) client.getContext().getProviderSpecificContext().getApi()).getVApp(node.getUri())
|
||||||
|
.getChildren(), 0);
|
||||||
|
String apiOutput = vm.getGuestCustomizationSection().getCustomizationScript();
|
||||||
|
checkApiOutput(apiOutput);
|
||||||
|
|
||||||
|
IPSocket socket = getSocket(node);
|
||||||
|
|
||||||
|
System.out.printf("%s:%s@%s", node.getCredentials().identity, node.getCredentials().credential, socket);
|
||||||
|
assert socketTester.apply(socket) : socket;
|
||||||
|
|
||||||
|
SshClient ssh = sshFactory.create(socket, node.getCredentials());
|
||||||
try {
|
try {
|
||||||
|
ssh.connect();
|
||||||
node = getOnlyElement(client.createNodesInGroup(group, 1, options));
|
ExecResponse vmTools = ssh.exec(PARSE_VMTOOLSD);
|
||||||
|
System.out.println(vmTools);
|
||||||
IPSocket socket = new IPSocket(get(node.getPublicAddresses(), 0), 22);
|
String fooTxt = ssh.exec("cat /root/foo.txt").getOutput();
|
||||||
|
String decodedVmToolsOutput = new String(base64(vmTools.getOutput().trim()));
|
||||||
assert socketTester.apply(socket);
|
checkVmOutput(fooTxt, decodedVmToolsOutput);
|
||||||
|
|
||||||
SshClient ssh = sshFactory.create(socket, node.getCredentials());
|
|
||||||
try {
|
|
||||||
ssh.connect();
|
|
||||||
|
|
||||||
assertEquals(ssh.exec(PARSE_VMTOOLSD).getOutput(), script.replaceAll("\n", "\r\n"));
|
|
||||||
assertEquals(ssh.exec("cat /root/foo.txt").getOutput().trim(), "I love candy");
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (ssh != null)
|
|
||||||
ssh.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
if (node != null)
|
if (ssh != null)
|
||||||
client.destroyNode(node.getId());
|
ssh.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void checkApiOutput(String apiOutput) {
|
||||||
|
checkApiOutput1_0_1(apiOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkApiOutput1_0_1(String apiOutput) {
|
||||||
|
// in 1.0.1, vcloud director seems to pass through characters via api flawlessly
|
||||||
|
assertEquals(apiOutput, script);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkApiOutput1_0_0(String apiOutput) {
|
||||||
|
// in 1.0.0, vcloud director seems to remove all newlines
|
||||||
|
assertEquals(apiOutput, script.replace("\n", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkVmOutput(String fooTxtContentsMadeByVMwareTools, String decodedVMwareToolsOutput) {
|
||||||
|
// note that vmwaretools throws in \r characters when executing scripts
|
||||||
|
assertEquals(fooTxtContentsMadeByVMwareTools, iLoveAscii + "\r\n");
|
||||||
|
assertEquals(decodedVMwareToolsOutput, script);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterTest
|
||||||
|
public void tearDown() {
|
||||||
|
if (node != null)
|
||||||
|
client.destroyNode(node.getId());
|
||||||
|
if (context != null)
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IPSocket getSocket(NodeMetadata node) {
|
||||||
|
return new IPSocket(get(node.getPublicAddresses(), 0), 22);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -12,7 +12,10 @@
|
||||||
<AdminPasswordEnabled>true</AdminPasswordEnabled>
|
<AdminPasswordEnabled>true</AdminPasswordEnabled>
|
||||||
<AdminPasswordAuto>true</AdminPasswordAuto>
|
<AdminPasswordAuto>true</AdminPasswordAuto>
|
||||||
<ResetPasswordRequired>false</ResetPasswordRequired>
|
<ResetPasswordRequired>false</ResetPasswordRequired>
|
||||||
<CustomizationScript>#!/bin/bash if [[ $1 == "postcustomization" ]]; then echo "post customization" touch /root/.postcustomization ping www.redhat.com -c 1 sleep 30 # register with RHN /usr/sbin/rhnreg_ks --profilename vic_`hostname`_`dmidecode -s system-uuid` --activationkey=XXXXXXXXXXXX --force echo "rhn registered" # make hostname fully qualified to speed up sendmail start perl -i -pe "s/`hostname`/`hostname`.victory.blk/g" /etc/sysconfig/network rm /etc/ssh/*_key* service sshd restart echo "completed" fi</CustomizationScript>
|
<CustomizationScript>cat > /root/foo.txt<<EOF
|
||||||
|
I '"love"' {asc|!}*&
|
||||||
|
EOF
|
||||||
|
</CustomizationScript>
|
||||||
<ComputerName>RHEL5</ComputerName>
|
<ComputerName>RHEL5</ComputerName>
|
||||||
<Link rel="edit"
|
<Link rel="edit"
|
||||||
type="application/vnd.vmware.vcloud.guestCustomizationSection+xml"
|
type="application/vnd.vmware.vcloud.guestCustomizationSection+xml"
|
||||||
|
|
|
@ -29,8 +29,8 @@
|
||||||
<ovf:Network ovf:name="none">
|
<ovf:Network ovf:name="none">
|
||||||
<ovf:Description>This is a special place-holder used for disconnected network interfaces.</ovf:Description>
|
<ovf:Description>This is a special place-holder used for disconnected network interfaces.</ovf:Description>
|
||||||
</ovf:Network>
|
</ovf:Network>
|
||||||
</ovf:NetworkSection> <NetworkConfigSection type="application/vnd.vmware.vcloud.networkConfigSection+xml" href="https://1.1.1.1/api/v1.0/vApp/vapp
|
</ovf:NetworkSection>
|
||||||
-1/networkConfigSection/" ovf:required="false">
|
<NetworkConfigSection type="application/vnd.vmware.vcloud.networkConfigSection+xml" href="https://1.1.1.1/api/v1.0/vApp/vapp-1/networkConfigSection/" ovf:required="false">
|
||||||
<ovf:Info>The configuration parameters for logical networks</ovf:Info>
|
<ovf:Info>The configuration parameters for logical networks</ovf:Info>
|
||||||
<Link rel="edit" type="application/vnd.vmware.vcloud.networkConfigSection+xml" href="https://1.1.1.1/api/v1.0/vApp/vapp-1/networkConfigSection/"/>
|
<Link rel="edit" type="application/vnd.vmware.vcloud.networkConfigSection+xml" href="https://1.1.1.1/api/v1.0/vApp/vapp-1/networkConfigSection/"/>
|
||||||
<NetworkConfig networkName="none">
|
<NetworkConfig networkName="none">
|
||||||
|
|
|
@ -310,7 +310,10 @@
|
||||||
<AdminPasswordEnabled>true</AdminPasswordEnabled>
|
<AdminPasswordEnabled>true</AdminPasswordEnabled>
|
||||||
<AdminPasswordAuto>true</AdminPasswordAuto>
|
<AdminPasswordAuto>true</AdminPasswordAuto>
|
||||||
<ResetPasswordRequired>false</ResetPasswordRequired>
|
<ResetPasswordRequired>false</ResetPasswordRequired>
|
||||||
<CustomizationScript>#!/bin/bash if [[ $1 == "postcustomization" ]]; then echo "post customization" touch /root/.postcustomization ping www.redhat.com -c 1 sleep 30 # register with RHN /usr/sbin/rhnreg_ks --profilename vic_`hostname`_`dmidecode -s system-uuid` --activationkey=XXXXXXXXXXXX --force echo "rhn registered" # make hostname fully qualified to speed up sendmail start perl -i -pe "s/`hostname`/`hostname`.victory.blk/g" /etc/sysconfig/network rm /etc/ssh/*_key* service sshd restart echo "completed" fi</CustomizationScript>
|
<CustomizationScript>cat > /root/foo.txt<<EOF
|
||||||
|
I '"love"' {asc|!}*&
|
||||||
|
EOF
|
||||||
|
</CustomizationScript>
|
||||||
<ComputerName>RHEL5</ComputerName>
|
<ComputerName>RHEL5</ComputerName>
|
||||||
<Link rel="edit"
|
<Link rel="edit"
|
||||||
type="application/vnd.vmware.vcloud.guestCustomizationSection+xml"
|
type="application/vnd.vmware.vcloud.guestCustomizationSection+xml"
|
||||||
|
|
|
@ -184,7 +184,10 @@
|
||||||
<AdminPasswordEnabled>true</AdminPasswordEnabled>
|
<AdminPasswordEnabled>true</AdminPasswordEnabled>
|
||||||
<AdminPasswordAuto>true</AdminPasswordAuto>
|
<AdminPasswordAuto>true</AdminPasswordAuto>
|
||||||
<ResetPasswordRequired>false</ResetPasswordRequired>
|
<ResetPasswordRequired>false</ResetPasswordRequired>
|
||||||
<CustomizationScript>#!/bin/bash if [[ $1 == "postcustomization" ]]; then echo "post customization" touch /root/.postcustomization ping www.redhat.com -c 1 sleep 30 # register with RHN /usr/sbin/rhnreg_ks --profilename vic_`hostname`_`dmidecode -s system-uuid` --activationkey=XXXXXXXXXXXX --force echo "rhn registered" # make hostname fully qualified to speed up sendmail start perl -i -pe "s/`hostname`/`hostname`.victory.blk/g" /etc/sysconfig/network rm /etc/ssh/*_key* service sshd restart echo "completed" fi</CustomizationScript>
|
<CustomizationScript>cat > /root/foo.txt<<EOF
|
||||||
|
I '"love"' {asc|!}*&
|
||||||
|
EOF
|
||||||
|
</CustomizationScript>
|
||||||
<ComputerName>RHEL5</ComputerName>
|
<ComputerName>RHEL5</ComputerName>
|
||||||
<Link rel="edit"
|
<Link rel="edit"
|
||||||
type="application/vnd.vmware.vcloud.guestCustomizationSection+xml"
|
type="application/vnd.vmware.vcloud.guestCustomizationSection+xml"
|
||||||
|
|
|
@ -161,6 +161,15 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
|
||||||
return template.osFamily(UBUNTU).osVersionMatches("10.04").os64Bit(true);
|
return template.osFamily(UBUNTU).osVersionMatches("10.04").os64Bit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default options if none are provided.
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Named("DEFAULT")
|
||||||
|
protected TemplateOptions provideTemplateOptions(Injector injector, TemplateOptions options) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* supplies how the tag is encoded into the name. A string of hex characters is the last argument
|
* supplies how the tag is encoded into the name. A string of hex characters is the last argument
|
||||||
* and tag is the first
|
* and tag is the first
|
||||||
|
|
|
@ -34,4 +34,8 @@ public class BlueLockVCloudDirectorGuestCustomizationLiveTest extends VCloudGues
|
||||||
provider = "bluelock-vcdirector";
|
provider = "bluelock-vcdirector";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void checkApiOutput(String apiOutput) {
|
||||||
|
checkApiOutput1_0_0(apiOutput);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue