added extension api to nova

This commit is contained in:
Adrian Cole 2012-03-12 21:42:49 -07:00
parent 67d70fae62
commit ee4f00b645
24 changed files with 1275 additions and 13 deletions

View File

@ -25,6 +25,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
import java.net.URI; import java.net.URI;
import org.jclouds.javax.annotation.Nullable;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
@ -50,6 +52,10 @@ public class Link {
* a permanent link to a resource that is appropriate for long term storage. * a permanent link to a resource that is appropriate for long term storage.
*/ */
BOOKMARK, BOOKMARK,
/**
*
*/
DESCRIBEDBY,
/** /**
* an alternate representation of the resource. For example, an OpenStack Compute image may * an alternate representation of the resource. For example, an OpenStack Compute image may
* have an alternate representation in the OpenStack Image service. * have an alternate representation in the OpenStack Image service.
@ -75,7 +81,11 @@ public class Link {
} }
public static Link create(Relation relation, URI href) { public static Link create(Relation relation, URI href) {
return new Link(relation, href); return new Link(relation, null, href);
}
public static Link create(Relation relation,String type, URI href) {
return new Link(relation, type, href);
} }
public static Builder builder() { public static Builder builder() {
@ -88,6 +98,7 @@ public class Link {
public static class Builder { public static class Builder {
protected Relation relation; protected Relation relation;
protected String type;
protected URI href; protected URI href;
/** /**
@ -98,25 +109,39 @@ public class Link {
return this; return this;
} }
/**
* @see Link#getType()
*/
public Builder type(String type) {
this.type = type;
return this;
}
/** /**
* @see Link#getHref() * @see Link#getHref()
*/ */
protected Builder href(URI href) { public Builder href(URI href) {
this.href = checkNotNull(href, "href"); this.href = checkNotNull(href, "href");
return this; return this;
} }
public Link build(){
return new Link(relation, type, href);
}
public Builder fromLink(Link from) { public Builder fromLink(Link from) {
return relation(from.getRelation()).href(from.getHref()); return relation(from.getRelation()).type(from.getType()).href(from.getHref());
} }
} }
@SerializedName("rel") @SerializedName("rel")
protected final Relation relation; protected final Relation relation;
protected final String type;
protected final URI href; protected final URI href;
protected Link(Relation relation, URI href) { protected Link(Relation relation, @Nullable String type, URI href) {
this.relation = checkNotNull(relation, "relation"); this.relation = checkNotNull(relation, "relation");
this.type = type;
this.href = checkNotNull(href, "href"); this.href = checkNotNull(href, "href");
} }
@ -135,6 +160,14 @@ public class Link {
return relation; return relation;
} }
/**
* @return the type of the resource or null if not specified
*/
@Nullable
public String getType() {
return type;
}
/** /**
* @return the href of the resource * @return the href of the resource
*/ */
@ -149,7 +182,7 @@ public class Link {
} }
if (object instanceof Link) { if (object instanceof Link) {
final Link other = Link.class.cast(object); final Link other = Link.class.cast(object);
return equal(relation, other.relation) && equal(href, other.href); return equal(relation, other.relation) && equal(type, other.type) && equal(href, other.href);
} else { } else {
return false; return false;
} }
@ -157,12 +190,12 @@ public class Link {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode(relation, href); return Objects.hashCode(relation, type, href);
} }
@Override @Override
public String toString() { public String toString() {
return toStringHelper("").add("relation", relation).add("href", href).toString(); return toStringHelper("").add("relation", relation).add("type", type).add("href", href).toString();
} }
} }

View File

@ -132,7 +132,7 @@ public class Resource implements Comparable<Resource> {
} }
if (object instanceof Resource) { if (object instanceof Resource) {
final Resource other = Resource.class.cast(object); final Resource other = Resource.class.cast(object);
return equal(id, other.id) && equal(name, other.name) && equal(links, other.links); return equal(getId(), other.getId()) && equal(name, other.name) && equal(links, other.links);
} else { } else {
return false; return false;
} }
@ -140,12 +140,12 @@ public class Resource implements Comparable<Resource> {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hashCode(id, name, links); return Objects.hashCode(getId(), name, links);
} }
@Override @Override
public String toString() { public String toString() {
return toStringHelper("").add("id", id).add("name", name).add("links", links).toString(); return toStringHelper("").add("id", getId()).add("name", name).add("links", links).toString();
} }
@Override @Override
@ -154,7 +154,7 @@ public class Resource implements Comparable<Resource> {
return 1; return 1;
if (this == that) if (this == that)
return 0; return 0;
return this.id.compareTo(that.id); return this.getId().compareTo(that.getId());
} }
} }

View File

