diff --git a/core/src/main/resources/rest.properties b/core/src/main/resources/rest.properties
index bd30efe6a3..6df8b026b5 100644
--- a/core/src/main/resources/rest.properties
+++ b/core/src/main/resources/rest.properties
@@ -74,6 +74,9 @@ eucalyptus-partnercloud-ec2.propertiesbuilder=org.jclouds.epc.EucalyptusPartnerC
nova.contextbuilder=org.jclouds.openstack.nova.NovaContextBuilder
nova.propertiesbuilder=org.jclouds.openstack.nova.NovaPropertiesBuilder
+openstack-nova.contextbuilder=org.jclouds.openstack.nova.v1_1.NovaContextBuilder
+openstack-nova.propertiesbuilder=org.jclouds.openstack.nova.v1_1.NovaPropertiesBuilder
+
cloudservers.contextbuilder=org.jclouds.cloudservers.CloudServersContextBuilder
cloudservers.propertiesbuilder=org.jclouds.cloudservers.CloudServersPropertiesBuilder
diff --git a/sandbox-apis/openstack-nova/pom.xml b/sandbox-apis/openstack-nova/pom.xml
new file mode 100644
index 0000000000..7c912a10fd
--- /dev/null
+++ b/sandbox-apis/openstack-nova/pom.xml
@@ -0,0 +1,152 @@
+
+
+
+ 4.0.0
+
+ org.jclouds
+ jclouds-project
+ 1.3.0-SNAPSHOT
+ ../../project/pom.xml
+
+ org.jclouds.api
+ openstack-nova
+ jcloud openstack-nova api
+ jclouds components to access an implementation of OpenStack Nova
+ bundle
+
+
+ http://localhost:8774/v1.1/
+ v1.1
+ FIXME_IDENTITY
+ FIXME_CREDENTIALS
+
+
+
+
+
+
+
+
+
+ org.jclouds.common
+ openstack-common
+ ${project.version}
+
+
+ org.jclouds
+ jclouds-compute
+ ${project.version}
+
+
+ org.jclouds
+ jclouds-core
+ ${project.version}
+ test-jar
+ test
+
+
+ org.jclouds.common
+ openstack-common
+ ${project.version}
+ test-jar
+ test
+
+
+ org.jclouds
+ jclouds-compute
+ ${project.version}
+ test-jar
+ test
+
+
+ org.jclouds.driver
+ jclouds-sshj
+ ${project.version}
+ test
+
+
+ org.jclouds.driver
+ jclouds-log4j
+ ${project.version}
+ test
+
+
+
+
+
+ live
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ org.apache.maven.surefire
+ surefire-testng
+
+
+
+
+ integration
+ integration-test
+
+ test
+
+
+
+ ${test.openstack-nova.endpoint}
+ ${test.openstack-nova.apiversion}
+ ${test.openstack-nova.identity}
+ ${test.openstack-nova.credential}
+ ${test.openstack-nova.image-id}
+ ${test.openstack-nova.image.login-user}
+ ${test.openstack-nova.image.authenticate-sudo}
+ ${test.ssh.keyfile.public}
+ ${test.ssh.keyfile.private}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+ ${project.artifactId}
+ org.jclouds.openstack.nova.v1_1.*;version="${project.version}"
+ org.jclouds.*;version="${project.version}",*
+
+
+
+
+
+
+
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java
new file mode 100644
index 0000000000..41e8769d4d
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1;
+
+import org.jclouds.openstack.nova.v1_1.features.ServerAsyncClient;
+import org.jclouds.rest.annotations.Delegate;
+
+/**
+ * Provides asynchronous access to Nova via their REST API.
+ *
+ *
+ * @see NovaClient
+ * @see
+ * @author Adrian Cole
+ */
+public interface NovaAsyncClient {
+
+ /**
+ * Provides asynchronous access to Server features.
+ */
+ @Delegate
+ ServerAsyncClient getServerClient();
+
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java
new file mode 100644
index 0000000000..e67b9de840
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1;
+
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.concurrent.Timeout;
+import org.jclouds.openstack.nova.v1_1.features.ServerClient;
+import org.jclouds.rest.annotations.Delegate;
+
+/**
+ * Provides synchronous access to Nova.
+ *
+ *
+ * @see NovaAsyncClient
+ * @see
+ * @author Adrian Cole
+ */
+@Timeout(duration = 60, timeUnit = TimeUnit.SECONDS)
+public interface NovaClient {
+
+ /**
+ * Provides synchronous access to Server features.
+ */
+ @Delegate
+ ServerClient getServerClient();
+
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaContextBuilder.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaContextBuilder.java
new file mode 100644
index 0000000000..867d84fef9
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaContextBuilder.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1;
+
+import java.util.List;
+import java.util.Properties;
+
+import org.jclouds.openstack.nova.v1_1.config.NovaRestClientModule;
+import org.jclouds.rest.RestContextBuilder;
+
+import com.google.inject.Module;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+public class NovaContextBuilder extends RestContextBuilder {
+
+ public NovaContextBuilder(Properties props) {
+ super(NovaClient.class, NovaAsyncClient.class, props);
+ }
+
+ @Override
+ protected void addClientModule(List modules) {
+ modules.add(new NovaRestClientModule());
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaPropertiesBuilder.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaPropertiesBuilder.java
new file mode 100644
index 0000000000..6c744a9654
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaPropertiesBuilder.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1;
+
+import static org.jclouds.Constants.PROPERTY_API_VERSION;
+import static org.jclouds.Constants.PROPERTY_ENDPOINT;
+import static org.jclouds.Constants.PROPERTY_IDENTITY;
+
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import org.jclouds.PropertiesBuilder;
+import org.jclouds.util.Strings2;
+
+/**
+ * Builds properties used in Nova Clients
+ *
+ * @author Adrian Cole
+ */
+public class NovaPropertiesBuilder extends PropertiesBuilder {
+ @Override
+ protected Properties defaultProperties() {
+ Properties properties = super.defaultProperties();
+ properties.setProperty(PROPERTY_ENDPOINT, "http://localhost:8774/{apiversion}/{identity}");
+ properties.setProperty(PROPERTY_API_VERSION, "v1.1");
+ return properties;
+ }
+
+ public NovaPropertiesBuilder(Properties properties) {
+ super(properties);
+ }
+
+ public static final Pattern IDENTITY_PATTERN = Pattern.compile("\\{identity\\}");
+ public static final Pattern API_VERSION_PATTERN = Pattern.compile("\\{apiversion\\}");
+
+ @Override
+ public Properties build() {
+ // TODO determine if we can more elegantly do this. right now we have to
+ // because URI.create() doesn't allow us to build a URi with an illegal
+ // character '{'
+ String endpoint = properties.getProperty(PROPERTY_ENDPOINT);
+ String identity = properties.getProperty(PROPERTY_IDENTITY);
+ String apiVersion = properties.getProperty(PROPERTY_API_VERSION);
+ String withIdentity = Strings2.replaceAll(endpoint, IDENTITY_PATTERN, identity);
+ String withVersion = Strings2.replaceAll(withIdentity, API_VERSION_PATTERN, apiVersion);
+ properties.setProperty(PROPERTY_ENDPOINT, withVersion);
+ return super.build();
+ }
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaParserModule.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaParserModule.java
new file mode 100644
index 0000000000..3978500918
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaParserModule.java
@@ -0,0 +1,50 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.config;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import javax.inject.Singleton;
+
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.json.config.GsonModule.DateAdapter;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+/**
+ * @author Adrian Cole
+ */
+public class NovaParserModule extends AbstractModule {
+
+ @Provides
+ @Singleton
+ public Map provideCustomAdapterBindings() {
+ return ImmutableMap.of(
+ );
+ }
+
+ @Override
+ protected void configure() {
+ bind(DateAdapter.class).to(GsonModule.Iso8601DateAdapter.class);
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java
new file mode 100644
index 0000000000..4ec78a195c
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java
@@ -0,0 +1,77 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.config;
+
+import java.util.Map;
+
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpRetryHandler;
+import org.jclouds.http.RequiresHttp;
+import org.jclouds.http.annotation.ClientError;
+import org.jclouds.http.annotation.Redirection;
+import org.jclouds.http.annotation.ServerError;
+import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
+import org.jclouds.openstack.config.OpenStackAuthenticationModule;
+import org.jclouds.openstack.nova.v1_1.NovaAsyncClient;
+import org.jclouds.openstack.nova.v1_1.NovaClient;
+import org.jclouds.openstack.nova.v1_1.features.ServerAsyncClient;
+import org.jclouds.openstack.nova.v1_1.features.ServerClient;
+import org.jclouds.openstack.nova.v1_1.handlers.NovaErrorHandler;
+import org.jclouds.rest.ConfiguresRestClient;
+import org.jclouds.rest.config.RestClientModule;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Configures the Nova connection.
+ *
+ * @author Adrian Cole
+ */
+@RequiresHttp
+@ConfiguresRestClient
+public class NovaRestClientModule extends RestClientModule {
+
+ public static final Map, Class>> DELEGATE_MAP = ImmutableMap., Class>> builder()//
+ .put(ServerClient.class, ServerAsyncClient.class)//
+ .build();
+
+ public NovaRestClientModule() {
+ super(NovaClient.class, NovaAsyncClient.class, DELEGATE_MAP);
+ }
+
+ @Override
+ protected void configure() {
+ install(new OpenStackAuthenticationModule());
+ install(new NovaParserModule());
+ super.configure();
+ }
+
+ @Override
+ protected void bindErrorHandlers() {
+ bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(NovaErrorHandler.class);
+ bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(NovaErrorHandler.class);
+ bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(NovaErrorHandler.class);
+ }
+
+ @Override
+ protected void bindRetryHandlers() {
+ bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(BackoffLimitedRetryHandler.class);
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java
new file mode 100644
index 0000000000..191a0aad17
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java
@@ -0,0 +1,160 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Listing of a server.
+ *
+ * @author Adrian Cole
+ * @see
+ */
+public class Server {
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ protected String id;
+ protected String hostname;
+ protected String datacenter;
+ protected String platform;
+
+ public Builder id(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder hostname(String hostname) {
+ this.hostname = hostname;
+ return this;
+ }
+
+ public Builder datacenter(String datacenter) {
+ this.datacenter = datacenter;
+ return this;
+ }
+
+ public Builder platform(String platform) {
+ this.platform = platform;
+ return this;
+ }
+
+ public Server build() {
+ return new Server(id, hostname, datacenter, platform);
+ }
+
+ public Builder fromServer(Server in) {
+ return datacenter(in.getDatacenter()).platform(in.getPlatform()).hostname(in.getHostname()).id(in.getId());
+ }
+ }
+
+ @SerializedName("serverid")
+ protected final String id;
+ protected final String hostname;
+ protected final String datacenter;
+ protected final String platform;
+
+ public Server(String id, String hostname, String datacenter, String platform) {
+ this.id = checkNotNull(id, "id");
+ this.hostname = checkNotNull(hostname, "hostname");
+ this.datacenter = checkNotNull(datacenter, "datacenter");
+ this.platform = checkNotNull(platform, "platform");
+ }
+
+ /**
+ * @return the generated id of the server
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @return the hostname of the server
+ */
+ public String getHostname() {
+ return hostname;
+ }
+
+ /**
+ * @return platform running the server (ex. {@code OpenVZ})
+ */
+ public String getPlatform() {
+ return platform;
+ }
+
+ /**
+ * @return the datacenter the server exists in (ex. {@code Falkenberg})
+ */
+ public String getDatacenter() {
+ return datacenter;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((datacenter == null) ? 0 : datacenter.hashCode());
+ result = prime * result + ((hostname == null) ? 0 : hostname.hashCode());
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ result = prime * result + ((platform == null) ? 0 : platform.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Server other = (Server) obj;
+ if (datacenter == null) {
+ if (other.datacenter != null)
+ return false;
+ } else if (!datacenter.equals(other.datacenter))
+ return false;
+ if (hostname == null) {
+ if (other.hostname != null)
+ return false;
+ } else if (!hostname.equals(other.hostname))
+ return false;
+ if (id == null) {
+ if (other.id != null)
+ return false;
+ } else if (!id.equals(other.id))
+ return false;
+ if (platform == null) {
+ if (other.platform != null)
+ return false;
+ } else if (!platform.equals(other.platform))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("[id=%s, hostname=%s, datacenter=%s, platform=%s]", id, hostname, datacenter, platform);
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerDetails.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerDetails.java
new file mode 100644
index 0000000000..9ee8846604
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerDetails.java
@@ -0,0 +1,150 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Detailed information about a server such as cpuCores, hardware configuration
+ * (cpu, memory and disk), ip adresses, cost, transfer, os and more.
+ *
+ * @author Adrian Cole
+ * @see
+ */
+public class ServerDetails extends Server {
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder extends Server.Builder {
+ private String description;
+ private int cpuCores;
+ private int memory;
+ private int disk;
+
+ public Builder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Builder cpuCores(int cpuCores) {
+ this.cpuCores = cpuCores;
+ return this;
+ }
+
+ public Builder memory(int memory) {
+ this.memory = memory;
+ return this;
+ }
+
+ public Builder disk(int disk) {
+ this.disk = disk;
+ return this;
+ }
+
+ public ServerDetails build() {
+ return new ServerDetails(id, hostname, datacenter, platform, description, cpuCores, memory, disk);
+ }
+
+ public Builder fromServerDetails(ServerDetails in) {
+ return fromServer(in).memory(in.getMemory()).disk(in.getDisk()).cpuCores(in.getCpuCores())
+ .description(in.getDescription());
+ }
+
+ @Override
+ public Builder id(String id) {
+ return Builder.class.cast(super.id(id));
+ }
+
+ @Override
+ public Builder hostname(String hostname) {
+ return Builder.class.cast(super.hostname(hostname));
+ }
+
+ @Override
+ public Builder datacenter(String datacenter) {
+ return Builder.class.cast(super.datacenter(datacenter));
+ }
+
+ @Override
+ public Builder platform(String platform) {
+ return Builder.class.cast(super.platform(platform));
+ }
+
+ @Override
+ public Builder fromServer(Server in) {
+ return Builder.class.cast(super.fromServer(in));
+ }
+ }
+
+ private final String description;
+ @SerializedName("cpucores")
+ private final int cpuCores;
+ private final int memory;
+ private final int disk;
+
+ public ServerDetails(String id, String hostname, String datacenter, String platform, String description,
+ int cpuCores, int memory, int disk) {
+ super(id, hostname, datacenter, platform);
+ this.description = checkNotNull(description, "description");
+ this.cpuCores = cpuCores;
+ this.memory = memory;
+ this.disk = disk;
+ }
+
+ /**
+ * @return the user-specified description of the server
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * @return number of cores on the server
+ */
+ public int getCpuCores() {
+ return cpuCores;
+ }
+
+ /**
+ * @return the disk of the server in GB
+ */
+ public int getDisk() {
+ return disk;
+ }
+
+ /**
+ * @return the memory of the server in MB
+ */
+ public int getMemory() {
+ return memory;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "[id=%s, hostname=%s, datacenter=%s, platform=%s, description=%s, cpuCores=%s, memory=%s, disk=%s]", id,
+ hostname, datacenter, platform, description, cpuCores, memory, disk);
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java
new file mode 100644
index 0000000000..4629e1ca0e
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.features;
+
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.openstack.filters.AuthenticateRequest;
+import org.jclouds.openstack.nova.v1_1.domain.Server;
+import org.jclouds.openstack.nova.v1_1.domain.ServerDetails;
+import org.jclouds.rest.annotations.ExceptionParser;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404;
+import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Provides asynchronous access to Server via their REST API.
+ *
+ *
+ * @see ServerClient
+ * @see
+ * @author Adrian Cole
+ */
+@RequestFilters(AuthenticateRequest.class)
+public interface ServerAsyncClient {
+
+ /**
+ * @see ServerClient#listServers
+ */
+ @GET
+ @SelectJson("servers")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/servers")
+ @ExceptionParser(ReturnEmptySetOnNotFoundOr404.class)
+ ListenableFuture> listServers();
+
+ /**
+ * @see ServerClient#getServerDetails
+ */
+ @GET
+ @SelectJson("server")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Path("/servers/{id}")
+ @ExceptionParser(ReturnNullOnNotFoundOr404.class)
+ ListenableFuture getServerDetails(@PathParam("id") String id);
+
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java
new file mode 100644
index 0000000000..9daacff4f3
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.features;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.concurrent.Timeout;
+import org.jclouds.openstack.nova.v1_1.domain.Server;
+import org.jclouds.openstack.nova.v1_1.domain.ServerDetails;
+
+/**
+ * Provides synchronous access to Server.
+ *
+ *
+ * @see ServerAsyncClient
+ * @see
+ * @author Adrian Cole
+ */
+@Timeout(duration = 30, timeUnit = TimeUnit.SECONDS)
+public interface ServerClient {
+
+ /**
+ * Get a list of all servers on this account.
+ *
+ * @return an account's associated server objects.
+ */
+ Set listServers();
+
+ /**
+ * Get detailed information about a server such as hostname, hardware
+ * configuration (cpu, memory and disk), ip adresses, cost, transfer, os and
+ * more.
+ *
+ * @param id
+ * id of the server
+ * @return server or null if not found
+ */
+ ServerDetails getServerDetails(String id);
+
+
+}
diff --git a/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java
new file mode 100644
index 0000000000..943a5dad47
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/handlers/NovaErrorHandler.java
@@ -0,0 +1,90 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.handlers;
+
+import java.io.IOException;
+
+import javax.inject.Singleton;
+
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.util.Strings2;
+
+import com.google.common.base.Throwables;
+import com.google.common.io.Closeables;
+
+/**
+ * This will parse and set an appropriate exception on the command object.
+ *
+ * @author Adrian Cole
+ *
+ */
+@Singleton
+public class NovaErrorHandler implements HttpErrorHandler {
+
+ public void handleError(HttpCommand command, HttpResponse response) {
+ // it is important to always read fully and close streams
+ String message = parseMessage(response);
+ Exception exception = message != null ? new HttpResponseException(command, response, message)
+ : new HttpResponseException(command, response);
+ try {
+ message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(),
+ response.getStatusLine());
+ switch (response.getStatusCode()) {
+ case 401:
+ case 403:
+ exception = new AuthorizationException(message, exception);
+ break;
+ case 404:
+ if (!command.getCurrentRequest().getMethod().equals("DELETE")) {
+ exception = new ResourceNotFoundException(message, exception);
+ }
+ break;
+ case 500:
+ if (message != null && message.indexOf("Unable to determine package for") != -1) {
+ exception = new ResourceNotFoundException(message, exception);
+ }
+ }
+ } finally {
+ if (response.getPayload() != null)
+ Closeables.closeQuietly(response.getPayload().getInput());
+ command.setException(exception);
+ }
+ }
+
+ public String parseMessage(HttpResponse response) {
+ if (response.getPayload() == null)
+ return null;
+ try {
+ return Strings2.toStringAndClose(response.getPayload().getInput());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ response.getPayload().getInput().close();
+ } catch (IOException e) {
+ Throwables.propagate(e);
+ }
+ }
+ }
+}
diff --git a/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClientTest.java b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClientTest.java
new file mode 100644
index 0000000000..69cd52b395
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClientTest.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.openstack.nova.v1_1.features.BaseNovaAsyncClientTest;
+import org.jclouds.rest.internal.RestAnnotationProcessor;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests behavior of {@code NovaAsyncClient}
+ *
+ * @author Adrian Cole
+ */
+// NOTE:without testName, this will not call @Before* and fail w/NPE during
+// surefire
+@Test(groups = "unit", testName = "NovaAsyncClientTest")
+public class NovaAsyncClientTest extends BaseNovaAsyncClientTest {
+
+ private NovaAsyncClient asyncClient;
+ private NovaClient syncClient;
+
+ public void testSync() throws SecurityException, NoSuchMethodException, InterruptedException, ExecutionException {
+ assert syncClient.getServerClient() != null;
+ }
+
+ public void testAsync() throws SecurityException, NoSuchMethodException, InterruptedException, ExecutionException {
+ assert asyncClient.getServerClient() != null;
+ }
+
+ @Override
+ protected TypeLiteral> createTypeLiteral() {
+ return new TypeLiteral>() {
+ };
+ }
+
+ @BeforeClass
+ @Override
+ protected void setupFactory() throws IOException {
+ super.setupFactory();
+ asyncClient = injector.getInstance(NovaAsyncClient.class);
+ syncClient = injector.getInstance(NovaClient.class);
+ }
+
+ @Override
+ protected void checkFilters(HttpRequest request) {
+
+ }
+}
diff --git a/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java
new file mode 100644
index 0000000000..94516d7afc
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/NovaErrorHandlerTest.java
@@ -0,0 +1,117 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.reportMatcher;
+import static org.easymock.classextension.EasyMock.createMock;
+import static org.easymock.classextension.EasyMock.replay;
+import static org.easymock.classextension.EasyMock.verify;
+
+import java.net.URI;
+
+import org.easymock.IArgumentMatcher;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.io.Payloads;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.ResourceNotFoundException;
+import org.jclouds.openstack.nova.v1_1.handlers.NovaErrorHandler;
+import org.jclouds.util.Strings2;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = { "unit" })
+public class NovaErrorHandlerTest {
+
+ @Test
+ public void test500MakesResourceNotFoundExceptionOnUnableToDeterminePackage() {
+ assertCodeMakes(
+ "GET",
+ URI.create("https://api.openstack.nova.v1_1.com/foo"),
+ 500,
+ "",
+ "{\"error\":\"Unable to determine package for 'node2102835255.me.org'.\"}",
+ ResourceNotFoundException.class);
+ }
+
+ @Test
+ public void test401MakesAuthorizationException() {
+ assertCodeMakes("GET", URI.create("https://api.openstack.nova.v1_1.com/foo"), 401, "", "Unauthorized",
+ AuthorizationException.class);
+ }
+
+ @Test
+ public void test404MakesResourceNotFoundException() {
+ assertCodeMakes("GET", URI.create("https://api.openstack.nova.v1_1.com/foo"), 404, "", "Not Found",
+ ResourceNotFoundException.class);
+ }
+
+ private void assertCodeMakes(String method, URI uri, int statusCode, String message, String content,
+ Class extends Exception> expected) {
+ assertCodeMakes(method, uri, statusCode, message, "text/xml", content, expected);
+ }
+
+ private void assertCodeMakes(String method, URI uri, int statusCode, String message, String contentType,
+ String content, Class extends Exception> expected) {
+
+ NovaErrorHandler function = Guice.createInjector().getInstance(NovaErrorHandler.class);
+
+ HttpCommand command = createMock(HttpCommand.class);
+ HttpRequest request = new HttpRequest(method, uri);
+ HttpResponse response = new HttpResponse(statusCode, message, Payloads.newInputStreamPayload(Strings2
+ .toInputStream(content)));
+ response.getPayload().getContentMetadata().setContentType(contentType);
+
+ expect(command.getCurrentRequest()).andReturn(request).atLeastOnce();
+ command.setException(classEq(expected));
+
+ replay(command);
+
+ function.handleError(command, response);
+
+ verify(command);
+ }
+
+ public static Exception classEq(final Class extends Exception> in) {
+ reportMatcher(new IArgumentMatcher() {
+
+ @Override
+ public void appendTo(StringBuffer buffer) {
+ buffer.append("classEq(");
+ buffer.append(in);
+ buffer.append(")");
+ }
+
+ @Override
+ public boolean matches(Object arg) {
+ return arg.getClass() == in;
+ }
+
+ });
+ return null;
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/BaseNovaAsyncClientTest.java b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/BaseNovaAsyncClientTest.java
new file mode 100644
index 0000000000..0a1de0f22a
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/BaseNovaAsyncClientTest.java
@@ -0,0 +1,50 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.features;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Properties;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.openstack.filters.AuthenticateRequest;
+import org.jclouds.openstack.nova.v1_1.NovaAsyncClient;
+import org.jclouds.openstack.nova.v1_1.NovaClient;
+import org.jclouds.rest.RestClientTest;
+import org.jclouds.rest.RestContextFactory;
+import org.jclouds.rest.RestContextSpec;
+
+/**
+ * @author Adrian Cole
+ */
+public abstract class BaseNovaAsyncClientTest extends RestClientTest {
+
+ @Override
+ protected void checkFilters(HttpRequest request) {
+ assertEquals(request.getFilters().size(), 1);
+ assertEquals(request.getFilters().get(0).getClass(), AuthenticateRequest.class);
+ }
+
+ @Override
+ public RestContextSpec createContextSpec() {
+ Properties props = new Properties();
+ return new RestContextFactory().createContextSpec("openstack-nova", "accountId", "accessKey", props);
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/BaseNovaClientLiveTest.java b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/BaseNovaClientLiveTest.java
new file mode 100644
index 0000000000..b83c4efbf3
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/BaseNovaClientLiveTest.java
@@ -0,0 +1,62 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.features;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.openstack.nova.v1_1.NovaAsyncClient;
+import org.jclouds.openstack.nova.v1_1.NovaClient;
+import org.jclouds.logging.log4j.config.Log4JLoggingModule;
+import org.jclouds.rest.RestContext;
+import org.jclouds.rest.RestContextFactory;
+import org.jclouds.sshj.config.SshjSshClientModule;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+/**
+ * Tests behavior of {@code NovaClient}
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "live")
+public class BaseNovaClientLiveTest {
+
+ protected RestContext context;
+
+ @BeforeGroups(groups = { "live" })
+ public void setupClient() {
+ String identity = checkNotNull(System.getProperty("test.openstack.nova.v1_1.identity"), "test.openstack.nova.v1_1.identity");
+ String credential = checkNotNull(System.getProperty("test.openstack.nova.v1_1.credential"), "test.openstack.nova.v1_1.credential");
+
+ context = new RestContextFactory().createContext("openstack-nova", identity, credential,
+ ImmutableSet. of(new Log4JLoggingModule(), new SshjSshClientModule()));
+
+ }
+
+ @AfterGroups(groups = "live")
+ protected void tearDown() {
+ if (context != null)
+ context.close();
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClientTest.java b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClientTest.java
new file mode 100644
index 0000000000..b7c18c3798
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClientTest.java
@@ -0,0 +1,79 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.features;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.functions.ParseFirstJsonValueNamed;
+import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404;
+import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
+import org.jclouds.rest.internal.RestAnnotationProcessor;
+import org.testng.annotations.Test;
+
+import com.google.inject.TypeLiteral;
+
+/**
+ * Tests annotation parsing of {@code ServerAsyncClient}
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "ServerAsyncClientTest")
+public class ServerAsyncClientTest extends BaseNovaAsyncClientTest {
+
+ public void testListServers() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = ServerAsyncClient.class.getMethod("listServers");
+ HttpRequest httpRequest = processor.createRequest(method);
+
+ assertRequestLineEquals(httpRequest, "GET http://localhost:8774/v1.1/accountId/servers HTTP/1.1");
+ assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\n");
+ assertPayloadEquals(httpRequest, null, null, false);
+
+ assertResponseParserClassEquals(method, httpRequest, ParseFirstJsonValueNamed.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, ReturnEmptySetOnNotFoundOr404.class);
+
+ checkFilters(httpRequest);
+
+ }
+
+ public void testGetServer() throws SecurityException, NoSuchMethodException, IOException {
+ Method method = ServerAsyncClient.class.getMethod("getServerDetails", String.class);
+ HttpRequest httpRequest = processor.createRequest(method, "abcd");
+
+ assertRequestLineEquals(httpRequest,
+ "GET http://localhost:8774/v1.1/accountId/servers/abcd HTTP/1.1");
+ assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\n");
+ assertPayloadEquals(httpRequest, null, null, false);
+
+ assertResponseParserClassEquals(method, httpRequest, ParseFirstJsonValueNamed.class);
+ assertSaxResponseParserClassEquals(method, null);
+ assertExceptionParserClassEquals(method, ReturnNullOnNotFoundOr404.class);
+
+ checkFilters(httpRequest);
+
+ }
+
+ @Override
+ protected TypeLiteral> createTypeLiteral() {
+ return new TypeLiteral>() {
+ };
+ }
+}
diff --git a/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientLiveTest.java b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientLiveTest.java
new file mode 100644
index 0000000000..b5fc287f9e
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientLiveTest.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.jclouds.openstack.nova.v1_1.domain.Server;
+import org.jclouds.openstack.nova.v1_1.domain.ServerDetails;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.Test;
+
+/**
+ * Tests behavior of {@code ServerClient}
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "live", testName = "ServerClientLiveTest")
+public class ServerClientLiveTest extends BaseNovaClientLiveTest {
+
+ @BeforeGroups(groups = {"live"})
+ public void setupClient() {
+ super.setupClient();
+ client = context.getApi().getServerClient();
+ }
+
+ private ServerClient client;
+
+ @Test
+ public void testListServers() throws Exception {
+ Set response = client.listServers();
+ assert null != response;
+ assertTrue(response.size() >= 0);
+ for (Server server : response) {
+ ServerDetails newDetails = client.getServerDetails(server.getId());
+ assertEquals(newDetails.getId(), server.getId());
+ assertEquals(newDetails.getHostname(), server.getHostname());
+ assertEquals(newDetails.getPlatform(), server.getPlatform());
+ assertEquals(newDetails.getDatacenter(), server.getDatacenter());
+ checkServer(newDetails);
+ }
+ }
+
+ private void checkServer(ServerDetails server) {
+ // description can be null
+ assert server.getCpuCores() > 0 : server;
+ assert server.getDisk() > 0 : server;
+ assert server.getMemory() > 0 : server;
+ }
+}
diff --git a/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerDetailsTest.java b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerDetailsTest.java
new file mode 100644
index 0000000000..88e27b5aa0
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerDetailsTest.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.parse;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.json.BaseItemParserTest;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.openstack.nova.v1_1.config.NovaParserModule;
+import org.jclouds.openstack.nova.v1_1.domain.ServerDetails;
+import org.jclouds.rest.annotations.SelectJson;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "ParseServerDetailsTest")
+public class ParseServerDetailsTest extends BaseItemParserTest {
+
+ @Override
+ public String resource() {
+ return "/server.json";
+ }
+
+ @Override
+ @SelectJson("server")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public ServerDetails expected() {
+ return ServerDetails.builder().id("vz1541880").hostname("mammamia").datacenter("Falkenberg").platform("OpenVZ")
+ .description("description").cpuCores(1).memory(128).disk(5).build();
+ }
+
+ protected Injector injector() {
+ return Guice.createInjector(new NovaParserModule(), new GsonModule());
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerListTest.java b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerListTest.java
new file mode 100644
index 0000000000..cf9d300b61
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/parse/ParseServerListTest.java
@@ -0,0 +1,61 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.openstack.nova.v1_1.parse;
+
+import java.util.Set;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.json.BaseSetParserTest;
+import org.jclouds.json.config.GsonModule;
+import org.jclouds.openstack.nova.v1_1.config.NovaParserModule;
+import org.jclouds.openstack.nova.v1_1.domain.Server;
+import org.jclouds.rest.annotations.SelectJson;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "ParseServerListTest")
+public class ParseServerListTest extends BaseSetParserTest {
+
+ @Override
+ public String resource() {
+ return "/server_list.json";
+ }
+
+ @Override
+ @SelectJson("servers")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Set expected() {
+ return ImmutableSet.of(Server.builder().id("vz1541880").hostname("mammamia").datacenter("Falkenberg")
+ .platform("OpenVZ").build());
+ }
+
+ protected Injector injector() {
+ return Guice.createInjector(new NovaParserModule(), new GsonModule());
+ }
+
+}
diff --git a/sandbox-apis/openstack-nova/src/test/resources/log4j.xml b/sandbox-apis/openstack-nova/src/test/resources/log4j.xml
new file mode 100644
index 0000000000..63810d3ca0
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/resources/log4j.xml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox-apis/openstack-nova/src/test/resources/server.json b/sandbox-apis/openstack-nova/src/test/resources/server.json
new file mode 100644
index 0000000000..83872c0fa8
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/resources/server.json
@@ -0,0 +1,54 @@
+{
+ "server": {
+ "status": "REBOOT",
+ "updated": "2011-12-06T22:37:44Z",
+ "hostId": "3852db942cbeb5e840d759de11bdef403e2a0149c66f06a5d7caa61e",
+ "user_id": "dev_41247879706381",
+ "name": "jd-test-tiny",
+ "links": [
+ {
+ "href": "http://se1.devex.me:8774/v1.1/dev_16767499955063/servers/804",
+ "rel": "self"
+ },
+ {
+ "href": "http://se1.devex.me:8774/dev_16767499955063/servers/804",
+ "rel": "bookmark"
+ }
+ ],
+ "addresses": {
+ "novanet_7": [
+ {
+ "version": 4,
+ "addr": "10.0.0.229"
+ }
+ ]
+ },
+ "tenant_id": "dev_16767499955063",
+ "image": {
+ "id": "146",
+ "links": [
+ {
+ "href": "http://se1.devex.me:8774/dev_16767499955063/images/146",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "created": "2011-12-02T16:04:54Z",
+ "uuid": "0bab236f-f5c4-46ef-93c2-a8e2bcfffff6",
+ "accessIPv4": "",
+ "accessIPv6": "",
+ "key_name": null,
+ "flavor": {
+ "id": "1",
+ "links": [
+ {
+ "href": "http://se1.devex.me:8774/dev_16767499955063/flavors/1",
+ "rel": "bookmark"
+ }
+ ]
+ },
+ "config_drive": "",
+ "id": 804,
+ "metadata": {}
+ }
+}
\ No newline at end of file
diff --git a/sandbox-apis/openstack-nova/src/test/resources/server_list.json b/sandbox-apis/openstack-nova/src/test/resources/server_list.json
new file mode 100644
index 0000000000..e6a7eec520
--- /dev/null
+++ b/sandbox-apis/openstack-nova/src/test/resources/server_list.json
@@ -0,0 +1,64 @@
+{
+ "servers": [
+ {
+ "uuid": "0bab236f-f5c4-46ef-93c2-a8e2bcfffff6",
+ "name": "jd-test-tiny",
+ "links": [
+ {
+ "href": "http://se1.devex.me:8774/v1.1/dev_16767499955063/servers/804",
+ "rel": "self"
+ },
+ {
+ "href": "http://se1.devex.me:8774/dev_16767499955063/servers/804",
+ "rel": "bookmark"
+ }
+ ],
+ "id": 804
+ },
+ {
+ "uuid": "488eac65-84ab-4f10-b2a4-555c23613428",
+ "name": "rgtest2",
+ "links": [
+ {
+ "href": "http://se1.devex.me:8774/v1.1/dev_16767499955063/servers/528",
+ "rel": "self"
+ },
+ {
+ "href": "http://se1.devex.me:8774/dev_16767499955063/servers/528",
+ "rel": "bookmark"
+ }
+ ],
+ "id": 528
+ },
+ {
+ "uuid": "df891742-4ecb-4f9f-b206-b2a4d821a100",
+ "name": "rgtest",
+ "links": [
+ {
+ "href": "http://se1.devex.me:8774/v1.1/dev_16767499955063/servers/507",
+ "rel": "self"
+ },
+ {
+ "href": "http://se1.devex.me:8774/dev_16767499955063/servers/507",
+ "rel": "bookmark"
+ }
+ ],
+ "id": 507
+ },
+ {
+ "uuid": "42cded93-1ade-4da2-b39f-70e6577c2b82",
+ "name": "fog_hprgmac.local_1320358100_server",
+ "links": [
+ {
+ "href": "http://se1.devex.me:8774/v1.1/dev_16767499955063/servers/430",
+ "rel": "self"
+ },
+ {
+ "href": "http://se1.devex.me:8774/dev_16767499955063/servers/430",
+ "rel": "bookmark"
+ }
+ ],
+ "id": 430
+ }
+ ]
+}