Issue 158: Can now place orders via VirtualGuestClient. Additional support classes present

This commit is contained in:
Jason King 2011-09-22 18:08:55 +01:00
parent b14ecb86d5
commit 3bd0c20856
9 changed files with 546 additions and 31 deletions

View File

@ -0,0 +1,130 @@
/**
* 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.binders;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.jclouds.http.HttpRequest;
import org.jclouds.json.Json;
import org.jclouds.rest.Binder;
import org.jclouds.softlayer.domain.ProductItemPrice;
import org.jclouds.softlayer.domain.ProductOrder;
import org.jclouds.softlayer.domain.VirtualGuest;
import javax.inject.Inject;
import java.util.Set;
/**
* Converts a ProductOrder into a json string
* valid for placing an order via the softlayer api
* The String is set into the payload of the HttpRequest
*
* @author Jason King
*/
public class ProductOrderToJson implements Binder {
private Json json;
@Inject
public ProductOrderToJson(Json json) {
this.json = json;
}
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
ProductOrder order = ProductOrder.class.cast(input);
request.setPayload(buildJson(order));
return request;
}
/**
* Builds a Json string suitable for sending to the softlayer api
* @param order
* @return
*/
String buildJson(ProductOrder order) {
Iterable<Price> prices = Iterables.transform(order.getPrices(),
new Function<ProductItemPrice,Price>() {
@Override
public Price apply(ProductItemPrice productItemPrice) {
return new Price(productItemPrice.getId());
}
});
Iterable<HostnameAndDomain> hosts = Iterables.transform(order.getVirtualGuests(),
new Function<VirtualGuest,HostnameAndDomain>() {
@Override
public HostnameAndDomain apply(VirtualGuest virtualGuest) {
return new HostnameAndDomain(virtualGuest.getHostname(),virtualGuest.getDomain());
}
});
OrderData data = new OrderData(order.getPackageId(),order.getLocation(),
Sets.newLinkedHashSet(prices),Sets.newLinkedHashSet(hosts),
order.getQuantity(),order.getUseHourlyPricing());
return json.toJson(ImmutableMap.of("parameters", ImmutableList.<OrderData>of(data)));
}
private static class OrderData {
private String complexType = "SoftLayer_Container_Product_Order_Virtual_Guest";
private long packageId = -1;
private String location;
private Set<Price> prices;
private Set<HostnameAndDomain> virtualGuests;
private long quantity;
private boolean useHourlyPricing;
public OrderData( long packageId, String location,
Set<Price> prices,Set<HostnameAndDomain> virtualGuests,
long quantity, boolean useHourlyPricing ) {
this.packageId = packageId;
this.location = location;
this.prices = prices;
this.virtualGuests = virtualGuests;
this.quantity = quantity;
this.useHourlyPricing = useHourlyPricing;
}
}
private static class HostnameAndDomain {
private String hostname;
private String domain;
public HostnameAndDomain(String hostname,String domain) {
this.hostname = hostname;
this.domain = domain;
}
}
private static class Price {
private long id;
public Price(long id) {
this.id = id;
}
}
}

View File