@ -0,0 +1,103 @@
/*
* 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.predicates;
import static com.google.common.base.Preconditions.checkNotNull;
import java.net.URI;
import org.jclouds.openstack.domain.Link;
import org.jclouds.openstack.domain.Link.Relation;
import com.google.common.base.Predicate;
/**
* Predicates handy when working with Link Types
*
* @author Adrian Cole
*/
public class LinkPredicates {
/**
* matches links of the given relation
*
* @param rel relation of the link
* @return predicate that will match links of the given rel
*/
public static Predicate<Link> relationEquals(final Relation rel) {
checkNotNull(rel, "rel must be defined");
return new Predicate<Link>() {
@Override
public boolean apply(Link link) {
return rel.equals(link.getRelation());
}
@Override
public String toString() {
return "relEquals(" + rel + ")";
}
};
}
/**
* matches links of the given href
*
* @param href
* @return predicate that will match links of the given href
*/
public static Predicate<Link> hrefEquals(final URI href) {
checkNotNull(href, "href must be defined");
return new Predicate<Link>() {
@Override
public boolean apply(Link link) {
return href.equals(link.getHref());
}
@Override
public String toString() {
return "hrefEquals(" + href + ")";
}
};
}
/**
* matches links of the given type
*
* @param type
* ex. application/pdf
* @return predicate that will match links of the given type
*/
public static Predicate<Link> typeEquals(final String type) {
checkNotNull(type, "type must be defined");
return new Predicate<Link>() {
@Override
public boolean apply(Link link) {
return type.equals(link.getType());
}
@Override
public String toString() {
return "typeEquals(" + type + ")";
}
};
}
}

View File

@ -0,0 +1,51 @@
package org.jclouds.openstack.predicates;
import static org.jclouds.openstack.predicates.LinkPredicates.hrefEquals;
import static org.jclouds.openstack.predicates.LinkPredicates.relationEquals;
import static org.jclouds.openstack.predicates.LinkPredicates.typeEquals;
import java.net.URI;
import org.jclouds.openstack.domain.Link;
import org.jclouds.openstack.domain.Link.Relation;
import org.testng.annotations.Test;
/**
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "LinkPredicatesTest")
public class LinkPredicatesTest {
Link ref = Link.builder().type("application/pdf").relation(Relation.DESCRIBEDBY).href(
URI.create("http://docs.openstack.org/ext/keypairs/api/v1.1")).build();
@Test
public void testRelationEqualsWhenEqual() {
assert relationEquals(Relation.DESCRIBEDBY).apply(ref);
}
@Test
public void testRelationEqualsWhenNotEqual() {
assert !relationEquals(Relation.UNRECOGNIZED).apply(ref);
}
@Test
public void testTypeEqualsWhenEqual() {
assert typeEquals("application/pdf").apply(ref);
}
@Test
public void testTypeEqualsWhenNotEqual() {
assert !typeEquals("foo").apply(ref);
}
@Test
public void testHrefEqualsWhenEqual() {
assert hrefEquals(URI.create("http://docs.openstack.org/ext/keypairs/api/v1.1")).apply(ref);
}
@Test
public void testHrefEqualsWhenNotEqual() {
assert !hrefEquals(URI.create("foo")).apply(ref);
}
}

View File

@ -26,6 +26,7 @@ import org.jclouds.location.functions.RegionToEndpointOrProviderIfNull;
import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPAsyncClient;
import org.jclouds.openstack.nova.v1_1.extensions.KeyPairAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairAsyncClient;
import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupAsyncClient;
import org.jclouds.openstack.nova.v1_1.features.ExtensionAsyncClient;
import org.jclouds.openstack.nova.v1_1.features.FlavorAsyncClient; import org.jclouds.openstack.nova.v1_1.features.FlavorAsyncClient;
import org.jclouds.openstack.nova.v1_1.features.ImageAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ImageAsyncClient;
import org.jclouds.openstack.nova.v1_1.features.ServerAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ServerAsyncClient;
@ -66,6 +67,13 @@ public interface NovaAsyncClient {
FlavorAsyncClient getFlavorClientForRegion( FlavorAsyncClient getFlavorClientForRegion(
@EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region);
/**
* Provides asynchronous access to Extension features.
*/
@Delegate
ExtensionAsyncClient getExtensionClientForRegion(
@EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region);
/** /**
* Provides asynchronous access to Image features. * Provides asynchronous access to Image features.
*/ */

View File

