Better documentation and a bugfix for cloud-init

This commit is contained in:
Zack Shoylev 2014-02-28 11:02:33 -06:00
parent a6d5adb696
commit eef83ed3cc
7 changed files with 153 additions and 24 deletions

View File

@ -109,6 +109,7 @@ public class NovaComputeServiceAdapter implements
options.securityGroupNames(templateOptions.getSecurityGroupNames().get()); options.securityGroupNames(templateOptions.getSecurityGroupNames().get());
options.userData(templateOptions.getUserData()); options.userData(templateOptions.getUserData());
options.diskConfig(templateOptions.getDiskConfig()); options.diskConfig(templateOptions.getDiskConfig());
options.configDrive(templateOptions.getConfigDrive());
options.networks(templateOptions.getNetworks()); options.networks(templateOptions.getNetworks());
Optional<String> privateKey = Optional.absent(); Optional<String> privateKey = Optional.absent();

View File

@ -69,11 +69,13 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
eTo.generateKeyPair(shouldGenerateKeyPair()); eTo.generateKeyPair(shouldGenerateKeyPair());
eTo.keyPairName(getKeyPairName()); eTo.keyPairName(getKeyPairName());
if (getUserData() != null) { if (getUserData() != null) {
eTo.userData(getUserData()); eTo.userData(getUserData());
} }
if (getDiskConfig() != null) { if (getDiskConfig() != null) {
eTo.diskConfig(getDiskConfig()); eTo.diskConfig(getDiskConfig());
} }
eTo.configDrive(getConfigDrive());
} }
} }
@ -83,6 +85,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
protected String keyPairName; protected String keyPairName;
protected byte[] userData; protected byte[] userData;
protected String diskConfig; protected String diskConfig;
protected boolean configDrive;
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
@ -96,12 +99,13 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
&& equal(this.generateKeyPair, that.generateKeyPair) && equal(this.generateKeyPair, that.generateKeyPair)
&& equal(this.keyPairName, that.keyPairName) && equal(this.keyPairName, that.keyPairName)
&& Arrays.equals(this.userData, that.userData) && Arrays.equals(this.userData, that.userData)
&& equal(this.diskConfig, that.diskConfig); && equal(this.diskConfig, that.diskConfig)
&& equal(this.configDrive, that.configDrive);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode(super.hashCode(), autoAssignFloatingIp, securityGroupNames, generateKeyPair, keyPairName, userData, diskConfig); return Objects.hashCode(super.hashCode(), autoAssignFloatingIp, securityGroupNames, generateKeyPair, keyPairName, userData, diskConfig, configDrive);
} }
@Override @Override
@ -116,6 +120,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
toString.add("keyPairName", keyPairName); toString.add("keyPairName", keyPairName);
toString.add("userData", userData); toString.add("userData", userData);
toString.add("diskConfig", diskConfig); toString.add("diskConfig", diskConfig);
toString.add("configDrive", configDrive);
return toString; return toString;
} }
@ -182,7 +187,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
public String getKeyPairName() { public String getKeyPairName() {
return keyPairName; return keyPairName;
} }
/** /**
* <h3>Note</h3> * <h3>Note</h3>
* *
@ -194,7 +199,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
public boolean shouldGenerateKeyPair() { public boolean shouldGenerateKeyPair() {
return generateKeyPair; return generateKeyPair;
} }
/** /**
* if unset, generate a default group prefixed with {@link jclouds#} according * if unset, generate a default group prefixed with {@link jclouds#} according
* to {@link #getInboundPorts()} * to {@link #getInboundPorts()}
@ -206,15 +211,22 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
} }
public byte[] getUserData() { public byte[] getUserData() {
return userData; return userData;
} }
/** /**
* @see CreateServerOptions#getDiskConfig() * @see CreateServerOptions#getDiskConfig()
*/ */
public String getDiskConfig() { public String getDiskConfig() {
return diskConfig; return diskConfig;
} }
/**
* @see CreateServerOptions#getConfigDrive()
*/
public boolean getConfigDrive() {
return configDrive;
}
public static class Builder { public static class Builder {
@ -238,7 +250,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
public static NovaTemplateOptions keyPairName(String keyPairName) { public static NovaTemplateOptions keyPairName(String keyPairName) {
return new NovaTemplateOptions().keyPairName(keyPairName); return new NovaTemplateOptions().keyPairName(keyPairName);
} }
/** /**
* @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getSecurityGroupNames * @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getSecurityGroupNames
*/ */
@ -352,7 +364,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
NovaTemplateOptions options = new NovaTemplateOptions(); NovaTemplateOptions options = new NovaTemplateOptions();
return options.overrideLoginCredentials(credentials); return options.overrideLoginCredentials(credentials);
} }
/** /**
* @see TemplateOptions#blockUntilRunning * @see TemplateOptions#blockUntilRunning
*/ */
@ -360,7 +372,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
NovaTemplateOptions options = new NovaTemplateOptions(); NovaTemplateOptions options = new NovaTemplateOptions();
return options.blockUntilRunning(blockUntilRunning); return options.blockUntilRunning(blockUntilRunning);
} }
/** /**
* @see NovaTemplateOptions#userData * @see NovaTemplateOptions#userData
*/ */
@ -368,7 +380,7 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
NovaTemplateOptions options = new NovaTemplateOptions(); NovaTemplateOptions options = new NovaTemplateOptions();
return NovaTemplateOptions.class.cast(options.userData(userData)); return NovaTemplateOptions.class.cast(options.userData(userData));
} }
/** /**
* @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getDiskConfig() * @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getDiskConfig()
*/ */
@ -376,6 +388,14 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
NovaTemplateOptions options = new NovaTemplateOptions(); NovaTemplateOptions options = new NovaTemplateOptions();
return NovaTemplateOptions.class.cast(options.diskConfig(diskConfig)); return NovaTemplateOptions.class.cast(options.diskConfig(diskConfig));
} }
/**
* @see org.jclouds.openstack.nova.v2_0.options.CreateServerOptions#getConfigDrive()
*/
public static NovaTemplateOptions configDrive(boolean configDrive) {
NovaTemplateOptions options = new NovaTemplateOptions();
return NovaTemplateOptions.class.cast(options.configDrive(configDrive));
}
} }
// methods that only facilitate returning the correct object type // methods that only facilitate returning the correct object type
@ -528,11 +548,11 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
* User data as bytes (not base64-encoded) * User data as bytes (not base64-encoded)
*/ */
public NovaTemplateOptions userData(byte[] userData) { public NovaTemplateOptions userData(byte[] userData) {
// This limit may not be needed for nova // This limit may not be needed for nova
checkArgument(checkNotNull(userData, "userData").length <= 16 * 1024, checkArgument(checkNotNull(userData, "userData").length <= 16 * 1024,
"userData cannot be larger than 16kb"); "userData cannot be larger than 16kb");
this.userData = userData; this.userData = userData;
return this; return this;
} }
/** /**
@ -541,5 +561,18 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
public NovaTemplateOptions diskConfig(String diskConfig) { public NovaTemplateOptions diskConfig(String diskConfig) {
this.diskConfig = diskConfig; this.diskConfig = diskConfig;
return this; return this;
} }
/**
* OpenStack can be configured to write metadata to a special configuration drive that will be
* attached to the instance when it boots. The instance can retrieve any information that would
* normally be available through the metadata service by mounting this disk and reading files from it.
* To enable the config drive, set this parameter to "true".
* This has to be enabled for user data cases.
* @see CreateServerOptions#getConfigDrive()
*/
public NovaTemplateOptions configDrive(boolean configDrive) {
this.configDrive = configDrive;
return this;
}
} }

