mirror of https://github.com/apache/jclouds.git
Issue 112: added better testing for cloud compatibility
git-svn-id: http://jclouds.googlecode.com/svn/trunk@2290 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
parent
edf82a7047
commit
9a1abce72f
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
*
|
||||
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
|
||||
*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.predicates;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* Retries a condition until it is met or a timeout occurs.
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
public class RetryablePredicate<T> implements Predicate<T> {
|
||||
private final int maxWait;
|
||||
private final int checkInterval;
|
||||
private final Predicate<T> predicate;
|
||||
|
||||
@Resource
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
public RetryablePredicate(Predicate<T> predicate, long maxWait, long checkInterval, TimeUnit unit) {
|
||||
this.predicate = predicate;
|
||||
this.maxWait = (int) unit.toMillis(maxWait);
|
||||
this.checkInterval = (int) unit.toMillis(checkInterval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(T input) {
|
||||
try {
|
||||
for (DateTime end = new DateTime().plusMillis(maxWait); before(end); Thread
|
||||
.sleep(checkInterval)) {
|
||||
if (predicate.apply(input)) {
|
||||
return true;
|
||||
} else if (!before(end)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.warn(e, "predicate %s on %s interrupted, returning false", input, predicate);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean before(DateTime end){
|
||||
return new DateTime().compareTo(end) < 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
*
|
||||
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
|
||||
*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF 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.predicates;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.logging.Logger;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* Tests to see if a socket is open.
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Singleton
|
||||
public class SocketOpen implements Predicate<InetSocketAddress> {
|
||||
|
||||
@Resource
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
@Inject(optional = true)
|
||||
@Named("org.jclouds.socket_timeout")
|
||||
private int timeout = 2000;
|
||||
|
||||
@Override
|
||||
public boolean apply(InetSocketAddress socketAddress) {
|
||||
Socket socket = null;
|
||||
try {
|
||||
logger.trace("testing socket %s", socketAddress);
|
||||
socket = new Socket();
|
||||
socket.setReuseAddress(false);
|
||||
socket.setSoLinger(false, 1);
|
||||
socket.setSoTimeout(timeout);
|
||||
socket.connect(socketAddress, timeout);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException ioe) {
|
||||
// no work to do
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package org.jclouds.predicates;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*
|
||||
*/
|
||||
@Test(groups = "unit", sequential = true, testName = "jclouds.RetryablePredicateTest")
|
||||
public class RetryablePredicateTest {
|
||||
|
||||
@Test
|
||||
void testAlwaysTrue() {
|
||||
RetryablePredicate<String> predicate = new RetryablePredicate<String>(Predicates
|
||||
.<String> alwaysTrue(), 3, 1, TimeUnit.SECONDS);
|
||||
DateTime start = new DateTime();
|
||||
predicate.apply("");
|
||||
DateTime now = new DateTime();
|
||||
assert now.compareTo(start.plusSeconds(1)) < 0 : String.format("%s should be less than %s",
|
||||
now, start.plusSeconds(1)); }
|
||||
|
||||
@Test
|
||||
void testAlwaysFalseMillis() {
|
||||
RetryablePredicate<String> predicate = new RetryablePredicate<String>(Predicates
|
||||
.<String> alwaysFalse(), 3, 1, TimeUnit.SECONDS);
|
||||
DateTime start = new DateTime();
|
||||
predicate.apply("");
|
||||
DateTime now = new DateTime();
|
||||
assert now.compareTo(start.plusSeconds(3)) > 0 : String.format("%s should be less than %s",
|
||||
start.plusSeconds(3), now);
|
||||
assert now.compareTo(start.plusSeconds(6)) <= 0 : String.format(
|
||||
"%s should be greater than %s", start.plusSeconds(6), now);
|
||||
|
||||
}
|
||||
|
||||
private static class SecondTimeTrue implements Predicate<String> {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@Override
|
||||
public boolean apply(String input) {
|
||||
return count++ == 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSecondTimeTrue() {
|
||||
RetryablePredicate<String> predicate = new RetryablePredicate<String>(new SecondTimeTrue(),
|
||||
3, 1, TimeUnit.SECONDS);
|
||||
|
||||
DateTime start = new DateTime();
|
||||
predicate.apply("");
|
||||
DateTime now = new DateTime();
|
||||
assert now.compareTo(start.plusSeconds(1)) > 0 : String.format("%s should be greater than %s",
|
||||
now,start.plusSeconds(1));
|
||||
assert now.compareTo(start.plusSeconds(2)) <= 0 : String.format(
|
||||
"%s should be greater than %s", start.plusSeconds(2), now);
|
||||
}
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@ package org.jclouds.vcloud;
|
|||
import static org.jclouds.vcloud.VCloudMediaType.CATALOG_XML;
|
||||
import static org.jclouds.vcloud.VCloudMediaType.TASKSLIST_XML;
|
||||
import static org.jclouds.vcloud.VCloudMediaType.TASK_XML;
|
||||
import static org.jclouds.vcloud.VCloudMediaType.VAPP_XML;
|
||||
import static org.jclouds.vcloud.VCloudMediaType.VDC_XML;
|
||||
|
||||
import java.net.URI;
|
||||
|
@ -157,4 +158,9 @@ public interface VCloudClient {
|
|||
@Path("/action/cancel")
|
||||
Future<Void> cancelTask(@Endpoint URI task);
|
||||
|
||||
@GET
|
||||
@Consumes(VAPP_XML)
|
||||
@Endpoint(org.jclouds.vcloud.endpoints.VCloud.class)
|
||||
@Path("/vapp/{vAppId}")
|
||||
String getVApp(@PathParam("vAppId") String appId);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
*/
|
||||
package org.jclouds.vcloud;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
|
||||
|
@ -52,5 +54,5 @@ public interface VCloudDiscovery {
|
|||
@Endpoint(Org.class)
|
||||
@Consumes(VCloudMediaType.ORG_XML)
|
||||
@XMLResponseParser(OrgHandler.class)
|
||||
Organization getOrganization();
|
||||
Future<? extends Organization> getOrganization();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
package org.jclouds.vcloud;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
|
@ -66,5 +67,5 @@ public interface VCloudLogin {
|
|||
@ResponseParser(ParseLoginResponseFromHeaders.class)
|
||||
@Path("/login")
|
||||
@Consumes(MediaType.APPLICATION_XML)
|
||||
VCloudSession login();
|
||||
Future<VCloudSession> login();
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.jclouds.concurrent.ExpirableSupplier;
|
|||
import org.jclouds.http.RequiresHttp;
|
||||
import org.jclouds.http.filters.BasicAuthentication;
|
||||
import org.jclouds.rest.RestClientFactory;
|
||||
import org.jclouds.util.Utils;
|
||||
import org.jclouds.vcloud.VCloudDiscovery;
|
||||
import org.jclouds.vcloud.VCloudLogin;
|
||||
import org.jclouds.vcloud.VCloudToken;
|
||||
|
@ -85,7 +86,12 @@ public class VCloudDiscoveryRestClientModule extends AbstractModule {
|
|||
@Named(PROPERTY_VCLOUD_SESSIONINTERVAL) long seconds, final VCloudLogin login) {
|
||||
return new ExpirableSupplier<VCloudSession>(new Supplier<VCloudSession>() {
|
||||
public VCloudSession get() {
|
||||
return login.login();
|
||||
try {
|
||||
return login.login().get(45, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
Utils.<RuntimeException> rethrowIfRuntimeOrSameType(e);
|
||||
throw new RuntimeException("Error logging in", e);
|
||||
}
|
||||
}
|
||||
}, seconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
|
|
@ -68,29 +68,30 @@ public class VCloudRestClientModule extends AbstractModule {
|
|||
@Provides
|
||||
@Catalog
|
||||
@Singleton
|
||||
protected URI provideCatalog(VCloudDiscovery discovery) {
|
||||
return discovery.getOrganization().getCatalog().getLocation();
|
||||
protected URI provideCatalog(VCloudDiscovery discovery) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return discovery.getOrganization().get(45, TimeUnit.SECONDS).getCatalog().getLocation();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@CatalogItemRoot
|
||||
@Singleton
|
||||
String provideCatalogItemRoot(@VCloud URI vcloudUri) {
|
||||
return vcloudUri.toASCIIString()+"/catalogItem";
|
||||
return vcloudUri.toASCIIString() + "/catalogItem";
|
||||
}
|
||||
|
||||
@Provides
|
||||
@VAppRoot
|
||||
@Singleton
|
||||
String provideVAppRoot(@VCloud URI vcloudUri) {
|
||||
return vcloudUri.toASCIIString()+"/vapp";
|
||||
return vcloudUri.toASCIIString() + "/vapp";
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@VDC
|
||||
@Singleton
|
||||
protected URI provideDefaultVDC(VCloudDiscovery discovery) {
|
||||
return discovery.getOrganization().getVDCs().values().iterator().next().getLocation();
|
||||
protected URI provideDefaultVDC(VCloudDiscovery discovery) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return discovery.getOrganization().get(45, TimeUnit.SECONDS).getVDCs().values().iterator()
|
||||
.next().getLocation();
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -105,7 +106,7 @@ public class VCloudRestClientModule extends AbstractModule {
|
|||
@Provides
|
||||
@TasksList
|
||||
@Singleton
|
||||
protected URI provideDefaultTasksList(VCloudDiscovery discovery) {
|
||||
return discovery.getOrganization().getTasksLists().values().iterator().next().getLocation();
|
||||
protected URI provideDefaultTasksList(VCloudDiscovery discovery) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return discovery.getOrganization().get(45, TimeUnit.SECONDS).getTasksLists().values().iterator().next().getLocation();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,11 @@ public class CatalogIdToUri implements Function<Object, String> {
|
|||
@Inject
|
||||
@CatalogItemRoot
|
||||
private String catalogItemRoot;
|
||||
|
||||
@Override
|
||||
|
||||
public String apply(Object from) {
|
||||
checkArgument(checkNotNull(from, "from") instanceof Integer,
|
||||
"this binder is only valid for Integers!");
|
||||
return String.format("%s/%d",catalogItemRoot,from);
|
||||
return String.format("%s/%d", catalogItemRoot, from);
|
||||
}
|
||||
|
||||
}
|
|
@ -18,12 +18,11 @@ public class VAppIdToUri implements Function<Object, String> {
|
|||
@Inject
|
||||
@VAppRoot
|
||||
private String vAppRoot;
|
||||
|
||||
@Override
|
||||
|
||||
public String apply(Object from) {
|
||||
checkArgument(checkNotNull(from, "from") instanceof Integer,
|
||||
"this binder is only valid for Integers!");
|
||||
return String.format("%s/%d",vAppRoot,from);
|
||||
return String.format("%s/%d", vAppRoot, from);
|
||||
}
|
||||
|
||||
}
|
|
@ -89,6 +89,13 @@ public class VCloudClientLiveTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test(enabled = false)
|
||||
public void testGetVApp() throws Exception {
|
||||
String response = connection.getVApp("188849-2");
|
||||
assertNotNull(response);
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@BeforeGroups(groups = { "live" })
|
||||
public void setupClient() {
|
||||
String endpoint = checkNotNull(System.getProperty("jclouds.test.endpoint"),
|
||||
|
|
|
@ -33,6 +33,7 @@ import static org.testng.Assert.assertNotNull;
|
|||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
@ -89,7 +90,7 @@ public class VCloudDiscoveryLiveTest {
|
|||
|
||||
@Test
|
||||
public void testOrganization() throws Exception {
|
||||
Organization response = context.getApi().getOrganization();
|
||||
Organization response = context.getApi().getOrganization().get(45, TimeUnit.SECONDS);
|
||||
assertNotNull(response);
|
||||
assertNotNull(account);
|
||||
assertNotNull(response.getCatalog());
|
||||
|
|
|
@ -32,6 +32,7 @@ import static org.testng.Assert.assertNotNull;
|
|||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
@ -91,7 +92,7 @@ public class VCloudLoginLiveTest {
|
|||
public void testLogin() throws Exception {
|
||||
VCloudLogin authentication = context.getApi();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
VCloudSession response = authentication.login();
|
||||
VCloudSession response = authentication.login().get(45, TimeUnit.SECONDS);
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getVCloudToken());
|
||||
assertNotNull(response.getOrgs());
|
||||
|
|
|
@ -30,6 +30,10 @@ import static org.jclouds.vcloud.reference.VCloudConstants.PROPERTY_VCLOUD_USER;
|
|||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.jclouds.http.HttpRetryHandler;
|
||||
|
@ -70,6 +74,25 @@ public class VCloudDiscoveryRestClientModuleTest {
|
|||
});
|
||||
}
|
||||
|
||||
public static abstract class FutureBase<V> implements Future<V> {
|
||||
public boolean cancel(boolean b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public V get(long l, TimeUnit timeUnit) throws InterruptedException, ExecutionException,
|
||||
TimeoutException {
|
||||
return get();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdatesOnlyOncePerSecond() throws NoSuchMethodException, InterruptedException {
|
||||
VCloudDiscoveryRestClientModule module = new VCloudDiscoveryRestClientModule();
|
||||
|
@ -77,18 +100,24 @@ public class VCloudDiscoveryRestClientModuleTest {
|
|||
|
||||
private final AtomicInteger token = new AtomicInteger();
|
||||
|
||||
public VCloudSession login() {
|
||||
return new VCloudSession() {
|
||||
public Future<VCloudSession> login() {
|
||||
return new FutureBase<VCloudSession>() {
|
||||
@Override
|
||||
public VCloudSession get() throws InterruptedException, ExecutionException {
|
||||
return new VCloudSession() {
|
||||
|
||||
public Map<String, NamedLink> getOrgs() {
|
||||
return null;
|
||||
public Map<String, NamedLink> getOrgs() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getVCloudToken() {
|
||||
return token.incrementAndGet() + "";
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public String getVCloudToken() {
|
||||
return token.incrementAndGet() + "";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package org.jclouds.vcloud.predicates;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.jclouds.logging.Logger;
|
||||
import org.jclouds.vcloud.VCloudClient;
|
||||
import org.jclouds.vcloud.domain.Task;
|
||||
import org.jclouds.vcloud.domain.TaskStatus;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* Tests to see if a task succeeds.
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
@Singleton
|
||||
public class TaskSuccess implements Predicate<URI> {
|
||||
|
||||
private final VCloudClient client;
|
||||
|
||||
@Inject(optional = true)
|
||||
@Named("org.jclouds.vcloud.timeout")
|
||||
private long taskTimeout = 30000;
|
||||
|
||||
@Resource
|
||||
protected Logger logger = Logger.NULL;
|
||||
|
||||
@Inject
|
||||
public TaskSuccess(VCloudClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public boolean apply(URI taskUri) {
|
||||
logger.trace("looking for status on task %s", taskUri);
|
||||
|
||||
Task task;
|
||||
try {
|
||||
task = client.getTask(taskUri).get(taskTimeout, TimeUnit.MILLISECONDS);
|
||||
logger.trace("%s: looking for status %s: currently: %s", task, TaskStatus.SUCCESS, task
|
||||
.getStatus());
|
||||
return task.getStatus() == TaskStatus.SUCCESS;
|
||||
} catch (InterruptedException e) {
|
||||
logger.warn(e, "%s interrupted, returning false", taskUri);
|
||||
} catch (ExecutionException e) {
|
||||
logger.warn(e, "%s exception, returning false", taskUri);
|
||||
} catch (TimeoutException e) {
|
||||
logger.warn(e, "%s timeout, returning false", taskUri);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -98,12 +98,25 @@
|
|||
<priority value="DEBUG" />
|
||||
<appender-ref ref="ASYNCWIRE" />
|
||||
</category>
|
||||
|
||||
|
||||
<category name="org.jclouds.predicates.SocketOpen">
|
||||
<priority value="TRACE" />
|
||||
<appender-ref ref="ASYNCWIRE" />
|
||||
</category>
|
||||
|
||||
<category name="org.jclouds.vcloud.predicates.TaskSuccess">
|
||||
<priority value="TRACE" />
|
||||
<appender-ref ref="ASYNCWIRE" />
|
||||
</category>
|
||||
|
||||
<!--
|
||||
<category name="jclouds.http.wire">
|
||||
<priority value="DEBUG" />
|
||||
<appender-ref ref="ASYNCWIRE" />
|
||||
</category>
|
||||
|
||||
<!-- ======================= -->
|
||||
--><!-- ======================= -->
|
||||
<!-- Setup the Root category -->
|
||||
<!-- ======================= -->
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ public class BindInstantiateVAppTemplateParamsToXmlEntity implements MapBinder {
|
|||
public void bindToRequest(HttpRequest request, Map<String, String> postParams) {
|
||||
|
||||
String name = checkNotNull(postParams.get("name"), "name parameter not present");
|
||||
String password = checkNotNull(postParams.get("password"), "password parameter not present");
|
||||
String row = checkNotNull(postParams.get("row"), "row parameter not present");
|
||||
String group = checkNotNull(postParams.get("group"), "group parameter not present");
|
||||
|
||||
String template = checkNotNull(postParams.get("template"), "template parameter not present");
|
||||
String count = checkNotNull(postParams.get("count"), "count parameter not present");
|
||||
String megabytes = checkNotNull(postParams.get("megabytes"),
|
||||
|
@ -36,6 +40,9 @@ public class BindInstantiateVAppTemplateParamsToXmlEntity implements MapBinder {
|
|||
String network = checkNotNull(postParams.get("network"), "network parameter not present");
|
||||
|
||||
String entity = xmlTemplate.replaceAll("\\{name\\}", name);
|
||||
entity = entity.replaceAll("\\{password\\}", password);
|
||||
entity = entity.replaceAll("\\{row\\}", row);
|
||||
entity = entity.replaceAll("\\{group\\}", group);
|
||||
entity = entity.replaceAll("\\{template\\}", template);
|
||||
entity = entity.replaceAll("\\{count\\}", count);
|
||||
entity = entity.replaceAll("\\{megabytes\\}", megabytes);
|
||||
|
|
|
@ -24,6 +24,13 @@ public class InstantiateVAppTemplateOptions extends BindInstantiateVAppTemplateP
|
|||
@Network
|
||||
private URI defaultNetwork;
|
||||
|
||||
@VisibleForTesting
|
||||
String password = "password";
|
||||
@VisibleForTesting
|
||||
String group = "default";
|
||||
@VisibleForTesting
|
||||
String row = "default";
|
||||
|
||||
@VisibleForTesting
|
||||
String cpuCount = "1";
|
||||
@VisibleForTesting
|
||||
|
@ -35,6 +42,9 @@ public class InstantiateVAppTemplateOptions extends BindInstantiateVAppTemplateP
|
|||
Map<String, String> copy = Maps.newHashMap();
|
||||
copy.putAll(postParams);
|
||||
copy.put("count", cpuCount);
|
||||
copy.put("password", password);
|
||||
copy.put("group", group);
|
||||
copy.put("row", row);
|
||||
copy.put("megabytes", megabytes);
|
||||
copy.put("network", network != null ? network : defaultNetwork.toASCIIString());
|
||||
super.bindToRequest(request, copy);
|
||||
|
@ -57,6 +67,21 @@ public class InstantiateVAppTemplateOptions extends BindInstantiateVAppTemplateP
|
|||
return this;
|
||||
}
|
||||
|
||||
public InstantiateVAppTemplateOptions withPassword(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public InstantiateVAppTemplateOptions inGroup(String group) {
|
||||
this.group = group;
|
||||
return this;
|
||||
}
|
||||
|
||||
public InstantiateVAppTemplateOptions inRow(String row) {
|
||||
this.row = row;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
/**
|
||||
|
@ -82,5 +107,30 @@ public class InstantiateVAppTemplateOptions extends BindInstantiateVAppTemplateP
|
|||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
return options.inNetwork(networkLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see InstantiateVAppTemplateOptions#withPassword(String)
|
||||
*/
|
||||
public static InstantiateVAppTemplateOptions withPassword(String password) {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
return options.withPassword(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see InstantiateVAppTemplateOptions#inGroup(String)
|
||||
*/
|
||||
public static InstantiateVAppTemplateOptions inGroup(String group) {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
return options.inGroup(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see InstantiateVAppTemplateOptions#inRow(String)
|
||||
*/
|
||||
public static InstantiateVAppTemplateOptions inRow(String row) {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
return options.inRow(row);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<InternetService xmlns="urn:tmrk:vCloudExpress-1.0"
|
||||
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Id>{id}</Id>{options}
|
||||
</InternetService>
|
|
@ -5,11 +5,11 @@
|
|||
<ProductSection xmlns:q1="http://www.vmware.com/vcloud/v1"
|
||||
xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1">
|
||||
<Property xmlns="http://schemas.dmtf.org/ovf/envelope/1"
|
||||
ovf:key="password" ovf:value="secretPassword" />
|
||||
ovf:key="password" ovf:value="{password}" />
|
||||
<Property xmlns="http://schemas.dmtf.org/ovf/envelope/1"
|
||||
ovf:key="row" ovf:value="Row1" />
|
||||
ovf:key="row" ovf:value="{row}" />
|
||||
<Property xmlns="http://schemas.dmtf.org/ovf/envelope/1"
|
||||
ovf:key="group" ovf:value="Group1" />
|
||||
ovf:key="group" ovf:value="{group}" />
|
||||
</ProductSection>
|
||||
<VirtualHardwareSection xmlns:q1="http://www.vmware.com/vcloud/v1">
|
||||
<Item xmlns="http://schemas.dmtf.org/ovf/envelope/1">
|
||||
|
|
|
@ -36,10 +36,10 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.jclouds.concurrent.WithinThreadExecutorService;
|
||||
import org.jclouds.concurrent.config.ExecutorServiceModule;
|
||||
import org.jclouds.http.HttpResponseException;
|
||||
import org.jclouds.logging.log4j.config.Log4JLoggingModule;
|
||||
import org.jclouds.predicates.RetryablePredicate;
|
||||
import org.jclouds.predicates.SocketOpen;
|
||||
import org.jclouds.ssh.SshClient;
|
||||
import org.jclouds.ssh.SshClient.Factory;
|
||||
import org.jclouds.ssh.jsch.config.JschSshClientModule;
|
||||
|
@ -48,6 +48,7 @@ import org.jclouds.vcloud.VCloudClientLiveTest;
|
|||
import org.jclouds.vcloud.domain.Task;
|
||||
import org.jclouds.vcloud.domain.TaskStatus;
|
||||
import org.jclouds.vcloud.domain.VAppStatus;
|
||||
import org.jclouds.vcloud.predicates.TaskSuccess;
|
||||
import org.jclouds.vcloud.terremark.domain.InternetService;
|
||||
import org.jclouds.vcloud.terremark.domain.Node;
|
||||
import org.jclouds.vcloud.terremark.domain.ResourceType;
|
||||
|
@ -58,7 +59,6 @@ import org.testng.annotations.BeforeGroups;
|
|||
import org.testng.annotations.Test;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
/**
|
||||
|
@ -77,6 +77,10 @@ public class TerremarkVCloudClientLiveTest extends VCloudClientLiveTest {
|
|||
private Node node;
|
||||
private VApp vApp;
|
||||
|
||||
private RetryablePredicate<InetSocketAddress> socketTester;
|
||||
|
||||
private RetryablePredicate<URI> successTester;
|
||||
|
||||
public static final String PREFIX = System.getProperty("user.name") + "-terremark";
|
||||
|
||||
@Test
|
||||
|
@ -95,12 +99,16 @@ public class TerremarkVCloudClientLiveTest extends VCloudClientLiveTest {
|
|||
String serverName = "adriantest";
|
||||
int processorCount = 1;
|
||||
int memory = 512;
|
||||
long hardDisk = 4194304;
|
||||
String catalogOs = "Ubuntu JeOS 9.04 (32-bit)";
|
||||
String expectedOs = "Ubuntu Linux (32-bit)";
|
||||
// long hardDisk = 4194304;
|
||||
// String catalogOs = "Ubuntu JeOS 9.04 (32-bit)";
|
||||
// String expectedOs = "Ubuntu Linux (32-bit)";
|
||||
long hardDisk = 4194304 / 4 * 10;
|
||||
String catalogOs = "CentOS 5.3 (32-bit)";
|
||||
String expectedOs = "Red Hat Enterprise Linux 5 (32-bit)";
|
||||
|
||||
int templateId = tmClient.getCatalog().get(45, TimeUnit.SECONDS).get(catalogOs).getId();
|
||||
|
||||
System.out.printf("%d: instantiating vApp%n", System.currentTimeMillis());
|
||||
vApp = tmClient.instantiateVAppTemplate(serverName, templateId).get(45, TimeUnit.SECONDS);
|
||||
|
||||
assertEquals(vApp.getStatus(), VAppStatus.CREATING);
|
||||
|
@ -127,13 +135,17 @@ public class TerremarkVCloudClientLiveTest extends VCloudClientLiveTest {
|
|||
assertEquals(((HttpResponseException) e.getCause()).getResponse().getStatusCode(), 501);
|
||||
}
|
||||
|
||||
deployTask = blockUntilSuccess(deployTask);
|
||||
assert successTester.apply(deployTask.getLocation());
|
||||
System.out.printf("%d: done deploying vApp%n", System.currentTimeMillis());
|
||||
|
||||
vApp = tmClient.getVApp(vApp.getId()).get(45, TimeUnit.SECONDS);
|
||||
verifyConfigurationOfVApp(vApp, serverName, expectedOs, processorCount, memory, hardDisk);
|
||||
assertEquals(vApp.getStatus(), VAppStatus.OFF);
|
||||
|
||||
deployTask = blockUntilSuccess(tmClient.powerOnVApp(vApp.getId()).get(45, TimeUnit.SECONDS));
|
||||
assert successTester.apply(tmClient.powerOnVApp(vApp.getId()).get(45, TimeUnit.SECONDS)
|
||||
.getLocation());
|
||||
System.out.printf("%d: done powering on vApp%n", System.currentTimeMillis());
|
||||
|
||||
vApp = tmClient.getVApp(vApp.getId()).get(45, TimeUnit.SECONDS);
|
||||
assertEquals(vApp.getStatus(), VAppStatus.ON);
|
||||
|
||||
|
@ -151,13 +163,7 @@ public class TerremarkVCloudClientLiveTest extends VCloudClientLiveTest {
|
|||
node = tmClient.addNode(is.getId(), vApp.getNetworkToAddresses().values().iterator().next(),
|
||||
vApp.getName() + "-SSH", 22).get(45, TimeUnit.SECONDS);
|
||||
publicIp = is.getPublicIpAddress().getAddress();
|
||||
try {
|
||||
doCheckPass(publicIp);
|
||||
} catch (Exception e) {
|
||||
// TODO - harden this up, when we stop hanging
|
||||
System.err.printf("%s:22 -> %s:22%n%s%n", vApp.getNetworkToAddresses().values().iterator()
|
||||
.next(), publicIp, e.getMessage());
|
||||
}
|
||||
doCheckPass(publicIp);
|
||||
}
|
||||
|
||||
@Test(dependsOnMethods = "testPublicIp")
|
||||
|
@ -178,15 +184,20 @@ public class TerremarkVCloudClientLiveTest extends VCloudClientLiveTest {
|
|||
assertEquals(((HttpResponseException) e.getCause()).getResponse().getStatusCode(), 501);
|
||||
}
|
||||
|
||||
blockUntilSuccess(tmClient.resetVApp(vApp.getId()).get(45, TimeUnit.SECONDS));
|
||||
assert successTester.apply(tmClient.resetVApp(vApp.getId()).get(45, TimeUnit.SECONDS)
|
||||
.getLocation());
|
||||
|
||||
vApp = tmClient.getVApp(vApp.getId()).get(45, TimeUnit.SECONDS);
|
||||
assertEquals(vApp.getStatus(), VAppStatus.ON);
|
||||
|
||||
tmClient.shutdownVApp(vApp.getId()).get(45, TimeUnit.SECONDS);
|
||||
vApp = tmClient.getVApp(vApp.getId()).get(45, TimeUnit.SECONDS);
|
||||
assertEquals(vApp.getStatus(), VAppStatus.ON);
|
||||
// TODO we need to determine whether shutdown is supported before invoking it.
|
||||
// tmClient.shutdownVApp(vApp.getId()).get(45, TimeUnit.SECONDS);
|
||||
// vApp = tmClient.getVApp(vApp.getId()).get(45, TimeUnit.SECONDS);
|
||||
// assertEquals(vApp.getStatus(), VAppStatus.ON);
|
||||
|
||||
assert successTester.apply(tmClient.powerOffVApp(vApp.getId()).get(45, TimeUnit.SECONDS)
|
||||
.getLocation());
|
||||
|
||||
blockUntilSuccess(tmClient.powerOffVApp(vApp.getId()).get(45, TimeUnit.SECONDS));
|
||||
vApp = tmClient.getVApp(vApp.getId()).get(45, TimeUnit.SECONDS);
|
||||
assertEquals(vApp.getStatus(), VAppStatus.OFF);
|
||||
}
|
||||
|
@ -208,18 +219,6 @@ public class TerremarkVCloudClientLiveTest extends VCloudClientLiveTest {
|
|||
.get(ResourceType.VIRTUAL_DISK).getVirtualQuantity());
|
||||
}
|
||||
|
||||
private Task blockUntilSuccess(Task task) throws InterruptedException, ExecutionException,
|
||||
TimeoutException {
|
||||
for (task = tmClient.getTask(task.getLocation()).get(30, TimeUnit.SECONDS); task.getStatus() != TaskStatus.SUCCESS; task = tmClient
|
||||
.getTask(task.getLocation()).get(30, TimeUnit.SECONDS)) {
|
||||
System.out.printf("%s blocking on status active: currently: %s%n", task.getOwner()
|
||||
.getName(), task.getStatus());
|
||||
Thread.sleep(5 * 1000);
|
||||
}
|
||||
System.out.printf("%s complete%n", task.getResult().getName());
|
||||
return task;
|
||||
}
|
||||
|
||||
private Task getLastTaskFor(URI owner) throws InterruptedException, ExecutionException,
|
||||
TimeoutException {
|
||||
return Iterables.getLast(tmClient.getDefaultTasksList().get(45, TimeUnit.SECONDS)
|
||||
|
@ -227,11 +226,17 @@ public class TerremarkVCloudClientLiveTest extends VCloudClientLiveTest {
|
|||
}
|
||||
|
||||
private void doCheckPass(InetAddress address) throws IOException {
|
||||
System.out.printf("%s:%s%n", address, 22);
|
||||
SshClient connection = sshFactory.create(new InetSocketAddress(address, 22), "vcloud",
|
||||
"p4ssw0rd");
|
||||
InetSocketAddress socket = new InetSocketAddress(address, 22);
|
||||
|
||||
System.out.printf("%d: %s awaiting ssh service to start%n", System.currentTimeMillis(),
|
||||
socket);
|
||||
assert socketTester.apply(socket);
|
||||
System.out.printf("%d: %s ssh service started%n", System.currentTimeMillis(), socket);
|
||||
|
||||
SshClient connection = sshFactory.create(socket, "vcloud", "p4ssw0rd");
|
||||
try {
|
||||
connection.connect();
|
||||
System.out.printf("%d: %s ssh connection made%n", System.currentTimeMillis(), socket);
|
||||
InputStream etcPasswd = connection.get("/etc/passwd");
|
||||
Utils.toStringAndClose(etcPasswd);
|
||||
} finally {
|
||||
|
@ -248,8 +253,10 @@ public class TerremarkVCloudClientLiveTest extends VCloudClientLiveTest {
|
|||
tmClient.deleteInternetService(is.getId()).get(30, TimeUnit.SECONDS);
|
||||
if (vApp != null) {
|
||||
try {
|
||||
blockUntilSuccess(tmClient.powerOffVApp(vApp.getId()).get(45, TimeUnit.SECONDS));
|
||||
successTester.apply(tmClient.powerOffVApp(vApp.getId()).get(45, TimeUnit.SECONDS)
|
||||
.getLocation());
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
tmClient.deleteVApp(vApp.getId()).get(45, TimeUnit.SECONDS);
|
||||
}
|
||||
|
@ -260,13 +267,19 @@ public class TerremarkVCloudClientLiveTest extends VCloudClientLiveTest {
|
|||
public void setupClient() {
|
||||
account = checkNotNull(System.getProperty("jclouds.test.user"), "jclouds.test.user");
|
||||
String key = checkNotNull(System.getProperty("jclouds.test.key"), "jclouds.test.key");
|
||||
connection = tmClient = new TerremarkVCloudContextBuilder(
|
||||
new TerremarkVCloudPropertiesBuilder(account, key).build()).withModules(
|
||||
new Log4JLoggingModule()).buildContext().getApi();
|
||||
Injector injector = Guice.createInjector(new Log4JLoggingModule(), new JschSshClientModule(),
|
||||
new ExecutorServiceModule(new WithinThreadExecutorService()));
|
||||
sshFactory = injector.getInstance(SshClient.Factory.class);
|
||||
Injector injector = new TerremarkVCloudContextBuilder(new TerremarkVCloudPropertiesBuilder(
|
||||
account, key).build()).withModules(new Log4JLoggingModule(),
|
||||
new JschSshClientModule()).buildInjector();
|
||||
|
||||
connection = tmClient = injector.getInstance(TerremarkVCloudClient.class);
|
||||
|
||||
sshFactory = injector.getInstance(SshClient.Factory.class);
|
||||
socketTester = new RetryablePredicate<InetSocketAddress>(injector
|
||||
.getInstance(SocketOpen.class), 130, 10, TimeUnit.SECONDS);// make it longer then
|
||||
// default internet
|
||||
// service timeout
|
||||
successTester = new RetryablePredicate<URI>(injector.getInstance(TaskSuccess.class), 300, 10,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -78,13 +78,16 @@ public class BindInstantiateVAppTemplateParamsToXmlEntityTest {
|
|||
|
||||
Map<String, String> map = Maps.newHashMap();
|
||||
map.put("name", "name");
|
||||
map.put("password", "password");
|
||||
map.put("row", "row");
|
||||
map.put("group", "group");
|
||||
map.put("template", "http://catalogItem/3");
|
||||
map.put("count", "1");
|
||||
map.put("megabytes", "512");
|
||||
map.put("network", "http://network");
|
||||
binder.bindToRequest(request, map);
|
||||
assertEquals(request.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE), "application/unknown");
|
||||
assertEquals(request.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH), "2247");
|
||||
assertEquals(request.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH), "2239");
|
||||
assertEquals(request.getEntity(), expected);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package org.jclouds.vcloud.terremark.options;
|
||||
|
||||
import static org.jclouds.vcloud.terremark.options.InstantiateVAppTemplateOptions.Builder.cpuCount;
|
||||
import static org.jclouds.vcloud.terremark.options.InstantiateVAppTemplateOptions.Builder.inGroup;
|
||||
import static org.jclouds.vcloud.terremark.options.InstantiateVAppTemplateOptions.Builder.inNetwork;
|
||||
import static org.jclouds.vcloud.terremark.options.InstantiateVAppTemplateOptions.Builder.inRow;
|
||||
import static org.jclouds.vcloud.terremark.options.InstantiateVAppTemplateOptions.Builder.megabytes;
|
||||
import static org.jclouds.vcloud.terremark.options.InstantiateVAppTemplateOptions.Builder.withPassword;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import java.net.URI;
|
||||
|
@ -23,6 +26,63 @@ public class InstantiateVAppTemplateOptionsTest {
|
|||
|
||||
Injector injector = Guice.createInjector(new ParserModule());
|
||||
|
||||
@Test
|
||||
public void testInGroupDefault() {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
assertEquals(options.group, "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInGroup() {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
options.inGroup("group1");
|
||||
assertEquals(options.group, "group1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInGroupStatic() {
|
||||
InstantiateVAppTemplateOptions options = inGroup("group1");
|
||||
assertEquals(options.group, "group1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInRowDefault() {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
assertEquals(options.row, "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInRow() {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
options.inRow("row1");
|
||||
assertEquals(options.row, "row1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInRowStatic() {
|
||||
InstantiateVAppTemplateOptions options = inRow("row1");
|
||||
assertEquals(options.row, "row1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithPasswordDefault() {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
assertEquals(options.password, "password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithPassword() {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
options.withPassword("password1");
|
||||
assertEquals(options.password, "password1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithPasswordStatic() {
|
||||
InstantiateVAppTemplateOptions options = withPassword("password1");
|
||||
assertEquals(options.password, "password1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInNetwork() {
|
||||
InstantiateVAppTemplateOptions options = new InstantiateVAppTemplateOptions();
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<ProductSection xmlns:q1="http://www.vmware.com/vcloud/v1"
|
||||
xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1">
|
||||
<Property xmlns="http://schemas.dmtf.org/ovf/envelope/1"
|
||||
ovf:key="password" ovf:value="secretPassword" />
|
||||
ovf:key="password" ovf:value="password" />
|
||||
<Property xmlns="http://schemas.dmtf.org/ovf/envelope/1"
|
||||
ovf:key="row" ovf:value="Row1" />
|
||||
ovf:key="row" ovf:value="row" />
|
||||
<Property xmlns="http://schemas.dmtf.org/ovf/envelope/1"
|
||||
ovf:key="group" ovf:value="Group1" />
|
||||
ovf:key="group" ovf:value="group" />
|
||||
</ProductSection>
|
||||
<VirtualHardwareSection xmlns:q1="http://www.vmware.com/vcloud/v1">
|
||||
<Item xmlns="http://schemas.dmtf.org/ovf/envelope/1">
|
||||
|
|
Loading…
Reference in New Issue