openstack-nova: Adding Flavor Extra Specs extension

This commit is contained in:
Adam Lowe 2012-05-11 15:18:11 +01:00
parent d5b2968a54
commit 7f101267e6
8 changed files with 521 additions and 1 deletions

View File

@ -152,4 +152,11 @@ public interface NovaAsyncClient {
Optional<HostAggregateAsyncClient> getHostAggregateExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides asynchronous access to Flavor extra specs features.
*/
@Delegate
Optional<FlavorExtraSpecsAsyncClient> getFlavorExtraSpecsExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
}

View File

@ -153,4 +153,11 @@ public interface NovaClient {
Optional<HostAggregateClient> getHostAggregateExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
/**
* Provides synchronous access to Flavor extra specs features.
*/
@Delegate
Optional<FlavorExtraSpecsClient> getFlavorExtraSpecsExtensionForZone(
@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone);
}

View File

@ -0,0 +1,39 @@
/**
* 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.binders;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.json.Json;
import com.google.inject.TypeLiteral;
/**
* @author Adam Lowe
*/
@Singleton
public class BindExtraSpecsToJsonPayload extends BindObjectToJsonPayload<Map<String,String>> {
@Inject
public BindExtraSpecsToJsonPayload(Json jsonBinder) {
super(jsonBinder, "extra_specs", new TypeLiteral<Map<String,String>>(){});
}
}

View File

@ -78,6 +78,7 @@ public class NovaRestClientModule extends RestClientModule<NovaClient, NovaAsync
.put(ServerWithSecurityGroupsClient.class, ServerWithSecurityGroupsAsyncClient.class)
.put(AdminActionsClient.class, AdminActionsAsyncClient.class)
.put(HostAggregateClient.class, HostAggregateAsyncClient.class)
.put(FlavorExtraSpecsClient.class, FlavorExtraSpecsAsyncClient.class)
.build();
public NovaRestClientModule() {

View File

@ -0,0 +1,115 @@
/**
* 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.extensions;
import java.util.Map;
import java.util.concurrent.TimeUnit;
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.concurrent.Timeout;
import org.jclouds.openstack.filters.AuthenticateRequest;
import org.jclouds.openstack.nova.v1_1.binders.BindExtraSpecsToJsonPayload;
import org.jclouds.openstack.services.Extension;
import org.jclouds.openstack.services.ServiceType;
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.Unwrap;
import org.jclouds.rest.functions.ReturnEmptyMapOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import com.google.common.util.concurrent.ListenableFuture;
/**
* Provide access to extra metadata for Nova flavors.
*
* @author Adam Lowe
* @see <a href="http://nova.openstack.org/api/nova.api.openstack.compute.contrib.flavorextraspecs.html"/>
* @see org.jclouds.openstack.nova.v1_1.features.FlavorClient
* @see FlavorExtraSpecsClient
*/
@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.FLAVOR_EXTRA_SPECS)
@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS)
@RequestFilters(AuthenticateRequest.class)
@Consumes(MediaType.APPLICATION_JSON)
public interface FlavorExtraSpecsAsyncClient {
/**
* @see FlavorExtraSpecsClient#getAllExtraSpecs(String)
*/
@GET
@SelectJson("extra_specs")
@Path("/flavors/{flavor_id}/os-extra_specs")
@ExceptionParser(ReturnEmptyMapOnNotFoundOr404.class)
ListenableFuture<Map<String, String>> getAllExtraSpecs(@PathParam("flavor_id") String flavorId);
/**
* @see FlavorExtraSpecsClient#setExtraSpec(String, String, String)
*/
@POST
@Path("/flavors/{flavor_id}/os-extra_specs")
@Produces(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnFalseOnNotFoundOr404.class)
@MapBinder(BindExtraSpecsToJsonPayload.class)
ListenableFuture<Boolean> setAllExtraSpecs(@PathParam("flavor_id") String flavorId, Map<String, String> specs);
/**
* @see FlavorExtraSpecsClient#getExtraSpec(String, String)
*/
@GET
@Path("/flavors/{flavor_id}/os-extra_specs/{key}")
@Unwrap
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<String> getExtraSpec(@PathParam("flavor_id") String flavorId, @PathParam("key") String key);
/**
* @see FlavorExtraSpecsClient#setExtraSpec(String, String, String)
*/
@PUT
@Path("/flavors/{flavor_id}/os-extra_specs/{key}")
@Produces(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnFalseOnNotFoundOr404.class)
@Payload("%7B\"{key}\":\"{value}\"%7D")
ListenableFuture<Boolean> setExtraSpec(@PathParam("flavor_id") String flavorId,
@PathParam("key") @PayloadParam("key") String key,
@PayloadParam("value") String value);
/**
* @see FlavorExtraSpecsClient#deleteExtraSpec(String, String)
*/
@DELETE
@Path("/flavors/{flavor_id}/os-extra_specs/{key}")
@ExceptionParser(ReturnFalseOnNotFoundOr404.class)
ListenableFuture<Boolean> deleteExtraSpec(@PathParam("flavor_id") String flavorId,
@PathParam("key") String key);
}