@ -28,6 +28,7 @@ import org.jclouds.location.functions.RegionToEndpointOrProviderIfNull;
import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPClient; import org.jclouds.openstack.nova.v1_1.extensions.FloatingIPClient;
import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient;
import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient;
import org.jclouds.openstack.nova.v1_1.features.ExtensionClient;
import org.jclouds.openstack.nova.v1_1.features.FlavorClient; import org.jclouds.openstack.nova.v1_1.features.FlavorClient;
import org.jclouds.openstack.nova.v1_1.features.ImageClient; import org.jclouds.openstack.nova.v1_1.features.ImageClient;
import org.jclouds.openstack.nova.v1_1.features.ServerClient; import org.jclouds.openstack.nova.v1_1.features.ServerClient;
@ -68,6 +69,13 @@ public interface NovaClient {
FlavorClient getFlavorClientForRegion( FlavorClient getFlavorClientForRegion(
@EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region); @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region);
/**
* Provides synchronous access to Extension features.
*/
@Delegate
ExtensionClient getExtensionClientForRegion(
@EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region);
/** /**
* Provides synchronous access to Image features. * Provides synchronous access to Image features.
*/ */

View File

@ -34,6 +34,8 @@ import org.jclouds.openstack.nova.v1_1.extensions.KeyPairAsyncClient;
import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient; import org.jclouds.openstack.nova.v1_1.extensions.KeyPairClient;
import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupAsyncClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupAsyncClient;
import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient; import org.jclouds.openstack.nova.v1_1.extensions.SecurityGroupClient;
import org.jclouds.openstack.nova.v1_1.features.ExtensionAsyncClient;
import org.jclouds.openstack.nova.v1_1.features.ExtensionClient;
import org.jclouds.openstack.nova.v1_1.features.FlavorAsyncClient; import org.jclouds.openstack.nova.v1_1.features.FlavorAsyncClient;
import org.jclouds.openstack.nova.v1_1.features.FlavorClient; import org.jclouds.openstack.nova.v1_1.features.FlavorClient;
import org.jclouds.openstack.nova.v1_1.features.ImageAsyncClient; import org.jclouds.openstack.nova.v1_1.features.ImageAsyncClient;
@ -59,6 +61,7 @@ public class NovaRestClientModule extends RestClientModule<NovaClient, NovaAsync
.put(ServerClient.class, ServerAsyncClient.class)// .put(ServerClient.class, ServerAsyncClient.class)//
.put(FlavorClient.class, FlavorAsyncClient.class) .put(FlavorClient.class, FlavorAsyncClient.class)
.put(ImageClient.class, ImageAsyncClient.class) .put(ImageClient.class, ImageAsyncClient.class)
.put(ExtensionClient.class, ExtensionAsyncClient.class)
.put(FloatingIPClient.class, FloatingIPAsyncClient.class) .put(FloatingIPClient.class, FloatingIPAsyncClient.class)
.put(SecurityGroupClient.class, SecurityGroupAsyncClient.class) .put(SecurityGroupClient.class, SecurityGroupAsyncClient.class)
.put(KeyPairClient.class, KeyPairAsyncClient.class) .put(KeyPairClient.class, KeyPairAsyncClient.class)

View File

@ -0,0 +1,158 @@
/**
* 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, Name 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.Objects.toStringHelper;
import java.net.URI;
import java.util.Date;
import java.util.Set;
import org.jclouds.openstack.domain.Link;
import org.jclouds.openstack.domain.Resource;
/**
* The OpenStack Compute API is extensible. Extensions serve two purposes: They allow the
* introduction of new features in the API without requiring a version change and they allow the
* introduction of vendor specific niche functionality.
*
* @author Adrian Cole
* @see <a href= "http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html"
* />
*/
public class Extension extends Resource {
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
return builder().fromExtension(this);
}
public static class Builder extends Resource.Builder {
private URI namespace;
private String alias;
private Date updated;
private String description;
public Builder namespace(URI namespace) {
this.namespace = namespace;
return this;
}
public Builder alias(String alias) {
this.alias = alias;
return this;
}
public Builder updated(Date updated) {
this.updated = updated;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Extension build() {
return new Extension(name, links, namespace, alias, updated, description);
}
public Builder fromExtension(Extension in) {
return fromResource(in).namespace(in.getNamespace()).alias(in.getAlias()).updated(in.getUpdated())
.description(in.getDescription());
}
/**
* {@inheritDoc}
*/
@Override
public Builder id(String id) {
return alias(id);
}
/**
* {@inheritDoc}
*/
@Override
public Builder name(String name) {
return Builder.class.cast(super.name(name));
}
/**
* {@inheritDoc}
*/
@Override
public Builder links(Set<Link> links) {
return Builder.class.cast(super.links(links));
}
/**
* {@inheritDoc}
*/
@Override
public Builder fromResource(Resource in) {
return Builder.class.cast(super.fromResource(in));
}
}
private URI namespace;
private String alias;
private Date updated;
private String description;
protected Extension(String name, Set<Link> links, URI namespace, String alias, Date updated,
String description) {
super(alias, name, links);
this.namespace = namespace;
this.alias = alias;
this.updated = updated;
this.description = description;
}
public URI getNamespace() {
return this.namespace;
}
@Override
public String getId() {
return this.alias;
}
public String getAlias() {
return this.alias;
}
public Date getUpdated() {
return this.updated;
}
public String getDescription() {
return this.description;
}
@Override
public String toString() {
return toStringHelper("").add("id", getId()).add("name", name).add("links", links).add("namespace", namespace).add(
"alias", alias).add("updated", updated).add("description", description).toString();
}
}

View File

@ -0,0 +1,34 @@
/**
* 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, Name 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.extensions;
import java.net.URI;
/**
* Extension namespaces
*
* @author Adrian Cole
* @see <a href= "http://nova.openstack.org/api_ext/" />
*/
public interface ExtensionNamespaces {
public static URI KEYPAIRS = URI.create("http://docs.openstack.org/ext/keypairs/api/v1.1");
public static URI VOLUMES = URI.create("http://docs.openstack.org/ext/volumes/api/v1.1");
public static URI SECURITY_GROUPS = URI.create("http://docs.openstack.org/ext/securitygroups/api/v1.1");
public static URI FLOATING_IPS = URI.create("http://docs.openstack.org/ext/floating_ips/api/v1.1");
}

View File

@ -31,6 +31,7 @@ import javax.ws.rs.core.MediaType;
import org.jclouds.openstack.filters.AuthenticateRequest; import org.jclouds.openstack.filters.AuthenticateRequest;
import org.jclouds.openstack.nova.v1_1.domain.FloatingIP; import org.jclouds.openstack.nova.v1_1.domain.FloatingIP;
import org.jclouds.openstack.nova.v1_1.features.ExtensionAsyncClient;
import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.ExceptionParser;
import org.jclouds.rest.annotations.Payload; import org.jclouds.rest.annotations.Payload;
import org.jclouds.rest.annotations.PayloadParam; import org.jclouds.rest.annotations.PayloadParam;
@ -48,6 +49,9 @@ import com.google.common.util.concurrent.ListenableFuture;
* *
* @see FloatingIPClient * @see FloatingIPClient
* @author Jeremy Daggett * @author Jeremy Daggett
* @see ExtensionAsyncClient
* @see <a href="http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html" />
* @see <a href="http://nova.openstack.org/api_ext" />
* @see <a href="http://wiki.openstack.org/os_api_floating_ip"/> * @see <a href="http://wiki.openstack.org/os_api_floating_ip"/>
*/ */
@SkipEncoding({ '/', '=' }) @SkipEncoding({ '/', '=' })

View File

@ -32,6 +32,7 @@ import javax.ws.rs.core.MediaType;
import org.jclouds.openstack.filters.AuthenticateRequest; import org.jclouds.openstack.filters.AuthenticateRequest;
import org.jclouds.openstack.nova.v1_1.domain.KeyPair; import org.jclouds.openstack.nova.v1_1.domain.KeyPair;
import org.jclouds.openstack.nova.v1_1.features.ExtensionAsyncClient;
import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.ExceptionParser;
import org.jclouds.rest.annotations.Payload; import org.jclouds.rest.annotations.Payload;
import org.jclouds.rest.annotations.PayloadParam; import org.jclouds.rest.annotations.PayloadParam;
@ -49,6 +50,9 @@ import com.google.common.util.concurrent.ListenableFuture;
* *
* @see KeyPairClient * @see KeyPairClient
* @author Jeremy Daggett * @author Jeremy Daggett
* @see ExtensionAsyncClient
* @see <a href="http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html" />
* @see <a href="http://nova.openstack.org/api_ext" />
* @see <a href="http://nova.openstack.org/api_ext/ext_keypairs.html" /> * @see <a href="http://nova.openstack.org/api_ext/ext_keypairs.html" />
*/ */
@SkipEncoding({ '/', '=' }) @SkipEncoding({ '/', '=' })

View File

@ -50,6 +50,8 @@ import com.google.common.util.concurrent.ListenableFuture;
* *
* @see SecurityGroupClient * @see SecurityGroupClient
* @author Jeremy Daggett * @author Jeremy Daggett
* @see <a href="http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html" />
* @see <a href="http://nova.openstack.org/api_ext" />
* @see <a href="http://wiki.openstack.org/os-security-groups" /> * @see <a href="http://wiki.openstack.org/os-security-groups" />
*/ */
@SkipEncoding({ '/', '=' }) @SkipEncoding({ '/', '=' })

View File

@ -0,0 +1,74 @@
/**
* 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.Extension;
import org.jclouds.rest.annotations.ExceptionParser;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.SelectJson;
import org.jclouds.rest.annotations.SkipEncoding;
import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import com.google.common.util.concurrent.ListenableFuture;
/**
* Provides asynchronous access to Extensions via their REST API.
* <p/>
*
* @see ExtensionClient
* @see <a href="http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html"
* />
* @author Adrian Cole
*/
@SkipEncoding({ '/', '=' })
@RequestFilters(AuthenticateRequest.class)
public interface ExtensionAsyncClient {
/**
* @see ExtensionClient#listExtensions
*/
@GET
@SelectJson("extensions")
@Consumes(MediaType.APPLICATION_JSON)
@Path("/extensions")
@ExceptionParser(ReturnEmptySetOnNotFoundOr404.class)
ListenableFuture<Set<Extension>> listExtensions();
/**
* @see ExtensionClient#getExtensionByAlias
*/
@GET
@SelectJson("extension")
@Consumes(MediaType.APPLICATION_JSON)
@Path("/extensions/{alias}")
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<Extension> getExtensionByAlias(@PathParam("alias") String id);
}

View File

@ -0,0 +1,55 @@
/**
* 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.Extension;
/**
* Provides asynchronous access to Extensions via their REST API.
* <p/>
*
* @see ExtensionClient
* @see <a href="http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html"
* />
* @author Adrian Cole
*/
@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS)
public interface ExtensionClient {
/**
* List all available extensions
*
* @return all extensions
*/
Set<Extension> listExtensions();
/**
* Extensions may also be queried individually by their unique alias.
*
* @param id
* id of the extension
* @return extension or null if not found
*/
Extension getExtensionByAlias(String alias);
}

