diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java index 3bb05e5b8f..9a1175b301 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaAsyncClient.java @@ -26,6 +26,8 @@ import org.jclouds.location.functions.RegionToEndpointOrProviderIfNull; import org.jclouds.openstack.nova.v1_1.features.FlavorClient; import org.jclouds.openstack.nova.v1_1.features.FloatingIPClient; import org.jclouds.openstack.nova.v1_1.features.ImageClient; +import org.jclouds.openstack.nova.v1_1.features.KeyPairClient; +import org.jclouds.openstack.nova.v1_1.features.SecurityGroupClient; import org.jclouds.openstack.nova.v1_1.features.ServerAsyncClient; import org.jclouds.rest.annotations.Delegate; import org.jclouds.rest.annotations.EndpointParam; @@ -77,4 +79,19 @@ public interface NovaAsyncClient { @Delegate FloatingIPClient getFloatingIPClientForRegion( @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); + + /** + * Provides asynchronous access to Security Group features. + */ + @Delegate + SecurityGroupClient getSecurityGroupClientForRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); + + /** + * Provides asynchronous access to Key Pair features. + */ + @Delegate + KeyPairClient getKeyPairClientForRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); + } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java index 49fa3c1096..3f2dab31d3 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/NovaClient.java @@ -28,6 +28,8 @@ import org.jclouds.location.functions.RegionToEndpointOrProviderIfNull; import org.jclouds.openstack.nova.v1_1.features.FlavorClient; import org.jclouds.openstack.nova.v1_1.features.FloatingIPClient; import org.jclouds.openstack.nova.v1_1.features.ImageClient; +import org.jclouds.openstack.nova.v1_1.features.KeyPairClient; +import org.jclouds.openstack.nova.v1_1.features.SecurityGroupClient; import org.jclouds.openstack.nova.v1_1.features.ServerClient; import org.jclouds.rest.annotations.Delegate; import org.jclouds.rest.annotations.EndpointParam; @@ -79,4 +81,19 @@ public interface NovaClient { @Delegate FloatingIPClient getFloatingIPClientForRegion( @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); + + /** + * Provides synchronous access to Security Group features. + */ + @Delegate + SecurityGroupClient getSecurityGroupClientForRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); + + /** + * Provides synchronous access to Key Pair features. + */ + @Delegate + KeyPairClient getKeyPairClientForRegion( + @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); + } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java index 44a89753ae..3e9ea93bac 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/config/NovaRestClientModule.java @@ -34,6 +34,10 @@ import org.jclouds.openstack.nova.v1_1.features.FloatingIPAsyncClient; import org.jclouds.openstack.nova.v1_1.features.FloatingIPClient; import org.jclouds.openstack.nova.v1_1.features.ImageAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ImageClient; +import org.jclouds.openstack.nova.v1_1.features.KeyPairAsyncClient; +import org.jclouds.openstack.nova.v1_1.features.KeyPairClient; +import org.jclouds.openstack.nova.v1_1.features.SecurityGroupAsyncClient; +import org.jclouds.openstack.nova.v1_1.features.SecurityGroupClient; 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; @@ -56,6 +60,8 @@ public class NovaRestClientModule extends RestClientModule metadata = Maps.newHashMap(); - public Builder status(ImageStatus status) { - this.status = status; - return this; - } - public Builder updated(Date updated) { this.updated = updated; return this; @@ -73,13 +72,38 @@ public class Image extends Resource { return this; } + public Builder tenantId(String tenantId) { + this.tenantId = tenantId; + return this; + } + + public Builder userId(String userId) { + this.userId = userId; + return this; + } + + public Builder status(ImageStatus status) { + this.status = status; + return this; + } + public Builder progress(int progress) { this.progress = progress; return this; } - public Builder serverRef(String serverRef) { - this.serverRef = serverRef; + public Builder minDisk(int minDisk) { + this.minDisk = minDisk; + return this; + } + + public Builder minRam(int minRam) { + this.minRam = minRam; + return this; + } + + public Builder server(Resource server) { + this.server = server; return this; } @@ -89,14 +113,14 @@ public class Image extends Resource { } public Image build() { - return new Image(id, name, links, status, updated, created, progress, - serverRef, metadata); + return new Image(id, name, links, updated, created, tenantId, userId, + status, progress, minDisk, minRam, server, metadata); } public Builder fromImage(Image in) { return fromResource(in).status(in.getStatus()) .updated(in.getUpdated()).created(in.getCreated()) - .progress(in.getProgress()).serverRef(in.getServerRef()) + .progress(in.getProgress()).server(in.getServer()) .metadata(in.getMetadata()); } @@ -133,29 +157,36 @@ public class Image extends Resource { } } - private ImageStatus status; private Date updated; private Date created; + @SerializedName("tenant_id") + private String tenantId; + @SerializedName("user_id") + private String userId; + private ImageStatus status; private int progress; - private String serverRef; + private int minDisk; + private int minRam; + private Resource server; private Map metadata = Maps.newHashMap(); - - protected Image(String id, String name, Set links, ImageStatus status, - Date updated, Date created, int progress, String serverRef, + + protected Image(String id, String name, Set links, Date updated, + Date created, String tenantId, String userId, ImageStatus status, + int progress, int minDisk, int minRam, Resource server, Map metadata) { super(id, name, links); - this.status = status; this.updated = updated; this.created = created; + this.tenantId = tenantId; + this.userId = userId; + this.status = status; this.progress = progress; - this.serverRef = serverRef; + this.minDisk = minDisk; + this.minRam = minRam; + this.server = server; this.metadata = metadata; } - public ImageStatus getStatus() { - return this.status; - } - public Date getUpdated() { return this.updated; } @@ -163,25 +194,47 @@ public class Image extends Resource { public Date getCreated() { return this.created; } - + + public String getTenantId() { + return this.tenantId; + } + + public String getUserId() { + return this.userId; + } + + public ImageStatus getStatus() { + return this.status; + } + public int getProgress() { return this.progress; } - - public String getServerRef() { - return this.serverRef; + + public int getMinDisk() { + return this.minDisk; } - + + public int getMinRam() { + return this.minRam; + } + + public Resource getServer() { + return this.server; + } + public Map getMetadata() { return this.metadata; } - - + @Override public String toString() { return toStringHelper("").add("id", id).add("name", name) - .add("links", links).add("status", status).add("updated", updated) - .add("created", created).add("progress", progress).add("serverRef", serverRef) + .add("links", links).add("updated", updated) + .add("created", created).add("tenantId", tenantId) + .add("userId", userId).add("status", status) + .add("progress", progress).add("minDisk", minDisk) + .add("minRam", minRam).add("server", server) .add("metadata", metadata).toString(); } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/RebootType.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/RebootType.java new file mode 100644 index 0000000000..e0e5c13e98 --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/RebootType.java @@ -0,0 +1,37 @@ +/** + * 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; + +/** + * + * @author Adrian Cole + */ +public enum RebootType { + + HARD, SOFT; + + public String value() { + return name(); + } + + public static RebootType fromValue(String v) { + return valueOf(v); + } + +} diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java index d3c64f0c27..fa216636cb 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/Server.java @@ -21,6 +21,7 @@ package org.jclouds.openstack.nova.v1_1.domain; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Date; import java.util.Map; import java.util.Set; @@ -29,14 +30,17 @@ import org.jclouds.openstack.domain.Resource; import org.jclouds.openstack.nova.v1_1.domain.Address.Type; import org.jclouds.util.Multimaps2; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import com.google.gson.annotations.SerializedName; /** - * A server is a virtual machine instance in the compute system. Flavor and image are requisite - * elements when creating a server. + * A server is a virtual machine instance in the compute system. Flavor and + * image are requisite elements when creating a server. * * @author Adrian Cole * @see addresses = LinkedHashMultimap.create(); + private String tenantId; + private String userId; + private Date updated; + private Date created; + private String hostId; + private String accessIPv4; + private String accessIPv6; + private ServerStatus status; + private int progress; + private Resource image; + private Resource flavor; + private Map metadata = Maps.newHashMap(); + // TODO: get gson multimap ad + private Multimap addresses = LinkedHashMultimap + .create(); + /** + * @see Server#getTenantId() + */ + public Builder tenantId(String tenantId) { + this.tenantId = tenantId; + return this; + } + + /** + * @see Server#getUserId() + */ + public Builder userId(String userId) { + this.userId = userId; + return this; + } + + /** + * @see Server#getUpdated() + */ + public Builder updated(Date updated) { + this.updated = updated; + return this; + } + + /** + * @see Server#getCreated() + */ + public Builder created(Date created) { + this.created = created; + return this; + } + + /** + * @see Server#getHostId() + */ + public Builder hostId(String hostId) { + this.hostId = hostId; + return this; + } + + /** + * @see Server#getAccessIPv4() + */ + public Builder accessIPv4(String accessIPv4) { + this.accessIPv4 = accessIPv4; + return this; + } + + /** + * @see Server#getAccessIPv6() + */ + public Builder accessIPv6(String accessIPv6) { + this.accessIPv6 = accessIPv6; + return this; + } + + /** + * @see Server#getStatus() + */ + public Builder status(ServerStatus status) { + this.status = status; + return this; + } + + /** + * @see Server#getProgress() + */ + public Builder progress(int progress) { + this.progress = progress; + return this; + } + + /** + * @see Server#getImage() + */ + public Builder image(Resource image) { + this.image = image; + return this; + } + + /** + * @see Server#getImage() + */ + public Builder flavor(Resource flavor) { + this.flavor = flavor; + return this; + } + + /** + * @see Server#getMetadata() + */ + public Builder metadata(Map metadata) { + this.metadata = ImmutableMap.copyOf(metadata); + return this; + } + /** * @see Server#getAddresses() */ public Builder addresses(Multimap addresses) { - this.addresses = ImmutableMultimap.copyOf(checkNotNull(addresses, "addresses")); + this.addresses = ImmutableMultimap.copyOf(checkNotNull(addresses, + "addresses")); return this; } @@ -67,15 +182,16 @@ public class Server extends Resource { * @see Server#getPrivateAddresses() */ public Builder privateAddresses(Address... privateAddresses) { - return privateAddresses(ImmutableSet.copyOf(checkNotNull(privateAddresses, "privateAddresses"))); + return privateAddresses(ImmutableSet.copyOf(checkNotNull( + privateAddresses, "privateAddresses"))); } /** * @see Server#getPrivateAddresses() */ public Builder privateAddresses(Set
privateAddresses) { - this.addresses.replaceValues(Address.Type.PRIVATE, ImmutableSet.copyOf(checkNotNull(privateAddresses, - "privateAddresses"))); + this.addresses.replaceValues(Address.Type.PRIVATE, ImmutableSet + .copyOf(checkNotNull(privateAddresses, "privateAddresses"))); return this; } @@ -83,20 +199,24 @@ public class Server extends Resource { * @see Server#getPublicAddresses() */ public Builder publicAddresses(Address... publicAddresses) { - return publicAddresses(ImmutableSet.copyOf(checkNotNull(publicAddresses, "publicAddresses"))); + return publicAddresses(ImmutableSet.copyOf(checkNotNull( + publicAddresses, "publicAddresses"))); } /** * @see Server#getPublicAddresses() */ public Builder publicAddresses(Set
publicAddresses) { - this.addresses.replaceValues(Address.Type.PUBLIC, ImmutableSet.copyOf(checkNotNull(publicAddresses, - "publicAddresses"))); + this.addresses.replaceValues(Address.Type.PUBLIC, ImmutableSet + .copyOf(checkNotNull(publicAddresses, "publicAddresses"))); return this; } public Server build() { - return new Server(id, name, links, addresses); + // return new Server(id, name, links, addresses); + return new Server(id, name, links, tenantId, userId, updated, + created, hostId, accessIPv4, accessIPv6, status, progress, + image, flavor, addresses, metadata); } public Builder fromServer(Server in) { @@ -135,15 +255,101 @@ public class Server extends Resource { return Builder.class.cast(super.fromResource(in)); } } - + + @SerializedName("tenant_id") + protected String tenantId; + @SerializedName("user_id") + protected String userId; + protected Date updated; + protected Date created; + protected String hostId; + protected String accessIPv4; + protected String accessIPv6; + protected ServerStatus status; + protected int progress; + protected Resource image; + protected Resource flavor; // TODO: get gson multimap adapter! protected final Map> addresses; + protected Map metadata; + protected String adminPass; - protected Server(String id, String name, Set links, Multimap addresses) { + protected Server(String id, String name, Set links, String tenantId, + String userId, Date updated, Date created, String hostId, + String accessIPv4, String accessIPv6, ServerStatus status, + int progress, Resource image, Resource flavor, + Multimap addresses, Map metadata) { super(id, name, links); - this.addresses = Multimaps2.toOldSchool(ImmutableMultimap.copyOf(checkNotNull(addresses, "addresses"))); + this.tenantId = tenantId; + this.userId = userId; + this.updated = updated; + this.created = created; + this.hostId = hostId; + this.accessIPv4 = accessIPv4; + this.accessIPv6 = accessIPv6; + this.status = status; + this.progress = progress; + this.image = image; + this.flavor = flavor; + this.metadata = Maps.newHashMap(metadata); + this.addresses = Multimaps2.toOldSchool(ImmutableMultimap + .copyOf(checkNotNull(addresses, "addresses"))); + } + public String getTenantId() { + return this.tenantId; + } + + public String getUserId() { + return this.userId; + } + + public Date getUpdated() { + return this.updated; + } + + public Date getCreated() { + return this.created; + } + + public String getHostId() { + return this.hostId; + } + + public String getAccessIPv4() { + return this.accessIPv4; + } + + public String getAccessIPv6() { + return this.accessIPv6; + } + + public ServerStatus getStatus() { + return this.status; + } + + public int getProgress() { + return this.progress; + } + + public Resource getImage() { + return this.image; + } + + public Resource getFlavor() { + return this.flavor; + } + + public Map getMetadata() { + return this.metadata; + } + + + public String getAdminPass() { + return this.adminPass; + } + /** * @return the private ip addresses assigned to the server */ @@ -167,8 +373,14 @@ public class Server extends Resource { @Override public String toString() { - return toStringHelper("").add("id", id).add("name", name).add("links", links).add("addresses", addresses) - .toString(); + return toStringHelper("").add("id", id).add("name", name) + .add("tenantId", tenantId).add("userId", userId).add("hostId", hostId) + .add("updated", updated).add("created", created) + .add("accessIPv4", accessIPv4).add("accessIPv6", accessIPv6) + .add("status", status).add("progress", progress) + .add("image", image).add("flavor", flavor) + .add("metadata", metadata) + .add("links", links).add("addresses", addresses).toString(); } } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerStatus.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerStatus.java new file mode 100644 index 0000000000..77c9531a04 --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/domain/ServerStatus.java @@ -0,0 +1,46 @@ +/** + * 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; + +/** + * Servers contain a status attribute that can be used as an indication of the + * current server state. Servers with an ACTIVE status are available for use. + * + * Other possible values for the status attribute include: BUILD, REBUILD, + * SUSPENDED, RESIZE, VERIFY_RESIZE, REVERT_RESIZE, PASSWORD, REBOOT, + * HARD_REBOOT, DELETED, UNKNOWN, and ERROR. + * + * @author Adrian Cole + */ +public enum ServerStatus { + ACTIVE, BUILD, REBUILD, SUSPENDED, RESIZE, VERIFY_RESIZE, REVERT_RESIZE, PASSWORD, REBOOT, HARD_REBOOT, DELETED, UNKNOWN, ERROR, UNRECOGNIZED; + + public String value() { + return name(); + } + + public static ServerStatus fromValue(String v) { + try { + return valueOf(v); + } catch (IllegalArgumentException e) { + return UNRECOGNIZED; + } + } + +} diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java index 4857db76b4..4ea13135bc 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerAsyncClient.java @@ -21,19 +21,32 @@ package org.jclouds.openstack.nova.v1_1.features; import java.util.Set; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.jclouds.openstack.domain.Resource; import org.jclouds.openstack.filters.AuthenticateRequest; +import org.jclouds.openstack.nova.v1_1.NovaClient; +import org.jclouds.openstack.nova.v1_1.domain.RebootType; import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.options.CreateServerOptions; +import org.jclouds.openstack.nova.v1_1.options.RebuildServerOptions; import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.MapBinder; +import org.jclouds.rest.annotations.Payload; +import org.jclouds.rest.annotations.PayloadParam; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.SelectJson; import org.jclouds.rest.annotations.SkipEncoding; +import org.jclouds.rest.annotations.Unwrap; import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import com.google.common.util.concurrent.ListenableFuture; @@ -81,4 +94,94 @@ public interface ServerAsyncClient { @ExceptionParser(ReturnNullOnNotFoundOr404.class) ListenableFuture getServer(@PathParam("id") String id); + + /** + * @see NovaClient#deleteServer + */ + @DELETE + @Consumes + @ExceptionParser(ReturnFalseOnNotFoundOr404.class) + @Path("/servers/{id}") + ListenableFuture deleteServer(@PathParam("id") String id); + + /** + * @see NovaClient#rebootServer + */ + @POST + @Path("/servers/{id}/action") + @Consumes + @Produces(MediaType.APPLICATION_JSON) + @Payload("%7B\"reboot\":%7B\"type\":\"{type}\"%7D%7D") + ListenableFuture rebootServer(@PathParam("id") String id, @PayloadParam("type") RebootType rebootType); + + /** + * @see NovaClient#resizeServer + */ + @POST + @Path("/servers/{id}/action") + @Consumes + @Produces(MediaType.APPLICATION_JSON) + @Payload("%7B\"resize\":%7B\"flavorId\":{flavorId}%7D%7D") + ListenableFuture resizeServer(@PathParam("id") String id, @PayloadParam("flavorId") String flavorId); + + /** + * @see NovaClient#confirmResizeServer + */ + @POST + @Path("/servers/{id}/action") + @Consumes + @Produces(MediaType.APPLICATION_JSON) + @Payload("{\"confirmResize\":null}") + ListenableFuture confirmResizeServer(@PathParam("id") String id); + + /** + * @see NovaClient#revertResizeServer + */ + @POST + @Path("/servers/{id}/action") + @Consumes + @Produces(MediaType.APPLICATION_JSON) + @Payload("{\"revertResize\":null}") + ListenableFuture revertResizeServer(@PathParam("id") String id); + + /** + * @see NovaClient#createServer + */ + @POST + @Unwrap + @Consumes(MediaType.APPLICATION_JSON) + @Path("/servers") + @MapBinder(CreateServerOptions.class) + ListenableFuture createServer(@PayloadParam("name") String name, @PayloadParam("imageRef") String imageRef, + @PayloadParam("flavorRef") String flavorRef, CreateServerOptions... options); + + /** + * @see NovaClient#rebuildServer + */ + @POST + @Path("/servers/{id}/action") + @Consumes + @MapBinder(RebuildServerOptions.class) + ListenableFuture rebuildServer(@PathParam("id") String id, RebuildServerOptions... options); + + + /** + * @see NovaClient#changeAdminPass + */ + @POST + @Path("/servers/{id}/action") + @Consumes + @Produces(MediaType.APPLICATION_JSON) + @Payload("%7B\"changePassword\":%7B\"adminPass\":\"{adminPass}\"%7D%7D") + ListenableFuture changeAdminPass(@PathParam("id") String id, @PayloadParam("adminPass") String adminPass); + + /** + * @see NovaClient#renameServer + */ + @PUT + @Path("/servers/{id}") + @Consumes + @Produces(MediaType.APPLICATION_JSON) + @Payload("%7B\"server\":%7B\"name\":\"{name}\"%7D%7D") + ListenableFuture renameServer(@PathParam("id") String id, @PayloadParam("name") String newName); } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java index 0827e1dda2..a281ad941f 100644 --- a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/features/ServerClient.java @@ -21,9 +21,30 @@ package org.jclouds.openstack.nova.v1_1.features; import java.util.Set; import java.util.concurrent.TimeUnit; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + import org.jclouds.concurrent.Timeout; import org.jclouds.openstack.domain.Resource; +import org.jclouds.openstack.nova.v1_1.NovaClient; +import org.jclouds.openstack.nova.v1_1.domain.RebootType; import org.jclouds.openstack.nova.v1_1.domain.Server; +import org.jclouds.openstack.nova.v1_1.options.CreateServerOptions; +import org.jclouds.openstack.nova.v1_1.options.RebuildServerOptions; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.MapBinder; +import org.jclouds.rest.annotations.Payload; +import org.jclouds.rest.annotations.PayloadParam; +import org.jclouds.rest.annotations.Unwrap; +import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; + +import com.google.common.util.concurrent.ListenableFuture; /** * Provides synchronous access to Server. @@ -60,4 +81,50 @@ public interface ServerClient { */ Server getServer(String id); + /** + * @see NovaClient#createServer + */ + Server createServer(String name, String imageRef, + String flavorRef, CreateServerOptions... options); + + /** + * @see NovaClient#deleteServer + */ + Boolean deleteServer(String id); + + /** + * @see NovaClient#rebootServer + */ + void rebootServer(String id, RebootType rebootType); + + /** + * @see NovaClient#resizeServer + */ + void resizeServer(String id, String flavorId); + + /** + * @see NovaClient#confirmResizeServer + */ + void confirmResizeServer(String id); + + /** + * @see NovaClient#revertResizeServer + */ + void revertResizeServer(String id); + + /** + * @see NovaClient#rebuildServer + */ + void rebuildServer(String id, RebuildServerOptions... options); + + /** + * @see NovaClient#changeAdminPass + */ + void changeAdminPass(String id, String adminPass); + + /** + * @see NovaClient#renameServer + */ + void renameServer(String id, String newName); + } diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateServerOptions.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateServerOptions.java new file mode 100644 index 0000000000..5b36ab0a3c --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/CreateServerOptions.java @@ -0,0 +1,245 @@ +/** + * 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.options; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.inject.Inject; + +import org.jclouds.encryption.internal.Base64; +import org.jclouds.http.HttpRequest; +import org.jclouds.openstack.nova.v1_1.domain.SecurityGroup; +import org.jclouds.rest.MapBinder; +import org.jclouds.rest.binders.BindToJsonPayload; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.annotations.SerializedName; + +/** + * + * @author Adrian Cole + * + */ +public class CreateServerOptions implements MapBinder { + @Inject + private BindToJsonPayload jsonBinder; + + static class File { + private final String path; + private final String contents; + + public File(String path, byte[] contents) { + this.path = checkNotNull(path, "path"); + this.contents = Base64.encodeBytes(checkNotNull(contents, "contents")); + checkArgument(path.getBytes().length < 255, String.format( + "maximum length of path is 255 bytes. Path specified %s is %d bytes", path, path.getBytes().length)); + checkArgument(contents.length < 10 * 1024, String.format( + "maximum size of the file is 10KB. Contents specified is %d bytes", contents.length)); + } + + public String getContents() { + return contents; + } + + public String getPath() { + return path; + } + + } + + @SuppressWarnings("unused") + private class ServerRequest { + final String name; + final String imageRef; + final String flavorRef; + String adminPass; + Map metadata; + List personality; + String key_name; + @SerializedName(value="security_groups") + Set securityGroups; + + private ServerRequest(String name, String imageRef, String flavorRef) { + this.name = name; + this.imageRef = imageRef; + this.flavorRef = flavorRef; + } + + } + + private Map metadata = Maps.newHashMap(); + private List files = Lists.newArrayList(); + private Set securityGroups = Sets.newHashSet(); + private String keyName; + private String adminPass; + + @Override + public R bindToRequest(R request, Map postParams) { + ServerRequest server = new ServerRequest(checkNotNull(postParams.get("name"), "name parameter not present"), + checkNotNull(postParams.get("imageRef"), "imageRef parameter not present"), checkNotNull(postParams + .get("flavorRef"), "flavorRef parameter not present")); + if (metadata.size() > 0) + server.metadata = metadata; + if (files.size() > 0) + server.personality = files; + if (keyName != null) + server.key_name = keyName; + if (securityGroups.size() > 0) { + server.securityGroups = Sets.newHashSet(); + for (String groupName : securityGroups) { + SecurityGroup group = SecurityGroup.builder().name(groupName).build(); + server.securityGroups.add(group); + } + } + if (adminPass != null) { + server.adminPass = adminPass; + } + + return bindToRequest(request, ImmutableMap.of("server", server)); + } + + /** + * You may further customize a cloud server by injecting data into the file system of the cloud + * server itself. This is useful, for example, for inserting ssh keys, setting configuration + * files, or storing data that you want to retrieve from within the instance itself. It is + * intended to provide a minimal amount of launch-time personalization. If significant + * customization is required, a custom image should be created. The max size of the file path + * data is 255 bytes while the max size of the file contents is 10KB. Note that the file contents + * should be encoded as a Base64 string and the 10KB limit refers to the number of bytes in the + * decoded data not the number of characters in the encoded data. The maximum number of file + * path/content pairs that can be supplied is 5. Any existing files that match the specified file + * will be renamed to include the extension bak followed by a time stamp. For example, the file + * /etc/passwd will be backed up as /etc/passwd.bak.1246036261.5785. All files will have root and + * the root group as owner and group owner, respectively and will allow user and group read + * access only (-r--r-----). + */ + public CreateServerOptions withFile(String path, byte[] contents) { + checkState(files.size() < 5, "maximum number of files allowed is 5"); + files.add(new File(path, contents)); + return this; + } + + public CreateServerOptions withAdminPass(String adminPass) { + checkNotNull(adminPass, "adminPass"); + this.adminPass = adminPass; + return this; + } + + /** + * Custom cloud server metadata can also be supplied at launch time. This metadata is stored in + * the API system where it is retrievable by querying the API for server status. The maximum size + * of the metadata key and value is each 255 bytes and the maximum number of key-value pairs that + * can be supplied per server is 5. + */ + public CreateServerOptions withMetadata(Map metadata) { + checkNotNull(metadata, "metadata"); + checkArgument(metadata.size() <= 5, "you cannot have more then 5 metadata values. You specified: " + + metadata.size()); + for (Entry entry : metadata.entrySet()) { + checkArgument(entry.getKey().getBytes().length < 255, String.format( + "maximum length of metadata key is 255 bytes. Key specified %s is %d bytes", entry.getKey(), entry + .getKey().getBytes().length)); + checkArgument(entry.getKey().getBytes().length < 255, String.format( + "maximum length of metadata value is 255 bytes. Value specified for %s (%s) is %d bytes", entry + .getKey(), entry.getValue(), entry.getValue().getBytes().length)); + } + this.metadata = metadata; + return this; + } + + /** + * A keypair name can be defined when creating a server. This key will be + * linked to the server and used to SSH connect to the machine + * + * @param keyName + * @return + */ + public CreateServerOptions withKeyName(String keyName) { + checkNotNull(keyName, "keyName"); + this.keyName = keyName; + return this; + } + + /** + * Defines the security group name to be used when creating a server. + * + * @param groupName + * @return + */ + public CreateServerOptions withSecurityGroup(String groupName) { + checkNotNull(groupName, "groupName"); + this.securityGroups.add(groupName); + return this; + } + + public static class Builder { + + /** + * @see CreateServerOptions#withFile(String,byte []) + */ + public static CreateServerOptions withFile(String path, byte[] contents) { + CreateServerOptions options = new CreateServerOptions(); + return options.withFile(path, contents); + } + + public static CreateServerOptions withAdminPass(String adminPass) { + CreateServerOptions options = new CreateServerOptions(); + return options.withAdminPass(adminPass); + } + + /** + * @see CreateServerOptions#withMetadata(Map) + */ + public static CreateServerOptions withMetadata(Map metadata) { + CreateServerOptions options = new CreateServerOptions(); + return options.withMetadata(metadata); + } + + /** + * @see CreateServerOptions#withKeyName(String) + */ + public static CreateServerOptions withKeyName(String keyName) { + CreateServerOptions options = new CreateServerOptions(); + return options.withKeyName(keyName); + } + + /** + * @see CreateServerOptions#withGroupName(String) + */ + public static CreateServerOptions withSecurityGroup(String name) { + CreateServerOptions options = new CreateServerOptions(); + return options.withSecurityGroup(name); + } + } + + @Override + public R bindToRequest(R request, Object input) { + return jsonBinder.bindToRequest(request, input); + } +} diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/ListOptions.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/ListOptions.java new file mode 100644 index 0000000000..1f922bbbd2 --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/ListOptions.java @@ -0,0 +1,109 @@ +/** + * 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.options; + +import java.util.Date; + +import org.jclouds.openstack.options.BaseListOptions; + +/** + * Options used to control the amount of detail in the request. + * + * @see BaseListOptions + * @see + * @author Adrian Cole + */ +public class ListOptions extends BaseListOptions { + + public static final ListOptions NONE = new ListOptions(); + + /** + * unless used, only the name and id will be returned per row. + * + * @return + */ + public ListOptions withDetails() { + this.pathSuffix = "/detail"; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public ListOptions changesSince(Date ifModifiedSince) { + super.changesSince(ifModifiedSince); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public ListOptions maxResults(int limit) { + super.maxResults(limit); + return this; + + } + + /** + * {@inheritDoc} + */ + @Override + public ListOptions startAt(long offset) { + super.startAt(offset); + return this; + } + + public static class Builder { + + /** + * @see ListOptions#withDetails() + */ + public static ListOptions withDetails() { + ListOptions options = new ListOptions(); + return options.withDetails(); + } + + /** + * @see BaseListOptions#startAt(long) + */ + public static ListOptions startAt(long prefix) { + ListOptions options = new ListOptions(); + return options.startAt(prefix); + } + + /** + * @see BaseListOptions#maxResults(long) + */ + public static ListOptions maxResults(int maxKeys) { + ListOptions options = new ListOptions(); + return options.maxResults(maxKeys); + } + + /** + * @see BaseListOptions#changesSince(Date) + */ + public static ListOptions changesSince(Date since) { + ListOptions options = new ListOptions(); + return options.changesSince(since); + } + + } +} diff --git a/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/RebuildServerOptions.java b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/RebuildServerOptions.java new file mode 100644 index 0000000000..42eee7df4b --- /dev/null +++ b/labs/openstack-nova/src/main/java/org/jclouds/openstack/nova/v1_1/options/RebuildServerOptions.java @@ -0,0 +1,80 @@ +/** + * 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.options; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Map; + +import javax.inject.Inject; + +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.MapBinder; +import org.jclouds.rest.binders.BindToJsonPayload; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +/** + * + * + * @author Adrian Cole + * + */ +public class RebuildServerOptions implements MapBinder { + @Inject + private BindToJsonPayload jsonBinder; + String imageRef; + + @Override + public R bindToRequest(R request, Map postParams) { + Map image = Maps.newHashMap(); + if (imageRef != null) + image.put("imageRef", imageRef); + return jsonBinder.bindToRequest(request, ImmutableMap.of("rebuild", image)); + } + + @Override + public R bindToRequest(R request, Object toBind) { + throw new IllegalStateException("RebuildServer is a POST operation"); + } + + /** + * @param ref + * - reference of the image to rebuild the server with. + */ + public RebuildServerOptions withImage(String ref) { + checkNotNull(ref, "image reference should not be null"); + checkArgument(!ref.isEmpty(), "image reference should not be empty"); + this.imageRef = ref; + return this; + } + + public static class Builder { + + /** + * @see RebuildServerOptions#withImage(String) + */ + public static RebuildServerOptions withImage(String ref) { + RebuildServerOptions options = new RebuildServerOptions(); + return options.withImage(ref); + } + } +} diff --git a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java index 265290a65a..122aea155b 100644 --- a/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java +++ b/labs/openstack-nova/src/test/java/org/jclouds/openstack/nova/v1_1/features/ServerClientExpectTest.java @@ -73,22 +73,4 @@ public class ServerClientExpectTest extends BaseNovaRestClientExpectTest { assertTrue(clientWhenNoServersExist.getServerClientForRegion("North").listServers().isEmpty()); } - - // TODO: gson deserializer for Multimap - public void testGetServerWhenResponseIs2xx() throws Exception { - HttpRequest listServers = HttpRequest.builder().method("GET").endpoint( - URI.create("https://compute.north.host/v1.1/3456/servers/foo")).headers( - ImmutableMultimap. builder().put("Accept", "application/json").put("X-Auth-Token", - authToken).build()).build(); - - HttpResponse listServersResponse = HttpResponse.builder().statusCode(200).payload( - payloadFromResource("/server_details.json")).build(); - - NovaClient clientWhenServersExist = requestsSendResponses(keystoneAuthWithAccessKeyAndSecretKey, - responseWithKeystoneAccess, listServers, listServersResponse); - - assertEquals(clientWhenServersExist.getServerClientForRegion("North").getServer("foo").toString(), - new ParseServerTest().expected().toString()); - } - } diff --git a/labs/openstack-nova/src/test/resources/image_details.json b/labs/openstack-nova/src/test/resources/image_details.json index 050a55ab70..b75d94c8db 100644 --- a/labs/openstack-nova/src/test/resources/image_details.json +++ b/labs/openstack-nova/src/test/resources/image_details.json @@ -10,6 +10,10 @@ "progress" : 80, "minDisk" : 5, "minRam" : 256, + "metadata" : { + "ImageType" : "Gold", + "ImageVersion" : "1.5" + }, "server" : { "id": "52415800-8b69-11e0-9b19-734f335aa7b3", "links": [