mirror of https://github.com/apache/jclouds.git
Better documentation and a bugfix for cloud-init
This commit is contained in:
parent
a6d5adb696
commit
eef83ed3cc
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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": [
|
||||||
|
|
Loading…
Reference in New Issue