View File

@ -107,6 +107,7 @@ public class CreateServerOptions implements MapBinder {
private String diskConfig; private String diskConfig;
private Set<String> networks = ImmutableSet.of(); private Set<String> networks = ImmutableSet.of();
private String availabilityZone; private String availabilityZone;
private boolean configDrive;
@Override @Override
public boolean equals(Object object) { public boolean equals(Object object) {
@ -119,7 +120,8 @@ public class CreateServerOptions implements MapBinder {
&& equal(metadata, other.metadata) && equal(personality, other.personality) && equal(metadata, other.metadata) && equal(personality, other.personality)
&& equal(adminPass, other.adminPass) && equal(diskConfig, other.diskConfig) && equal(adminPass, other.adminPass) && equal(diskConfig, other.diskConfig)
&& equal(adminPass, other.adminPass) && equal(networks, other.networks) && equal(adminPass, other.adminPass) && equal(networks, other.networks)
&& equal(availabilityZone, other.availabilityZone); && equal(availabilityZone, other.availabilityZone)
&& equal(configDrive, other.configDrive);
} else { } else {
return false; return false;
} }
@ -127,7 +129,7 @@ public class CreateServerOptions implements MapBinder {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode(keyName, securityGroupNames, metadata, personality, adminPass, networks, availabilityZone); return Objects.hashCode(keyName, securityGroupNames, metadata, personality, adminPass, networks, availabilityZone, configDrive);
} }
protected ToStringHelper string() { protected ToStringHelper string() {
@ -147,6 +149,7 @@ public class CreateServerOptions implements MapBinder {
if (!networks.isEmpty()) if (!networks.isEmpty())
toString.add("networks", networks); toString.add("networks", networks);
toString.add("availability_zone", availabilityZone == null ? null : availabilityZone); toString.add("availability_zone", availabilityZone == null ? null : availabilityZone);
toString.add("configDrive", configDrive);
return toString; return toString;
} }
@ -171,6 +174,8 @@ public class CreateServerOptions implements MapBinder {
@Named("OS-DCF:diskConfig") @Named("OS-DCF:diskConfig")
String diskConfig; String diskConfig;
Set<Map<String, String>> networks; Set<Map<String, String>> networks;
@Named("config_drive")
String configDrive;
private ServerRequest(String name, String imageRef, String flavorRef) { private ServerRequest(String name, String imageRef, String flavorRef) {
this.name = name; this.name = name;
@ -195,6 +200,8 @@ public class CreateServerOptions implements MapBinder {
server.availabilityZone = availabilityZone; server.availabilityZone = availabilityZone;
if (userData != null) if (userData != null)
server.user_data = base64().encode(userData); server.user_data = base64().encode(userData);
if (configDrive == true)
server.configDrive = "true";
if (securityGroupNames.size() > 0) { if (securityGroupNames.size() > 0) {
server.securityGroupNames = Sets.newLinkedHashSet(); server.securityGroupNames = Sets.newLinkedHashSet();
for (String groupName : securityGroupNames) { for (String groupName : securityGroupNames) {
@ -290,12 +297,24 @@ public class CreateServerOptions implements MapBinder {
* Custom user-data can be also be supplied at launch time. * Custom user-data can be also be supplied at launch time.
* It is retrievable by the instance and is often used for launch-time configuration * It is retrievable by the instance and is often used for launch-time configuration
* by instance scripts. * by instance scripts.
* Pass userData unencdoed, as the value will be base64 encoded automatically.
*/ */
public CreateServerOptions userData(byte[] userData) { public CreateServerOptions userData(byte[] userData) {
this.userData = userData; this.userData = userData;
return this; return this;
} }
/**
* Set to true to use a config drive for metadata.
* This is a separate configuration drive that can be used separately from the metadata service.
* This needs to be set to "true" when trying to use user data for cloud-init.
* @see http://docs.openstack.org/grizzly/openstack-compute/admin/content/config-drive.html
*/
public CreateServerOptions configDrive(boolean configDrive) {
this.configDrive = configDrive;
return this;
}
/** /**
* A keypair name can be defined when creating a server. This key will be * A keypair name can be defined when creating a server. This key will be
* linked to the server and used to SSH connect to the machine * linked to the server and used to SSH connect to the machine

View File

@ -131,6 +131,40 @@ public class NovaComputeServiceAdapterExpectTest extends BaseNovaComputeServiceC
assertEquals(server.getNode().getServer().getDiskConfig().orNull(), Server.DISK_CONFIG_AUTO); assertEquals(server.getNode().getServer().getDiskConfig().orNull(), Server.DISK_CONFIG_AUTO);
} }
public void testCreateNodeWithGroupEncodedIntoNameWithConfigDrive() throws Exception {
HttpRequest createServer = HttpRequest
.builder()
.method("POST")
.endpoint("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers")
.addHeader("Accept", "application/json")
.addHeader("X-Auth-Token", authToken)
.payload(payloadFromStringWithContentType(
"{\"server\":{\"name\":\"test-e92\",\"imageRef\":\"1241\",\"flavorRef\":\"100\",\"config_drive\":\"true\"}}","application/json"))
.build();
HttpResponse createServerResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted")
.payload(payloadFromResourceWithContentType("/new_server_config_drive.json","application/json; charset=UTF-8")).build();
Map<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
.put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess)
.put(extensionsOfNovaRequest, extensionsOfNovaResponse)
.put(listDetail, listDetailResponse)
.put(listFlavorsDetail, listFlavorsDetailResponse)
.put(createServer, createServerResponse)
.put(serverDetail, serverDetailResponse).build();
Injector forConfigDrive = requestsSendResponses(requestResponseMap);
Template template = forConfigDrive.getInstance(TemplateBuilder.class).build();
template.getOptions().as(NovaTemplateOptions.class).configDrive(true);
NovaComputeServiceAdapter adapter = forConfigDrive.getInstance(NovaComputeServiceAdapter.class);
NodeAndInitialCredentials<ServerInZone> server = adapter.createNodeWithGroupEncodedIntoName("test", "test-e92", template);
assertNotNull(server);
}
public void testCreateNodeWithGroupEncodedIntoNameWhenSecurityGroupsArePresent() throws Exception { public void testCreateNodeWithGroupEncodedIntoNameWhenSecurityGroupsArePresent() throws Exception {
HttpRequest createServer = HttpRequest HttpRequest createServer = HttpRequest

View File

@ -203,7 +203,7 @@ public class NovaTemplateOptionsTest {
options.diskConfig(Server.DISK_CONFIG_AUTO); options.diskConfig(Server.DISK_CONFIG_AUTO);
assertEquals(options.getDiskConfig(), Server.DISK_CONFIG_AUTO); assertEquals(options.getDiskConfig(), Server.DISK_CONFIG_AUTO);
} }
@Test(expectedExceptions = IllegalArgumentException.class) @Test(expectedExceptions = IllegalArgumentException.class)
public void testblockOnPortBadFormat() { public void testblockOnPortBadFormat() {
NovaTemplateOptions options = new NovaTemplateOptions(); NovaTemplateOptions options = new NovaTemplateOptions();

View File

@ -0,0 +1,42 @@
{
"server": {
"status": "BUILD(scheduling)",
"updated": "2012-03-19T06:21:13Z",
"hostId": "",
"user_id": "54297837463082",
"name": "test-e92",
"links": [{
"href": "https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/37936628937291/servers/71752",
"rel": "self"
}, {
"href": "https://az-1.region-a.geo-1.compute.hpcloudsvc.com/37936628937291/servers/71752",
"rel": "bookmark"
}],
"addresses": {},
"tenant_id": "37936628937291",
"image": {
"id": "1241",
"links": [{
"href": "https://az-1.region-a.geo-1.compute.hpcloudsvc.com/37936628937291/images/1241",
"rel": "bookmark"
}]
},
"created": "2012-03-19T06:21:13Z",
"uuid": "47491020-6a78-4f63-9475-23195ac4515c",
"accessIPv4": "",
"accessIPv6": "",
"key_name": null,
"adminPass": "ZWuHcmTMQ7eXoHeM",
"flavor": {
"id": "100",
"links": [{
"href": "https://az-1.region-a.geo-1.compute.hpcloudsvc.com/37936628937291/flavors/100",
"rel": "bookmark"
}]
},
"config_drive": "true",
"id": 71752,
"metadata": {},
"OS-DCF:diskConfig": "AUTO"
}
}

View File

@ -11,7 +11,7 @@
"accessIPv6" : "::babe:67.23.10.132", "accessIPv6" : "::babe:67.23.10.132",
"status": "BUILD(scheduling)", "status": "BUILD(scheduling)",
"progress": 60, "progress": 60,
"OS-DCF:diskConfig": "AUTO", "OS-DCF:diskConfig": "AUTO",
"image" : { "image" : {
"id": "52415800-8b69-11e0-9b19-734f6f006e54", "id": "52415800-8b69-11e0-9b19-734f6f006e54",
"links": [ "links": [