View File

@ -0,0 +1,83 @@
/**
* 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.extensions;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.jclouds.concurrent.Timeout;
import org.jclouds.openstack.filters.AuthenticateRequest;
import org.jclouds.openstack.services.Extension;
import org.jclouds.openstack.services.ServiceType;
import org.jclouds.rest.annotations.RequestFilters;
/**
* Provide access to extra metadata for Nova flavors.
*
* @author Adam Lowe
* @see <a href="http://nova.openstack.org/api/nova.api.openstack.compute.contrib.flavorextraspecs.html"/>
* @see org.jclouds.openstack.nova.v1_1.features.FlavorClient
* @see org.jclouds.openstack.nova.v1_1.extensions.FlavorExtraSpecsAsyncClient
*/
@Extension(of = ServiceType.COMPUTE, namespace = ExtensionNamespaces.FLAVOR_EXTRA_SPECS)
@Timeout(duration = 180, timeUnit = TimeUnit.SECONDS)
@RequestFilters(AuthenticateRequest.class)
public interface FlavorExtraSpecsClient {
/**
* Retrieve all extra specs for a flavor
*
* @return the set of extra metadata for the flavor
*/
Map<String, String> getAllExtraSpecs(String flavorId);
/**
* Creates or updates the extra specs for a given flavor
*
* @param flavorId the id of the flavor to modify
* @param specs the extra specs to apply
*/
Boolean setAllExtraSpecs(String flavorId, Map<String, String> specs);
/**
* Return a single extra spec value
*
* @param flavorId the id of the flavor to modify
* @param key the extra spec key to retrieve
*/
String getExtraSpec(String flavorId, String key);
/**
* Creates or updates a single extra spec value
*
* @param flavorId the id of the flavor to modify
* @param key the extra spec key (when creating ensure this does not include whitespace or other difficult characters)
* @param value the value to associate with the key
*/
Boolean setExtraSpec(String flavorId, String key, String value);
/**
* Deletes an extra spec
*
* @param flavorId the id of the flavor to modify
* @param key the extra spec key to delete
*/
Boolean deleteExtraSpec(String flavorId, String key);
}

View File