View File

@ -0,0 +1,82 @@
/*
* 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.predicates;
import static com.google.common.base.Preconditions.checkNotNull;
import java.net.URI;
import org.jclouds.openstack.nova.v1_1.domain.Extension;
import com.google.common.base.Predicate;
/**
* Predicates handy when working with Extensions
*
* @author Adrian Cole
*/
public class ExtensionPredicates {
/**
* matches namespace of the given extension
*
* @param namespace
* ex {@code http://docs.openstack.org/ext/keypairs/api/v1.1}
* @return predicate that will match namespace of the given extension
*/
public static Predicate<Extension> namespaceEquals(final URI namespace) {
checkNotNull(namespace, "namespace must be defined");
return new Predicate<Extension>() {
@Override
public boolean apply(Extension ext) {
return namespace.equals(ext.getNamespace());
}
@Override
public String toString() {
return "namespaceEquals(" + namespace + ")";
}
};
}
/**
* matches alias of the given extension
*
* @param alias
* ex. {@code os-keypairs}
* @return predicate that will alias of the given extension
*/
public static Predicate<Extension> aliasEquals(final String alias) {
checkNotNull(alias, "alias must be defined");
return new Predicate<Extension>() {
@Override
public boolean apply(Extension ext) {
return alias.equals(ext.getAlias());
}
@Override
public String toString() {
return "aliasEquals(" + alias + ")";
}
};
}
}