@ -0,0 +1,219 @@
/**
* 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.domain;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.emptyToNull;
/**
*
* @author Jason King
* @see <a href= "http://sldn.softlayer.com/reference/datatypes/SoftLayer_Container_Product_Order_Virtual_Guest"
* />
*/
public class ProductOrder {
public static Builder builder() {
return new Builder();
}
public static class Builder {
private long packageId = -1;
private Set<ProductItemPrice> prices = Sets.newLinkedHashSet();
private Set<VirtualGuest> virtualGuests = Sets.newLinkedHashSet();
private String location;
private long quantity;
private boolean useHourlyPricing;
public Builder packageId(long packageId) {
this.packageId = packageId;
return this;
}
/**
* Adds a price to the order
* All that is required to send in is the price ID of each item desired to be ordered.
* @param prices
* The Prices of the item desired to be ordered
*/
public Builder price(ProductItemPrice prices) {
this.prices.add(checkNotNull(prices, "prices"));
return this;
}
/**
* Adds multiple prices to the order, overwriting any existing ones
* All that is required to send in is the price ID of each item desired to be ordered.
* @param prices
* The Prices of the items desired to be ordered
*/
public Builder prices(Iterable<ProductItemPrice> prices) {
this.prices = ImmutableSet.<ProductItemPrice> copyOf(checkNotNull(prices, "prices"));
return this;
}
/**
* Adds a virtualGuest to the order
* @param virtualGuest
* The virtualGuest to add. Needs domain and hostname.
*/
public Builder virtualGuest(VirtualGuest virtualGuest) {
this.virtualGuests.add(checkNotNull(virtualGuest, "virtualGuest"));
return this;
}
public Builder virtualGuests(Iterable<VirtualGuest> virtualGuests) {
this.virtualGuests = ImmutableSet.<VirtualGuest> copyOf(checkNotNull(virtualGuests, "virtualGuests"));
return this;
}
public Builder location(String location) {
this.location = location;
return this;
}
public Builder quantity(long quantity) {
this.quantity = quantity;
return this;
}
public Builder useHourlyPricing(Boolean useHourlyPricing) {
this.useHourlyPricing = useHourlyPricing;
return this;
}
public ProductOrder build() {
return new ProductOrder(packageId, location,prices, virtualGuests, quantity, useHourlyPricing);
}
public static Builder fromProductOrder(ProductOrder in) {
return ProductOrder.builder().packageId(in.getPackageId())
.location(in.getLocation())
.prices(in.getPrices())
.virtualGuests(in.getVirtualGuests())
.quantity(in.getQuantity())
.useHourlyPricing(in.getUseHourlyPricing());
}
}
private long packageId = -1;
private String location;
private Set<ProductItemPrice> prices = Sets.newLinkedHashSet();
private Set<VirtualGuest> virtualGuests = Sets.newLinkedHashSet();
private long quantity;
private boolean useHourlyPricing;
// for deserializer
ProductOrder() {
}
public ProductOrder(long packageId, String location, Iterable<ProductItemPrice> prices, Iterable<VirtualGuest> virtualGuest, long quantity, boolean useHourlyPricing) {
this.packageId = packageId;
this.location = checkNotNull(emptyToNull(location),"location cannot be null or empty:"+location);
this.prices = ImmutableSet.<ProductItemPrice> copyOf(checkNotNull(prices, "prices"));
this.virtualGuests = ImmutableSet.<VirtualGuest> copyOf(checkNotNull(virtualGuest, "virtualGuest"));
this.quantity = quantity;
this.useHourlyPricing = useHourlyPricing;
}
/**
* @return The package id of an order. This is required.
*/
public long getPackageId() {
return packageId;
}
/**
* @return The region keyname or specific location keyname where the order should be provisioned.
*/
public String getLocation() {
return location;
}
/**
* Gets the item prices in this order.
* All that is required to be present is the price ID
* @return the prices.
*/
public Set<ProductItemPrice> getPrices() {
return prices;
}
/**
* Gets the virtual guests in this order.
* @return the the virtual guests.
*/
public Set<VirtualGuest> getVirtualGuests() {
return virtualGuests;
}
public long getQuantity() {
return quantity;
}
public boolean getUseHourlyPricing() {
return useHourlyPricing;
}
public Builder toBuilder() {
return Builder.fromProductOrder(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductOrder that = (ProductOrder) o;
if (packageId != that.packageId) return false;
if (quantity != that.quantity) return false;
if (useHourlyPricing != that.useHourlyPricing) return false;
if (location != null ? !location.equals(that.location) : that.location != null)
return false;
if (prices != null ? !prices.equals(that.prices) : that.prices != null)
return false;
if (virtualGuests != null ? !virtualGuests.equals(that.virtualGuests) : that.virtualGuests != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = (int) (packageId ^ (packageId >>> 32));
result = 31 * result + (location != null ? location.hashCode() : 0);
result = 31 * result + (prices != null ? prices.hashCode() : 0);
result = 31 * result + (virtualGuests != null ? virtualGuests.hashCode() : 0);
result = 31 * result + (int) (quantity ^ (quantity >>> 32));
result = 31 * result + (useHourlyPricing ? 1 : 0);
return result;
}
public String toString() {
return "[packageId=" + packageId + ", location=" + location + ", prices=" + prices
+ ", virtualGuests=" + virtualGuests +", quantity=" + quantity + ", useHourlyPricing=" + useHourlyPricing + "]";
}
}

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.softlayer.domain;
/**
*
* @author Jason King
* @see <a href= "http://sldn.softlayer.com/reference/datatypes/SoftLayer_Container_Product_Order_Receipt"
* />
*/
public class ProductOrderReceipt implements Comparable<ProductOrderReceipt> {
public static Builder builder() {
return new Builder();
}
public static class Builder {
private long orderId = -1;
public Builder orderId(long orderId) {
this.orderId = orderId;
return this;
}
public ProductOrderReceipt build() {
return new ProductOrderReceipt(orderId);
}
public static Builder fromAddress(ProductOrderReceipt in) {
return ProductOrderReceipt.builder().orderId(in.getOrderId());
}
}
private long orderId = -1;
// for deserializer
ProductOrderReceipt() {
}
public ProductOrderReceipt(long orderId) {
this.orderId = orderId;
}
@Override
public int compareTo(ProductOrderReceipt arg0) {
return new Long(orderId).compareTo(arg0.getOrderId());
}
/**
* @return unique identifier for the order.
*/
public long getOrderId() {
return orderId;
}
public Builder toBuilder() {
return Builder.fromAddress(this);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (orderId ^ (orderId >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ProductOrderReceipt other = (ProductOrderReceipt) obj;
if (orderId != other.orderId)
return false;
return true;
}
@Override
public String toString() {
return "[orderId=" + orderId + "]";
}
}

View File

@ -43,7 +43,31 @@ public class VirtualGuest implements Comparable<VirtualGuest> {
} }
public static class Builder { public static class Builder {
private String domain;
private String hostname;
public Builder domain(String domain) {
this.domain = domain;
return this;
}
public Builder hostname(String hostname) {
this.hostname = hostname;
return this;
}
public VirtualGuest build() {
return new VirtualGuest(-1, null, true, domain,null,hostname,
-1,null,-1, null,-1,null,null,null,
true,-1,-1,null,null,null,null);
}
public static Builder fromVirtualGuest(VirtualGuest in) {
return VirtualGuest.builder()
.domain(in.getDomain())
.hostname(in.getHostname());
}
} }
public static enum State { public static enum State {
@ -389,7 +413,10 @@ public class VirtualGuest implements Comparable<VirtualGuest> {
return false; return false;
} else if (!uuid.equals(other.uuid)) } else if (!uuid.equals(other.uuid))
return false; return false;
if (!billingItem.equals(other.billingItem)) if (billingItem == null) {
if (other.billingItem != null)
return false;
} else if (!billingItem.equals(other.billingItem))
return false; return false;
return true; return true;
} }

View File

@ -18,24 +18,23 @@
*/ */
package org.jclouds.softlayer.features; package org.jclouds.softlayer.features;
import java.util.Set; import com.google.common.util.concurrent.ListenableFuture;
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.http.filters.BasicAuthentication; import org.jclouds.http.filters.BasicAuthentication;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.ExceptionParser;
import org.jclouds.rest.annotations.QueryParams; import org.jclouds.rest.annotations.QueryParams;
import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404; import org.jclouds.rest.functions.ReturnEmptySetOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404;
import org.jclouds.softlayer.binders.ProductOrderToJson;
import org.jclouds.softlayer.domain.ProductOrder;
import org.jclouds.softlayer.domain.ProductOrderReceipt;
import org.jclouds.softlayer.domain.VirtualGuest; import org.jclouds.softlayer.domain.VirtualGuest;
import com.google.common.util.concurrent.ListenableFuture; import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.Set;
/** /**
* Provides asynchronous access to VirtualGuest via their REST API. * Provides asynchronous access to VirtualGuest via their REST API.
@ -123,4 +122,14 @@ public interface VirtualGuestAsyncClient {
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnVoidOnNotFoundOr404.class) @ExceptionParser(ReturnVoidOnNotFoundOr404.class)
ListenableFuture<Boolean> cancelService(@PathParam("id") long id); ListenableFuture<Boolean> cancelService(@PathParam("id") long id);
/**
* @see org.jclouds.softlayer.features.VirtualGuestClient#orderVirtualGuest
*/
@POST
@Path("/SoftLayer_Product_Order/placeOrder.json")
@Consumes(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture<ProductOrderReceipt> orderVirtualGuest(@BinderParam(ProductOrderToJson.class)ProductOrder order);
} }

View File

@ -22,6 +22,8 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.jclouds.concurrent.Timeout; import org.jclouds.concurrent.Timeout;
import org.jclouds.softlayer.domain.ProductOrder;
import org.jclouds.softlayer.domain.ProductOrderReceipt;
import org.jclouds.softlayer.domain.VirtualGuest; import org.jclouds.softlayer.domain.VirtualGuest;
/** /**
@ -98,4 +100,13 @@ public interface VirtualGuestClient {
* @return true or false * @return true or false
*/ */
boolean cancelService(long id); boolean cancelService(long id);
/**
* Use this method for placing server orders and additional services orders.
* @param order
* Details required to order.
* @return A receipt for the order
* @see <a href="http://sldn.softlayer.com/reference/services/SoftLayer_Product_Order/placeOrder" />
*/
ProductOrderReceipt orderVirtualGuest(ProductOrder order);
} }