@ -0,0 +1,143 @@
/**
* 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.extensions;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.net.URI;
import javax.ws.rs.core.MediaType;
import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientExpectTest;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
/**
* Tests guice wiring and parsing of FlavorExtraSpecsClient
*
* @author Adam Lowe
*/
@Test(groups = "unit", testName = "FlavorExtraSpecsClientExpectTest")
public class FlavorExtraSpecsClientExpectTest extends BaseNovaClientExpectTest {
public void testGetAllExtraSpecs() {
URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/flavors/9/os-extra_specs");
FlavorExtraSpecsClient client = requestsSendResponses(
keystoneAuthWithUsernameAndPassword,
responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse,
standardRequestBuilder(endpoint).build(),
standardResponseBuilder(200).payload(payloadFromResource("/volume_type_extra_specs.json")).build()
).getFlavorExtraSpecsExtensionForZone("az-1.region-a.geo-1").get();
assertEquals(client.getAllExtraSpecs("9"), ImmutableMap.of("test", "value1"));
}
public void testGetAllExtraSpecsFailNotFound() {
URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/flavors/9/os-extra_specs");
FlavorExtraSpecsClient client = requestsSendResponses(
keystoneAuthWithUsernameAndPassword,
responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse,
standardRequestBuilder(endpoint).build(),
standardResponseBuilder(404).build()
).getFlavorExtraSpecsExtensionForZone("az-1.region-a.geo-1").get();
assertTrue(client.getAllExtraSpecs("9").isEmpty());
}
public void testSetAllExtraSpecs() {
URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/flavors/9/os-extra_specs");
FlavorExtraSpecsClient client = requestsSendResponses(
keystoneAuthWithUsernameAndPassword,
responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse,
standardRequestBuilder(endpoint)
.method("POST")
.payload(payloadFromStringWithContentType("{\"extra_specs\":{\"test1\":\"somevalue\"}}", MediaType.APPLICATION_JSON)).build(),
standardResponseBuilder(200).build()
).getFlavorExtraSpecsExtensionForZone("az-1.region-a.geo-1").get();
assertTrue(client.setAllExtraSpecs("9", ImmutableMap.of("test1", "somevalue")));
}
public void testSetExtraSpec() {
URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/flavors/5/os-extra_specs/test1");
FlavorExtraSpecsClient client = requestsSendResponses(
keystoneAuthWithUsernameAndPassword,
responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse,
standardRequestBuilder(endpoint)
.method("PUT")
.payload(payloadFromStringWithContentType("{\"test1\":\"somevalue\"}", MediaType.APPLICATION_JSON)).build(),
standardResponseBuilder(200).build()
).getFlavorExtraSpecsExtensionForZone("az-1.region-a.geo-1").get();
assertTrue(client.setExtraSpec("5", "test1", "somevalue"));
}
public void testGetExtraSpec() {
URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/flavors/5/os-extra_specs/test1");
FlavorExtraSpecsClient client = requestsSendResponses(
keystoneAuthWithUsernameAndPassword,
responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse,
standardRequestBuilder(endpoint).build(),
standardResponseBuilder(200).payload(payloadFromStringWithContentType("{\"test1\":\"another value\"}", MediaType.APPLICATION_JSON)).build()
).getFlavorExtraSpecsExtensionForZone("az-1.region-a.geo-1").get();
assertEquals(client.getExtraSpec("5", "test1"), "another value");
}
public void testGetExtraSpecFailNotFound() {
URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/flavors/5/os-extra_specs/test1");
FlavorExtraSpecsClient client = requestsSendResponses(
keystoneAuthWithUsernameAndPassword,
responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse,
standardRequestBuilder(endpoint).build(),
standardResponseBuilder(404).build()
).getFlavorExtraSpecsExtensionForZone("az-1.region-a.geo-1").get();
assertNull(client.getExtraSpec("5", "test1"));
}
public void testDeleteExtraSpec() {
URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/flavors/5/os-extra_specs/test1");
FlavorExtraSpecsClient client = requestsSendResponses(
keystoneAuthWithUsernameAndPassword,
responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse,
standardRequestBuilder(endpoint).method("DELETE").build(),
standardResponseBuilder(200).build()
).getFlavorExtraSpecsExtensionForZone("az-1.region-a.geo-1").get();
assertTrue(client.deleteExtraSpec("5", "test1"));
}
public void testDeleteExtraSpecFailNotFound() {
URI endpoint = URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/flavors/5/os-extra_specs/test1");
FlavorExtraSpecsClient client = requestsSendResponses(
keystoneAuthWithUsernameAndPassword,
responseWithKeystoneAccess, extensionsOfNovaRequest, extensionsOfNovaResponse,
standardRequestBuilder(endpoint).method("DELETE").build(),
standardResponseBuilder(404).build()
).getFlavorExtraSpecsExtensionForZone("az-1.region-a.geo-1").get();
assertFalse(client.deleteExtraSpec("5", "test1"));
}
}