View File

@ -0,0 +1,141 @@
/**
* 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.assertNull;
import static org.testng.Assert.assertTrue;
import java.net.URI;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.openstack.nova.v1_1.NovaClient;
import org.jclouds.openstack.nova.v1_1.internal.BaseNovaRestClientExpectTest;
import org.jclouds.openstack.nova.v1_1.parse.ParseExtensionListTest;
import org.jclouds.openstack.nova.v1_1.parse.ParseExtensionTest;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
/**
* Tests annotation parsing of {@code ExtensionAsyncClient}
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "ExtensionClientExpectTest")
public class ExtensionClientExpectTest extends BaseNovaRestClientExpectTest {
public void testListExtensionsWhenResponseIs2xx() throws Exception {
HttpRequest listExtensions = HttpRequest
.builder()
.method("GET")
.endpoint(
URI.create("https://compute.north.host/v1.1/3456/extensions"))
.headers(
ImmutableMultimap.<String, String> builder()
.put("Accept", "application/json")
.put("X-Auth-Token", authToken).build()).build();
HttpResponse listExtensionsResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResource("/extension_list.json")).build();
NovaClient clientWhenExtensionsExist = requestsSendResponses(
keystoneAuthWithAccessKeyAndSecretKey, responseWithKeystoneAccess,
listExtensions, listExtensionsResponse);
assertEquals(clientWhenExtensionsExist.getConfiguredRegions(),
ImmutableSet.of("North"));
assertEquals(clientWhenExtensionsExist.getExtensionClientForRegion("North")
.listExtensions().toString(), new ParseExtensionListTest().expected()
.toString());
}
public void testListExtensionsWhenReponseIs404IsEmpty() throws Exception {
HttpRequest listExtensions = HttpRequest
.builder()
.method("GET")
.endpoint(
URI.create("https://compute.north.host/v1.1/3456/extensions"))
.headers(
ImmutableMultimap.<String, String> builder()
.put("Accept", "application/json")
.put("X-Auth-Token", authToken).build()).build();
HttpResponse listExtensionsResponse = HttpResponse.builder().statusCode(404)
.build();
NovaClient clientWhenNoServersExist = requestsSendResponses(
keystoneAuthWithAccessKeyAndSecretKey, responseWithKeystoneAccess,
listExtensions, listExtensionsResponse);
assertTrue(clientWhenNoServersExist.getExtensionClientForRegion("North")
.listExtensions().isEmpty());
}
// TODO: gson deserializer for Multimap
public void testGetExtensionByAliasWhenResponseIs2xx() throws Exception {
HttpRequest getExtension = HttpRequest
.builder()
.method("GET")
.endpoint(
URI.create("https://compute.north.host/v1.1/3456/extensions/RS-PIE"))
.headers(
ImmutableMultimap.<String, String> builder()
.put("Accept", "application/json")
.put("X-Auth-Token", authToken).build()).build();
HttpResponse getExtensionResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResource("/extension_details.json")).build();
NovaClient clientWhenExtensionsExist = requestsSendResponses(
keystoneAuthWithAccessKeyAndSecretKey, responseWithKeystoneAccess,
getExtension, getExtensionResponse);
assertEquals(clientWhenExtensionsExist.getExtensionClientForRegion("North")
.getExtensionByAlias("RS-PIE").toString(),
new ParseExtensionTest().expected().toString());
}
public void testGetExtensionByAliasWhenResponseIs404() throws Exception {
HttpRequest getExtension = HttpRequest
.builder()
.method("GET")
.endpoint(
URI.create("https://compute.north.host/v1.1/3456/extensions/RS-PIE"))
.headers(
ImmutableMultimap.<String, String> builder()
.put("Accept", "application/json")
.put("X-Auth-Token", authToken).build()).build();
HttpResponse getExtensionResponse = HttpResponse.builder().statusCode(404)
.payload(payloadFromResource("/extension_details.json")).build();
NovaClient clientWhenNoExtensionsExist = requestsSendResponses(
keystoneAuthWithAccessKeyAndSecretKey, responseWithKeystoneAccess,
getExtension, getExtensionResponse);
assertNull(clientWhenNoExtensionsExist.getExtensionClientForRegion("North").getExtensionByAlias("RS-PIE"));
}
}

View File

@ -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 org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.Set;
import org.jclouds.openstack.nova.v1_1.domain.Extension;
import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest;
import org.testng.annotations.Test;
/**
* Tests behavior of {@code ExtensionClient}
*
* @author Adrian Cole
*/
@Test(groups = "live", testName = "ExtensionClientLiveTest")
public class ExtensionClientLiveTest extends BaseNovaClientLiveTest {
/**
* Tests the listing of Extensions (getExtension() is tested too!)
*
* @throws Exception
*/
@Test
public void testListExtensions() throws Exception {
for (String regionId : context.getApi().getConfiguredRegions()) {
ExtensionClient client = context.getApi().getExtensionClientForRegion(regionId);
Set<Extension> response = client.listExtensions();
assert null != response;
assertTrue(response.size() >= 0);
for (Extension extension : response) {
Extension newDetails = client.getExtensionByAlias(extension.getId());
assertEquals(newDetails.getId(), extension.getId());
assertEquals(newDetails.getName(), extension.getName());
assertEquals(newDetails.getDescription(), extension.getDescription());
assertEquals(newDetails.getNamespace(), extension.getNamespace());
assertEquals(newDetails.getUpdated(), extension.getUpdated());
assertEquals(newDetails.getLinks(), extension.getLinks());
}
}
}
}