View File

@ -19,7 +19,7 @@
package org.jclouds.softlayer.reference; package org.jclouds.softlayer.reference;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import org.jclouds.softlayer.domain.ProductItemPrice;
import java.util.Set; import java.util.Set;
@ -34,16 +34,16 @@ public interface SoftLayerConstants {
*/ */
public static final String PROPERTY_SOFTLAYER_VIRTUALGUEST_PACKAGE_NAME = "jclouds.softlayer.virtualguest.package-name"; public static final String PROPERTY_SOFTLAYER_VIRTUALGUEST_PACKAGE_NAME = "jclouds.softlayer.virtualguest.package-name";
public static final Set<Long> DEFAULT_VIRTUAL_GUEST_PRICES = ImmutableSet.<Long>builder() public static final Set<ProductItemPrice> DEFAULT_VIRTUAL_GUEST_PRICES = ImmutableSet.<ProductItemPrice>builder()
.add(1639L) // 100 GB (SAN) .add(ProductItemPrice.builder().id(1639L).build()) // 100 GB (SAN)
.add(21L) // 1 IP Address .add(ProductItemPrice.builder().id(21L).build()) // 1 IP Address
.add(55L) // Host Ping .add(ProductItemPrice.builder().id(55L).build()) // Host Ping
.add(58L) // Automated Notification .add(ProductItemPrice.builder().id(58L).build()) // Automated Notification
.add(1800L) // 0 GB Bandwidth .add(ProductItemPrice.builder().id(1800L).build()) // 0 GB Bandwidth
.add(57L) // Email and Ticket .add(ProductItemPrice.builder().id(57L).build()) // Email and Ticket
.add(274L) // 1000 Mbps Public & Private Networks .add(ProductItemPrice.builder().id(274L).build()) // 1000 Mbps Public & Private Networks
.add(905L) // Reboot / Remote Console .add(ProductItemPrice.builder().id(905L).build()) // Reboot / Remote Console
.add(418L) // Nessus Vulnerability Assessment & Reporting .add(ProductItemPrice.builder().id(418L).build()) // Nessus Vulnerability Assessment & Reporting
.add(420L) // Unlimited SSL VPN Users & 1 PPTP VPN User per account .add(ProductItemPrice.builder().id(420L).build()) // Unlimited SSL VPN Users & 1 PPTP VPN User per account
.build(); .build();
} }

