Issue 158: Get prices via orderTemplateCall. Use this to build hardware/image/os

This commit is contained in:
Jason King 2011-10-03 16:32:07 +01:00
parent 78017f8045
commit b9c6b2766f
9 changed files with 1673 additions and 44 deletions

View File

@ -21,9 +21,11 @@ package org.jclouds.softlayer.compute.functions;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import org.jclouds.softlayer.domain.ProductItem;
import org.jclouds.softlayer.domain.ProductItemCategory;
import org.jclouds.softlayer.domain.ProductItemPrice;
import java.util.NoSuchElementException;
import java.util.Set;
public class ProductItems {
@ -65,4 +67,25 @@ public class ProductItems {
}
};
}
/**
* Creates a function to get the ProductItem for the ProductItemPrice.
* Copies the category information from the price to the item if necessary
* // TODO: This method needs unit testing.
*/
public static Function<ProductItemPrice,ProductItem> item() {
return new Function<ProductItemPrice,ProductItem>() {
@Override
public ProductItem apply(ProductItemPrice productItemPrice) {
Set<ProductItemCategory> categories = productItemPrice.getCategories();
ProductItem item = productItemPrice.getItem();
ProductItem.Builder builder = ProductItem.Builder.fromProductItem(productItemPrice.getItem());
if( item.getCategories().size()==0 && categories.size() != 0) {
builder.categories(categories);
}
return builder.build();
}
};
}
}

View File