View File

@ -0,0 +1,103 @@
/**
* 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.net.URI;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import org.jclouds.date.internal.SimpleDateFormatDateService;
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.Extension;
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 = "ParseExtensionListNormalTest")
public class ParseExtensionListNormalTest extends BaseSetParserTest<Extension> {
@Override
public String resource() {
return "/extension_list_normal.json";
}
@Override
@SelectJson("extensions")
@Consumes(MediaType.APPLICATION_JSON)
public Set<Extension> expected() {
return ImmutableSet
.of(Extension
.builder()
.alias("os-keypairs")
.name("Keypairs")
.namespace(URI.create("http://docs.openstack.org/ext/keypairs/api/v1.1"))
.updated(
new SimpleDateFormatDateService()
.iso8601SecondsDateParse("2011-08-08T00:00:00+00:00"))
.description("Keypair Support")
.build(),
Extension
.builder()
.alias("os-volumes")
.name("Volumes")
.namespace(URI.create("http://docs.openstack.org/ext/volumes/api/v1.1"))
.updated(
new SimpleDateFormatDateService()
.iso8601SecondsDateParse("2011-03-25T00:00:00+00:00"))
.description("Volumes support")
.build(),
Extension
.builder()
.alias("security_groups")
.name("SecurityGroups")
.namespace(URI.create("http://docs.openstack.org/ext/securitygroups/api/v1.1"))
.updated(
new SimpleDateFormatDateService()
.iso8601SecondsDateParse("2011-07-21T00:00:00+00:00"))
.description("Security group support")
.build(),
Extension
.builder()
.alias("os-floating-ips")
.name("Floating_ips")
.namespace(URI.create("http://docs.openstack.org/ext/floating_ips/api/v1.1"))
.updated(
new SimpleDateFormatDateService()
.iso8601SecondsDateParse("2011-06-16T00:00:00+00:00"))
.description("Floating IPs support")
.build()
);
}
protected Injector injector() {
return Guice.createInjector(new NovaParserModule(), new GsonModule());
}
}

View File

@ -0,0 +1,104 @@
/**
* 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.net.URI;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.json.BaseSetParserTest;
import org.jclouds.json.config.GsonModule;
import org.jclouds.openstack.domain.Link;
import org.jclouds.openstack.domain.Link.Relation;
import org.jclouds.openstack.nova.v1_1.config.NovaParserModule;
import org.jclouds.openstack.nova.v1_1.domain.Extension;
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 = "ParseExtensionListTest")
public class ParseExtensionListTest extends BaseSetParserTest<Extension> {
@Override
public String resource() {
return "/extension_list.json";
}
@Override
@SelectJson("extensions")
@Consumes(MediaType.APPLICATION_JSON)
public Set<Extension> expected() {
return ImmutableSet
.of(Extension
.builder()
.alias("RAX-PIE")
.name("Public Image Extension")
.namespace(URI.create("http://docs.rackspacecloud.com/servers/api/ext/pie/v1.0"))
.updated(
new SimpleDateFormatDateService()
.iso8601SecondsDateParse("2011-01-22T13:25:27-06:00"))
.description("Adds the capability to share an image with other users.")
.links(
ImmutableSet.of(
Link.create(
Relation.DESCRIBEDBY,
"application/pdf",
URI.create("http://docs.rackspacecloud.com/servers/api/ext/cs-pie-20111111.pdf")),
Link.create(
Relation.DESCRIBEDBY,
"application/vnd.sun.wadl+xml",
URI.create("http://docs.rackspacecloud.com/servers/api/ext/cs-pie.wadl"))))
.build(),
Extension
.builder()
.alias("RAX-CBS")
.name("Cloud Block Storage")
.namespace(URI.create("http://docs.rackspacecloud.com/servers/api/ext/cbs/v1.0"))
.updated(
new SimpleDateFormatDateService()
.iso8601SecondsDateParse("2011-01-12T11:22:33-06:00"))
.description("Allows mounting cloud block storage volumes.")
.links(
ImmutableSet.of(
Link.create(
Relation.DESCRIBEDBY,
"application/pdf",
URI.create("http://docs.rackspacecloud.com/servers/api/ext/cs-cbs-20111201.pdf")),
Link.create(
Relation.DESCRIBEDBY,
"application/vnd.sun.wadl+xml",
URI.create("http://docs.rackspacecloud.com/servers/api/ext/cs-cbs.wadl"))))
.build());
}
protected Injector injector() {
return Guice.createInjector(new NovaParserModule(), new GsonModule());
}
}

View File

@ -0,0 +1,81 @@
/**
* 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.net.URI;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.json.BaseItemParserTest;
import org.jclouds.json.config.GsonModule;
import org.jclouds.openstack.domain.Link;
import org.jclouds.openstack.domain.Link.Relation;
import org.jclouds.openstack.nova.v1_1.config.NovaParserModule;
import org.jclouds.openstack.nova.v1_1.domain.Extension;
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 = "ParseExtensionTest")
public class ParseExtensionTest extends BaseItemParserTest<Extension> {
@Override
public String resource() {
return "/extension_details.json";
}
@Override
@SelectJson("extension")
@Consumes(MediaType.APPLICATION_JSON)
public Extension expected() {
return Extension
.builder()
.alias("RS-PIE")
.name("Public Image Extension")
.namespace(URI.create("http://docs.rackspacecloud.com/servers/api/ext/pie/v1.0"))
.updated(
new SimpleDateFormatDateService()
.iso8601SecondsDateParse("2011-01-22T13:25:27-06:00"))
.description("Adds the capability to share an image with other users.")
.links(
ImmutableSet.of(
Link.create(
Relation.DESCRIBEDBY,
"application/pdf",
URI.create("http://docs.rackspacecloud.com/servers/api/ext/cs-pie-20111111.pdf")),
Link.create(
Relation.DESCRIBEDBY,
"application/vnd.sun.wadl+xml",
URI.create("http://docs.rackspacecloud.com/servers/api/ext/cs-pie.wadl"))))
.build();
}
protected Injector injector() {
return Guice.createInjector(new NovaParserModule(), new GsonModule());
}
}

View File

@ -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.predicates;
import java.net.URI;
import static org.jclouds.openstack.nova.v1_1.predicates.ExtensionPredicates.*;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.openstack.nova.v1_1.domain.Extension;
import org.testng.annotations.Test;
/**
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "ExtensionPredicatesTest")
public class ExtensionPredicatesTest {
Extension ref = Extension.builder().alias("os-keypairs").name("Keypairs").namespace(
URI.create("http://docs.openstack.org/ext/keypairs/api/v1.1")).updated(
new SimpleDateFormatDateService().iso8601SecondsDateParse("2011-08-08T00:00:00+00:00")).description(
"Keypair Support").build();
@Test
public void testAliasEqualsWhenEqual() {
assert aliasEquals("os-keypairs").apply(ref);
}
@Test
public void testAliasEqualsWhenNotEqual() {
assert !aliasEquals("foo").apply(ref);
}
@Test
public void testNamespaceEqualsWhenEqual() {
assert namespaceEquals(URI.create("http://docs.openstack.org/ext/keypairs/api/v1.1")).apply(ref);
}
@Test
public void testNamespaceEqualsWhenNotEqual() {
assert !namespaceEquals(URI.create("foo")).apply(ref);
}
}

View File

@ -0,0 +1,21 @@
{
"extension" : {
"name" : "Public Image Extension",
"namespace" : "http://docs.rackspacecloud.com/servers/api/ext/pie/v1.0",
"alias" : "RS-PIE",
"updated" : "2011-01-22T13:25:27-06:00",
"description" : "Adds the capability to share an image with other users.",
"links" : [
{
"rel" : "describedby",
"type" : "application/pdf",
"href" : "http://docs.rackspacecloud.com/servers/api/ext/cs-pie-20111111.pdf"
},
{
"rel" : "describedby",
"type" : "application/vnd.sun.wadl+xml",
"href" : "http://docs.rackspacecloud.com/servers/api/ext/cs-pie.wadl"
}
]
}
}

View File

@ -0,0 +1,42 @@
{
"extensions": [
{
"name": "Public Image Extension",
"namespace": "http://docs.rackspacecloud.com/servers/api/ext/pie/v1.0",
"alias": "RAX-PIE",
"updated": "2011-01-22T13:25:27-06:00",
"description": "Adds the capability to share an image with other users.",
"links": [
{
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/servers/api/ext/cs-pie-20111111.pdf"
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/servers/api/ext/cs-pie.wadl"
}
]
},
{
"name": "Cloud Block Storage",
"namespace": "http://docs.rackspacecloud.com/servers/api/ext/cbs/v1.0",
"alias": "RAX-CBS",
"updated": "2011-01-12T11:22:33-06:00",
"description": "Allows mounting cloud block storage volumes.",
"links": [
{
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/servers/api/ext/cs-cbs-20111201.pdf"
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/servers/api/ext/cs-cbs.wadl"
}
]
}
]
}

View File

@ -0,0 +1,31 @@
{
"extensions": [{
"updated": "2011-08-08T00:00:00+00:00",
"name": "Keypairs",
"links": [],
"namespace": "http://docs.openstack.org/ext/keypairs/api/v1.1",
"alias": "os-keypairs",
"description": "Keypair Support"
}, {
"updated": "2011-03-25T00:00:00+00:00",
"name": "Volumes",
"links": [],
"namespace": "http://docs.openstack.org/ext/volumes/api/v1.1",
"alias": "os-volumes",
"description": "Volumes support"
}, {
"updated": "2011-07-21T00:00:00+00:00",
"name": "SecurityGroups",
"links": [],
"namespace": "http://docs.openstack.org/ext/securitygroups/api/v1.1",
"alias": "security_groups",
"description": "Security group support"
}, {
"updated": "2011-06-16T00:00:00+00:00",
"name": "Floating_ips",
"links": [],
"namespace": "http://docs.openstack.org/ext/floating_ips/api/v1.1",
"alias": "os-floating-ips",
"description": "Floating IPs support"
}]
}