View File

@ -147,11 +147,11 @@ public class ProductPackageClientLiveTest extends BaseSoftLayerClientLiveTest {
@Test @Test
public void testPricesForLaunchingGuestVM() { public void testPricesForLaunchingGuestVM() {
Iterable<ProductItem> ramItems = Iterables.filter(cloudServerProductPackage.getItems(), Iterable<ProductItem> ramItems = Iterables.filter(cloudServerProductPackage.getItems(),
Predicates.and(categoryCode("ram"), capacity(1.0f))); Predicates.and(categoryCode("ram"), capacity(2.0f)));
Map<Float, ProductItem> ramToProductItem = Maps.uniqueIndex(ramItems, ProductItems.capacity()); Map<Float, ProductItem> ramToProductItem = Maps.uniqueIndex(ramItems, ProductItems.capacity());
ProductItemPrice ramPrice = ProductItems.price().apply(ramToProductItem.get(1.0f)); ProductItemPrice ramPrice = ProductItems.price().apply(ramToProductItem.get(2.0f));
Iterable<ProductItem> cpuItems = Iterables.filter(cloudServerProductPackage.getItems(), Predicates.and(units("PRIVATE_CORE"), capacity(2.0f))); Iterable<ProductItem> cpuItems = Iterables.filter(cloudServerProductPackage.getItems(), Predicates.and(units("PRIVATE_CORE"), capacity(2.0f)));
Map<Float, ProductItem> coresToProductItem = Maps.uniqueIndex(cpuItems, ProductItems.capacity()); Map<Float, ProductItem> coresToProductItem = Maps.uniqueIndex(cpuItems, ProductItems.capacity());
@ -162,13 +162,27 @@ public class ProductPackageClientLiveTest extends BaseSoftLayerClientLiveTest {
Map<String, ProductItem> osToProductItem = Maps.uniqueIndex(operatingSystems, ProductItems.description()); Map<String, ProductItem> osToProductItem = Maps.uniqueIndex(operatingSystems, ProductItems.description());
ProductItemPrice osPrice = ProductItems.price().apply(osToProductItem.get("Ubuntu Linux 8 LTS Hardy Heron - Minimal Install (64 bit)")); ProductItemPrice osPrice = ProductItems.price().apply(osToProductItem.get("Ubuntu Linux 8 LTS Hardy Heron - Minimal Install (64 bit)"));
Set<Long> prices = Sets.<Long>newLinkedHashSet(); Set<ProductItemPrice> prices = Sets.<ProductItemPrice>newLinkedHashSet();
prices.addAll(SoftLayerConstants.DEFAULT_VIRTUAL_GUEST_PRICES); prices.addAll(SoftLayerConstants.DEFAULT_VIRTUAL_GUEST_PRICES);
prices.add(ramPrice.getId()); prices.add(ramPrice);
prices.add(cpuPrice.getId()); prices.add(cpuPrice);
prices.add(osPrice.getId()); prices.add(osPrice);
//This should be everything needed to launch Ubuntu Hardy Heron with 1GB Ram and 2 CPU Cores VirtualGuest guest = VirtualGuest.builder().domain("jclouds.org")
.hostname("livetest")
.build();
String location = ""+Iterables.get(cloudServerProductPackage.getDatacenters(),0).getId();
ProductOrder order = ProductOrder.builder()
.packageId(cloudServerPackageId)
.location(location)
.quantity(1)
.useHourlyPricing(true)
.prices(prices)
.virtualGuest(guest)
.build();
//ProductOrderReceipt receipt = context.getApi().getVirtualGuestClient().orderVirtualGuest(order);
//TODO: There must be a more concise way of expressing this logic. //TODO: There must be a more concise way of expressing this logic.
} }

View File

@ -56,6 +56,9 @@ public class VirtualGuestClientLiveTest extends BaseSoftLayerClientLiveTest {
} }
private void checkVirtualGuest(VirtualGuest vg) { private void checkVirtualGuest(VirtualGuest vg) {
if (vg.getBillingItem()==null) return;//Quotes and shutting down guests
checkBillingItem(vg.getBillingItem());
assert vg.getAccountId() > 0 : vg; assert vg.getAccountId() > 0 : vg;
assert vg.getCreateDate() != null : vg; assert vg.getCreateDate() != null : vg;
assert vg.getDomain() != null : vg; assert vg.getDomain() != null : vg;
@ -72,8 +75,6 @@ public class VirtualGuestClientLiveTest extends BaseSoftLayerClientLiveTest {
assert vg.getUuid() != null : vg; assert vg.getUuid() != null : vg;
assert vg.getPrimaryBackendIpAddress() != null : vg; assert vg.getPrimaryBackendIpAddress() != null : vg;
assert vg.getPrimaryIpAddress() != null : vg; assert vg.getPrimaryIpAddress() != null : vg;
checkBillingItem(vg.getBillingItem());
} }
private void checkBillingItem(BillingItemVirtualGuest billingItem) { private void checkBillingItem(BillingItemVirtualGuest billingItem) {