@ -28,18 +28,25 @@ import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.jclouds.collect.FindResourceInSet;
import org.jclouds.collect.Memoized;
import org.jclouds.compute.domain.*;
import org.jclouds.domain.Credentials;
import org.jclouds.domain.Location;
import org.jclouds.http.HttpResponseException;
import org.jclouds.softlayer.SoftLayerClient;
import org.jclouds.softlayer.domain.Datacenter;
import org.jclouds.softlayer.domain.ProductItem;
import org.jclouds.softlayer.domain.ProductOrder;
import org.jclouds.softlayer.domain.VirtualGuest;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.jclouds.softlayer.predicates.ProductItemPredicates;
/**
* @author Adrian Cole
@ -54,12 +61,19 @@ public class VirtualGuestToNodeMetadata implements Function<VirtualGuest, NodeMe
private final Map<String, Credentials> credentialStore;
private final FindLocationForVirtualGuest findLocationForVirtualGuest;
private final GetHardwareForVirtualGuest getHardwareForVirtualGuest;
private final GetImageForVirtualGuest getImageForVirtualGuest;
@Inject
VirtualGuestToNodeMetadata(Map<String, Credentials> credentialStore,
FindLocationForVirtualGuest findLocationForVirtualGuest) {
FindLocationForVirtualGuest findLocationForVirtualGuest,
GetHardwareForVirtualGuest getHardwareForVirtualGuest,
GetImageForVirtualGuest getImageForVirtualGuest
) {
this.credentialStore = checkNotNull(credentialStore, "credentialStore");
this.findLocationForVirtualGuest = checkNotNull(findLocationForVirtualGuest, "findLocationForVirtualGuest");
this.getHardwareForVirtualGuest = checkNotNull(getHardwareForVirtualGuest, "getHardwareForVirtualGuest");
this.getImageForVirtualGuest = checkNotNull(getImageForVirtualGuest, "getImageForVirtualGuest");
}
@Override
@ -68,14 +82,19 @@ public class VirtualGuestToNodeMetadata implements Function<VirtualGuest, NodeMe
NodeMetadataBuilder builder = new NodeMetadataBuilder();
builder.ids(from.getId() + "");
builder.name(from.getHostname());
builder.hostname(from.getHostname());
builder.location(findLocationForVirtualGuest.apply(from));
builder.group(parseGroupFromName(from.getHostname()));
// TODO determine image id (product price)from virtual guest
// builder.imageId(from.imageId + "");
// TODO make operating system from virtual guest
// builder.operatingSystem(OperatingSystem.builder()...);
builder.hardware(getHardware(from));
Image image = getImageForVirtualGuest.getImage(from);
if (image!=null) {
builder.imageId(image.getId());
builder.operatingSystem(image.getOperatingSystem());
}
Hardware hardware = getHardwareForVirtualGuest.getHardware(from);
if (hardware!=null) builder.hardware(hardware);
builder.state(serverStateToNodeState.get(from.getPowerState().getKeyName()));
// These are null for 'bad' guest orders in the HALTED state.
@ -105,19 +124,53 @@ public class VirtualGuestToNodeMetadata implements Function<VirtualGuest, NodeMe
}
}
private Hardware getHardware(VirtualGuest from) {
HardwareBuilder builder = new HardwareBuilder().id("TODO");
@Singleton
public static class GetHardwareForVirtualGuest {
final double cpus = from.getMaxCpu();
if (cpus>0) {
builder.processor(new Processor(cpus, CORE_SPEED));
private SoftLayerClient client;
@Inject
public GetHardwareForVirtualGuest(SoftLayerClient client) {
this.client = client;
}
final int maxMemory = from.getMaxMemory();
if (maxMemory>0) {
builder.ram(maxMemory);
public Hardware getHardware(VirtualGuest guest) {
// 'bad' orders have no start cpu's and cause the order lookup to fail.
if (guest.getStartCpus()<1) return null;
try {
ProductOrder order = client.getVirtualGuestClient().getOrderTemplate(guest.getId());
Iterable<ProductItem> items = Iterables.transform(order.getPrices(),ProductItems.item());
return new ProductItemsToHardware().apply(Sets.newLinkedHashSet(items));
} catch (HttpResponseException e) {
//For singapore
return null;
}
}
}
return builder.build();
@Singleton
public static class GetImageForVirtualGuest {
private SoftLayerClient client;
@Inject
public GetImageForVirtualGuest(SoftLayerClient client) {
this.client = client;
}
public Image getImage(VirtualGuest guest) {
// 'bad' orders have no start cpu's and cause the order lookup to fail.
if (guest.getStartCpus()<1) return null;
try {
ProductOrder order = client.getVirtualGuestClient().getOrderTemplate(guest.getId());
Iterable<ProductItem> items = Iterables.transform(order.getPrices(),ProductItems.item());
ProductItem os = Iterables.find(items, ProductItemPredicates.categoryCode("os"));
return new ProductItemToImage().apply(os);
} catch (HttpResponseException e) {
//For singapore
return null;
}
}
}
}

View File

@ -18,8 +18,14 @@
*/
package org.jclouds.softlayer.domain;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.jclouds.javax.annotation.Nullable;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* The SoftLayer_Product_Item_Price data type contains general information
* relating to a single SoftLayer product item price. You can find out what
@ -44,6 +50,8 @@ public class ProductItemPrice implements Comparable<ProductItemPrice> {
private long itemId = -1;
private Float recurringFee;
private Float hourlyRecurringFee;
private ProductItem item;
private Set<ProductItemCategory> categories = Sets.newLinkedHashSet();
public Builder id(int id) {
this.id = id;
@ -65,8 +73,23 @@ public class ProductItemPrice implements Comparable<ProductItemPrice> {
return this;
}
public Builder item(ProductItem item) {
this.item = item;
return this;
}
public Builder category(ProductItemCategory categories) {
this.categories.add(checkNotNull(categories, "categories"));
return this;
}
public Builder categories(Iterable<ProductItemCategory> categories) {
this.categories = ImmutableSet.<ProductItemCategory> copyOf(checkNotNull(categories, "categories"));
return this;
}
public ProductItemPrice build() {
return new ProductItemPrice(id, itemId, recurringFee, hourlyRecurringFee);
return new ProductItemPrice(id, itemId, recurringFee, hourlyRecurringFee, item, categories);
}
public static Builder fromPrice(ProductItemPrice in) {
@ -79,17 +102,21 @@ public class ProductItemPrice implements Comparable<ProductItemPrice> {
private long itemId = -1;
private Float recurringFee;
private Float hourlyRecurringFee;
private ProductItem item;
private Set<ProductItemCategory> categories = Sets.newLinkedHashSet();
// for deserializer
ProductItemPrice() {
}
public ProductItemPrice(int id, long itemId, Float recurringFee, Float hourlyRecurringFee) {
public ProductItemPrice(int id, long itemId, Float recurringFee, Float hourlyRecurringFee, ProductItem item, Iterable<ProductItemCategory> categories) {
this.id = id;
this.itemId = itemId;
this.recurringFee = recurringFee;
this.hourlyRecurringFee = hourlyRecurringFee;
this.item = item;
this.categories = ImmutableSet.<ProductItemCategory> copyOf(checkNotNull(categories, "categories"));
}
@Override
@ -130,10 +157,26 @@ public class ProductItemPrice implements Comparable<ProductItemPrice> {
return hourlyRecurringFee;
}
/**
*
* @return An item's associated item categories.
*/
public Set<ProductItemCategory> getCategories() {
return categories;
}
/**
* @return The product item a price is tied to.
*/
public ProductItem getItem() {
return item;
}
public Builder toBuilder() {
return Builder.fromPrice(this);
}
//TODO: Add category and item (may break tests).
@Override
public String toString() {
return "[id=" + id + ", itemId=" + itemId + ", recurringFee=" + recurringFee + ", hourlyRecurringFee="

View File

@ -130,7 +130,7 @@ public class ProductOrder {
public ProductOrder(int packageId, String location, Iterable<ProductItemPrice> prices, Iterable<VirtualGuest> virtualGuest, int quantity, boolean useHourlyPricing) {
this.packageId = packageId;
this.location = checkNotNull(emptyToNull(location),"location cannot be null or empty:"+location);
this.location = location;
this.prices = ImmutableSet.<ProductItemPrice> copyOf(checkNotNull(prices, "prices"));
this.virtualGuests = ImmutableSet.<VirtualGuest> copyOf(checkNotNull(virtualGuest, "virtualGuest"));
this.quantity = quantity;

View File

@ -133,4 +133,15 @@ public interface VirtualGuestAsyncClient {
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<ProductOrderReceipt> orderVirtualGuest(@BinderParam(ProductOrderToJson.class)ProductOrder order);
/**
* Throws an Internal Server Error if called on bad orders (mapped to HttpResponseException)
* @see VirtualGuestClient#getOrderTemplate
* @throws org.jclouds.http.HttpResponseException if called with a 'bad' order.
*/
@GET
@Path("SoftLayer_Virtual_Guest/{id}/getOrderTemplate/MONTHLY.json")
@Consumes(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnVoidOnNotFoundOr404.class)
ListenableFuture<ProductOrder> getOrderTemplate(@PathParam("id") long id);
}

View File

@ -18,14 +18,14 @@
*/
package org.jclouds.softlayer.features;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.jclouds.concurrent.Timeout;
import org.jclouds.softlayer.domain.ProductOrder;
import org.jclouds.softlayer.domain.ProductOrderReceipt;
import org.jclouds.softlayer.domain.VirtualGuest;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Provides synchronous access to VirtualGuest.
* <p/>
@ -109,4 +109,16 @@ public interface VirtualGuestClient {
* @see <a href="http://sldn.softlayer.com/reference/services/SoftLayer_Product_Order/placeOrder" />
*/
ProductOrderReceipt orderVirtualGuest(ProductOrder order);
/**
* Obtain an order container that is ready to be sent to the orderVirtualGuest method.
* This container will include all services that the selected computing instance has.
* If desired you may remove prices which were returned.
* @see <a href=" @see <a href="http://sldn.softlayer.com/reference/services/SoftLayer_Product_Order/placeOrder" />
* @param id
* The id of the existing Virtual Guest
* @return
* The ProductOrder used to create the VirtualGust
*/
ProductOrder getOrderTemplate(long id);
}

View File

@ -61,14 +61,17 @@ public class VirtualGuestToNodeMetadataTest {
.<Location> of(expectedLocation));
VirtualGuestToNodeMetadata parser = new VirtualGuestToNodeMetadata(credentialStore,
new FindLocationForVirtualGuest(locationSupplier));
new FindLocationForVirtualGuest(locationSupplier),new GetHardwareForVirtualGuestMock(),new GetImageForVirtualGuestMock());
NodeMetadata node = parser.apply(guest);
assertEquals(node, new NodeMetadataBuilder().ids("416788").name("node1000360500").location(
expectedLocation).state(NodeState.PENDING).publicAddresses(ImmutableSet.of("173.192.29.186"))
.privateAddresses(ImmutableSet.of("10.37.102.194"))
.hardware(new HardwareBuilder().id("TODO").processor(new Processor(1,2.0)).ram(1042).build())
assertEquals(node, new NodeMetadataBuilder().ids("416788")
.name("node1000360500").hostname("node1000360500")
.location(expectedLocation).state(NodeState.PENDING)
.publicAddresses(ImmutableSet.of("173.192.29.186")).privateAddresses(ImmutableSet.of("10.37.102.194"))
.hardware(new GetHardwareForVirtualGuestMock().getHardware(guest))
.imageId(new GetImageForVirtualGuestMock().getImage(guest).getId())
.operatingSystem(new GetImageForVirtualGuestMock().getImage(guest).getOperatingSystem())
.build());
// because it wasn't present in the credential store.
@ -90,14 +93,16 @@ public class VirtualGuestToNodeMetadataTest {
.<Location> of());
VirtualGuestToNodeMetadata parser = new VirtualGuestToNodeMetadata(credentialStore,
new FindLocationForVirtualGuest(locationSupplier));
new FindLocationForVirtualGuest(locationSupplier),new GetHardwareForVirtualGuestMock(),new GetImageForVirtualGuestMock());
NodeMetadata node = parser.apply(guest);
assertEquals(node, new NodeMetadataBuilder().ids("413348")
.name("foo-ef4").group("foo")
.name("foo-ef4").hostname("foo-ef4").group("foo")
.state(NodeState.PENDING)
.hardware(new HardwareBuilder().id("TODO").ram(256).build())
.hardware(new GetHardwareForVirtualGuestMock().getHardware(guest))
.imageId(new GetImageForVirtualGuestMock().getImage(guest).getId())
.operatingSystem(new GetImageForVirtualGuestMock().getImage(guest).getOperatingSystem())
.build());
// because it wasn't present in the credential store.
@ -120,14 +125,17 @@ public class VirtualGuestToNodeMetadataTest {
.<Location> of(expectedLocation));
VirtualGuestToNodeMetadata parser = new VirtualGuestToNodeMetadata(credentialStore,
new FindLocationForVirtualGuest(locationSupplier));
new FindLocationForVirtualGuest(locationSupplier),new GetHardwareForVirtualGuestMock(),new GetImageForVirtualGuestMock());
NodeMetadata node = parser.apply(guest);
assertEquals(node, new NodeMetadataBuilder().ids("416700").name("node1703810489").location(
expectedLocation).state(NodeState.PENDING).credentials(credentials)
assertEquals(node, new NodeMetadataBuilder().ids("416700")
.name("node1703810489").hostname("node1703810489")
.location(expectedLocation).state(NodeState.PENDING).credentials(credentials)
.publicAddresses(ImmutableSet.of("173.192.29.187")).privateAddresses(ImmutableSet.of("10.37.102.195"))
.hardware(new HardwareBuilder().id("TODO").processor(new Processor(1,2.0)).ram(1042).build())
.hardware(new GetHardwareForVirtualGuestMock().getHardware(guest))
.imageId(new GetImageForVirtualGuestMock().getImage(guest).getId())
.operatingSystem(new GetImageForVirtualGuestMock().getImage(guest).getOperatingSystem())
.build());
// because it wasn't present in the credential store.
@ -150,14 +158,17 @@ public class VirtualGuestToNodeMetadataTest {
.<Location> of(expectedLocation));
VirtualGuestToNodeMetadata parser = new VirtualGuestToNodeMetadata(credentialStore,
new FindLocationForVirtualGuest(locationSupplier));
new FindLocationForVirtualGuest(locationSupplier),new GetHardwareForVirtualGuestMock(),new GetImageForVirtualGuestMock());
NodeMetadata node = parser.apply(guest);
assertEquals(node, new NodeMetadataBuilder().ids("416700").name("node1703810489").location(
expectedLocation).state(NodeState.SUSPENDED).credentials(credentials)
assertEquals(node, new NodeMetadataBuilder().ids("416700")
.name("node1703810489").hostname("node1703810489")
.location(expectedLocation).state(NodeState.SUSPENDED).credentials(credentials)
.publicAddresses(ImmutableSet.of("173.192.29.187")).privateAddresses(ImmutableSet.of("10.37.102.195"))
.hardware(new HardwareBuilder().id("TODO").processor(new Processor(1,2.0)).ram(1042).build())
.hardware(new GetHardwareForVirtualGuestMock().getHardware(guest))
.imageId(new GetImageForVirtualGuestMock().getImage(guest).getId())
.operatingSystem(new GetImageForVirtualGuestMock().getImage(guest).getOperatingSystem())
.build());
// because it wasn't present in the credential store.
@ -180,17 +191,44 @@ public class VirtualGuestToNodeMetadataTest {
.<Location> of(expectedLocation));
VirtualGuestToNodeMetadata parser = new VirtualGuestToNodeMetadata(credentialStore,
new FindLocationForVirtualGuest(locationSupplier));
new FindLocationForVirtualGuest(locationSupplier),new GetHardwareForVirtualGuestMock(),new GetImageForVirtualGuestMock());
NodeMetadata node = parser.apply(guest);
assertEquals(node, new NodeMetadataBuilder().ids("416700").name("node1703810489").location(
expectedLocation).state(NodeState.RUNNING).credentials(credentials)
assertEquals(node, new NodeMetadataBuilder().ids("416700")
.name("node1703810489").hostname("node1703810489")
.location(expectedLocation).state(NodeState.RUNNING).credentials(credentials)
.publicAddresses(ImmutableSet.of("173.192.29.187")).privateAddresses(ImmutableSet.of("10.37.102.195"))
.hardware(new HardwareBuilder().id("TODO").processor(new Processor(1,2.0)).ram(1042).build())
.hardware(new GetHardwareForVirtualGuestMock().getHardware(guest))
.imageId(new GetImageForVirtualGuestMock().getImage(guest).getId())
.operatingSystem(new GetImageForVirtualGuestMock().getImage(guest).getOperatingSystem())
.build());
// because it wasn't present in the credential store.
assertEquals(node.getCredentials(), credentials);
}
private static class GetHardwareForVirtualGuestMock extends VirtualGuestToNodeMetadata.GetHardwareForVirtualGuest {
public GetHardwareForVirtualGuestMock() {
super(null);
}
@Override
public Hardware getHardware(VirtualGuest guest) {
return new HardwareBuilder().id("mocked hardware").build();
}
}
private static class GetImageForVirtualGuestMock extends VirtualGuestToNodeMetadata.GetImageForVirtualGuest {
public GetImageForVirtualGuestMock() {
super(null);
}
@Override
public Image getImage(VirtualGuest guest) {
return new ImageBuilder().id("123").description("mocked image")
.operatingSystem(OperatingSystem.builder().description("foo os").build())
.build();
}
}
}

View File

@ -0,0 +1,112 @@
/**
* 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.softlayer.parse;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.jclouds.http.HttpResponse;
import org.jclouds.json.BaseItemParserTest;
import org.jclouds.json.config.GsonModule;
import org.jclouds.softlayer.compute.functions.ProductItems;
import org.jclouds.softlayer.config.SoftLayerParserModule;
import org.jclouds.softlayer.domain.ProductItem;
import org.jclouds.softlayer.domain.ProductItemPrice;
import org.jclouds.softlayer.domain.ProductOrder;
import org.jclouds.softlayer.predicates.ProductItemPredicates;
import org.testng.annotations.Test;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import java.util.Set;
import static org.testng.Assert.assertNotNull;
/**
*
* @author Jason King
*/
@Test(groups = "unit", testName = "ParseProductOrderTest")
public class ParseProductOrderTest extends BaseItemParserTest<ProductOrder> {
@Override
public String resource() {
return "/product_order_template.json";
}
@Override
@Consumes(MediaType.APPLICATION_JSON)
public ProductOrder expected() {
Set<ProductItemPrice> prices = ImmutableSet.<ProductItemPrice>builder()
.add(ProductItemPrice.builder().id(1962).itemId(1045).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(1644).itemId(861).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(905).itemId(503).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(274).itemId(188).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(1800).itemId(439).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(21).itemId(15).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(1639).itemId(865).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(1693).itemId(884).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(55).itemId(49).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(57).itemId(51).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(58).itemId(52).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(420).itemId(309).recurringFee(0F).hourlyRecurringFee(0F).build())
.add(ProductItemPrice.builder().id(418).itemId(307).recurringFee(0F).hourlyRecurringFee(0F).build())
.build();
ProductOrder order = ProductOrder.builder()
.quantity(0)
.packageId(46)
.useHourlyPricing(true)
.prices(prices)
.build();
return order;
}
@Test
public void test() {
ProductOrder expects = expected();
Function<HttpResponse, ProductOrder> parser = parser(injector());
ProductOrder response = parser.apply(new HttpResponse(200, "ok", payload()));
compare(expects, response);
hasOs(response);
}
private void hasOs(ProductOrder order) {
Iterable<ProductItem> items = Iterables.transform(order.getPrices(), ProductItems.item());
ProductItem os = Iterables.find(ImmutableSet.copyOf(items), ProductItemPredicates.categoryCode("os"));
assertNotNull(os);
}
protected Injector injector() {
return Guice.createInjector(new SoftLayerParserModule(), new GsonModule() {
@Override
protected void configure() {
bind(DateAdapter.class).to(Iso8601DateAdapter.class);
super.configure();
}
});
}
}

File diff suppressed because it is too large Load Diff