View File

@ -0,0 +1,125 @@
/**
* 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.extensions;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.util.Map;
import org.jclouds.openstack.domain.Resource;
import org.jclouds.openstack.nova.v1_1.features.FlavorClient;
import org.jclouds.openstack.nova.v1_1.internal.BaseNovaClientLiveTest;
import org.testng.annotations.AfterGroups;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
/**
* Tests behavior of FlavorExtraSpecsClient
*
* @author Adam Lowe
*/
@Test(groups = "live", testName = "FlavorExtraSpecsClientLiveTest", singleThreaded = true)
public class FlavorExtraSpecsClientLiveTest extends BaseNovaClientLiveTest {
private FlavorClient flavorClient;
private Optional<FlavorExtraSpecsClient> clientOption;
private String zone;
private Resource testFlavor;
private Map<String, String> testSpecs = ImmutableMap.of("jclouds-test", "some data", "jclouds-test2", "more data!");
@BeforeGroups(groups = { "integration", "live" })
@Override
public void setupContext() {
super.setupContext();
zone = Iterables.getLast(novaContext.getApi().getConfiguredZones(), "nova");
flavorClient = novaContext.getApi().getFlavorClientForZone(zone);
clientOption = novaContext.getApi().getFlavorExtraSpecsExtensionForZone(zone);
}
@AfterGroups(groups = "live")
@Override
public void tearDown() {
if (clientOption.isPresent() && testFlavor != null) {
for(String key : testSpecs.keySet()) {
assertTrue(clientOption.get().deleteExtraSpec(testFlavor.getId(), key));
}
}
super.tearDown();
}
public void testCreateExtraSpecs() {
if (clientOption.isPresent()) {
FlavorExtraSpecsClient client = clientOption.get();
testFlavor = Iterables.getLast(flavorClient.listFlavors());
Map<String, String> before = client.getAllExtraSpecs(testFlavor.getId());
assertNotNull(before);
Map<String, String> specs = Maps.newHashMap(before);
specs.putAll(testSpecs);
assertTrue(client.setAllExtraSpecs(testFlavor.getId(), specs));
assertEquals(client.getAllExtraSpecs(testFlavor.getId()), specs);
for (Map.Entry<String, String> entry : specs.entrySet()) {
assertEquals(client.getExtraSpec(testFlavor.getId(), entry.getKey()), entry.getValue());
}
}
}
@Test(dependsOnMethods = "testCreateExtraSpecs")
public void testListExtraSpecs() {
if (clientOption.isPresent()) {
FlavorExtraSpecsClient client = clientOption.get();
for (String key : testSpecs.keySet()) {
assertTrue(client.getAllExtraSpecs(testFlavor.getId()).containsKey(key));
}
for (Resource flavor : flavorClient.listFlavors()) {
Map<String, String> specs = client.getAllExtraSpecs(flavor.getId());
assertNotNull(specs);
for (Map.Entry<String, String> entry : specs.entrySet()) {
assertEquals(client.getExtraSpec(flavor.getId(), entry.getKey()), entry.getValue());
}
}
}
}
@Test(dependsOnMethods = "testCreateExtraSpecs")
public void testTwiddleIndividualSpecs() {
if (clientOption.isPresent()) {
FlavorExtraSpecsClient client = clientOption.get();
for (String key : testSpecs.keySet()) {
assertTrue(client.setExtraSpec(testFlavor.getId(), key, "new value"));
}
for (String key : testSpecs.keySet()) {
assertEquals(client.getExtraSpec(testFlavor.getId(), key), "new value");
}
for (Resource flavor : flavorClient.listFlavors()) {
Map<String, String> specs = client.getAllExtraSpecs(flavor.getId());
assertNotNull(specs);
for (Map.Entry<String, String> entry : specs.entrySet()) {
assertEquals(client.getExtraSpec(flavor.getId(), entry.getKey()), entry.getValue());
}
}
}
}
}