mirror of https://github.com/apache/jclouds.git
Merge branch 'master' of https://github.com/jsonking/jclouds
* 'master' of https://github.com/jsonking/jclouds: Issue 158: Removed function to find virtual guest - no longer needed issue 384: IsoToMachine + Test started issue 384: IsoToMachine + Test started Issue 158: Property/Predicate/finder to test that the product order got created. Initial timeout of 5s added option to specify domainName as an option when using softlayer fixed location for openhosting to FL, not VA
This commit is contained in:
commit
ae98f06d99
|
@ -39,6 +39,7 @@ public class SoftLayerPropertiesBuilder extends PropertiesBuilder {
|
||||||
properties.setProperty(PROPERTY_ENDPOINT, "https://api.softlayer.com/rest");
|
properties.setProperty(PROPERTY_ENDPOINT, "https://api.softlayer.com/rest");
|
||||||
properties.setProperty(PROPERTY_API_VERSION, "3");
|
properties.setProperty(PROPERTY_API_VERSION, "3");
|
||||||
properties.setProperty(SoftLayerConstants.PROPERTY_SOFTLAYER_VIRTUALGUEST_PACKAGE_NAME, "Cloud Server");
|
properties.setProperty(SoftLayerConstants.PROPERTY_SOFTLAYER_VIRTUALGUEST_PACKAGE_NAME, "Cloud Server");
|
||||||
|
properties.setProperty(SoftLayerConstants.PROPERTY_SOFTLAYER_VIRTUALGUEST_ORDER_DELAY, "5000");
|
||||||
properties.setProperty(PROPERTY_ISO3166_CODES, "SG,US-CA,US-TX,US-VA,US-WA,US-TX");
|
properties.setProperty(PROPERTY_ISO3166_CODES, "SG,US-CA,US-TX,US-VA,US-WA,US-TX");
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,20 +18,12 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.softlayer.compute.strategy;
|
package org.jclouds.softlayer.compute.strategy;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import com.google.common.base.Predicates;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import com.google.common.base.Splitter;
|
||||||
import static org.jclouds.softlayer.predicates.ProductItemPredicates.categoryCode;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import static org.jclouds.softlayer.predicates.ProductItemPredicates.matches;
|
import com.google.common.collect.Iterables;
|
||||||
import static org.jclouds.softlayer.predicates.ProductItemPredicates.units;
|
import com.google.common.collect.Maps;
|
||||||
import static org.jclouds.softlayer.predicates.ProductPackagePredicates.named;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.jclouds.compute.ComputeService;
|
import org.jclouds.compute.ComputeService;
|
||||||
import org.jclouds.compute.ComputeServiceAdapter;
|
import org.jclouds.compute.ComputeServiceAdapter;
|
||||||
import org.jclouds.compute.domain.Template;
|
import org.jclouds.compute.domain.Template;
|
||||||
|
@ -39,25 +31,21 @@ import org.jclouds.domain.Credentials;
|
||||||
import org.jclouds.softlayer.SoftLayerClient;
|
import org.jclouds.softlayer.SoftLayerClient;
|
||||||
import org.jclouds.softlayer.compute.functions.ProductItems;
|
import org.jclouds.softlayer.compute.functions.ProductItems;
|
||||||
import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
|
import org.jclouds.softlayer.compute.options.SoftLayerTemplateOptions;
|
||||||
import org.jclouds.softlayer.domain.BillingItemVirtualGuest;
|
import org.jclouds.softlayer.domain.*;
|
||||||
import org.jclouds.softlayer.domain.Datacenter;
|
|
||||||
import org.jclouds.softlayer.domain.OperatingSystem;
|
|
||||||
import org.jclouds.softlayer.domain.Password;
|
|
||||||
import org.jclouds.softlayer.domain.ProductItem;
|
|
||||||
import org.jclouds.softlayer.domain.ProductItemPrice;
|
|
||||||
import org.jclouds.softlayer.domain.ProductOrder;
|
|
||||||
import org.jclouds.softlayer.domain.ProductPackage;
|
|
||||||
import org.jclouds.softlayer.domain.VirtualGuest;
|
|
||||||
import org.jclouds.softlayer.features.AccountClient;
|
import org.jclouds.softlayer.features.AccountClient;
|
||||||
import org.jclouds.softlayer.features.ProductPackageClient;
|
import org.jclouds.softlayer.features.ProductPackageClient;
|
||||||
import org.jclouds.softlayer.reference.SoftLayerConstants;
|
import org.jclouds.softlayer.reference.SoftLayerConstants;
|
||||||
|
|
||||||
import com.google.common.base.Predicates;
|
import javax.inject.Inject;
|
||||||
import com.google.common.base.Splitter;
|
import javax.inject.Named;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import javax.inject.Singleton;
|
||||||
import com.google.common.collect.Iterables;
|
import java.util.Map;
|
||||||
import com.google.common.collect.Maps;
|
import java.util.Set;
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static org.jclouds.softlayer.predicates.ProductItemPredicates.*;
|
||||||
|
import static org.jclouds.softlayer.predicates.ProductPackagePredicates.named;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* defines the connection between the {@link SoftLayerClient} implementation and the jclouds
|
* defines the connection between the {@link SoftLayerClient} implementation and the jclouds
|
||||||
|
@ -66,9 +54,9 @@ import com.google.common.collect.Sets;
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class SoftLayerComputeServiceAdapter implements
|
public class SoftLayerComputeServiceAdapter implements
|
||||||
ComputeServiceAdapter<VirtualGuest, Set<ProductItem>, ProductItem, Datacenter> {
|
ComputeServiceAdapter<VirtualGuest, Set<ProductItem>, ProductItem, Datacenter> {
|
||||||
|
|
||||||
public static final String SAN_DESCRIPTION_REGEX=".*GB \\(SAN\\).*";
|
public static final String SAN_DESCRIPTION_REGEX = ".*GB \\(SAN\\).*";
|
||||||
private static final Float BOOT_VOLUME_CAPACITY = 100F;
|
private static final Float BOOT_VOLUME_CAPACITY = 100F;
|
||||||
|
|
||||||
private final SoftLayerClient client;
|
private final SoftLayerClient client;
|
||||||
|
@ -87,62 +75,36 @@ public class SoftLayerComputeServiceAdapter implements
|
||||||
checkNotNull(template, "template was null");
|
checkNotNull(template, "template was null");
|
||||||
checkNotNull(template.getOptions(), "template options was null");
|
checkNotNull(template.getOptions(), "template options was null");
|
||||||
checkArgument(template.getOptions().getClass().isAssignableFrom(SoftLayerTemplateOptions.class),
|
checkArgument(template.getOptions().getClass().isAssignableFrom(SoftLayerTemplateOptions.class),
|
||||||
"options class %s should have been assignable from SoftLayerTemplateOptions", template.getOptions()
|
"options class %s should have been assignable from SoftLayerTemplateOptions", template.getOptions()
|
||||||
.getClass());
|
.getClass());
|
||||||
|
|
||||||
Iterable<VirtualGuest> existing = findVirtualGuests(name,group);
|
String domainName = template.getOptions().as(SoftLayerTemplateOptions.class).getDomainName();
|
||||||
if(!Iterables.isEmpty(existing)) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"VirtualGuest(s) already exist with hostname:"+name+", group:"+group+". Existing:"+existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualGuest newGuest = VirtualGuest.builder()
|
VirtualGuest newGuest = VirtualGuest.builder().domain(domainName).hostname(name).build();
|
||||||
.domain(template.getOptions().as(SoftLayerTemplateOptions.class).getDomainName())
|
|
||||||
.hostname(name)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
ProductOrder order = ProductOrder.builder()
|
ProductOrder order = ProductOrder.builder().packageId(getProductPackage().getId()).location(
|
||||||
.packageId(getProductPackage().getId())
|
template.getLocation().getId()).quantity(1).useHourlyPricing(true).prices(getPrices(template))
|
||||||
.location(template.getLocation().getId())
|
.virtualGuest(newGuest).build();
|
||||||
.quantity(1)
|
|
||||||
.useHourlyPricing(true)
|
|
||||||
.prices(getPrices(template))
|
|
||||||
.virtualGuest(newGuest)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
client.getVirtualGuestClient().orderVirtualGuest(order);
|
ProductOrderReceipt productOrderReceipt = client.getVirtualGuestClient().orderVirtualGuest(order);
|
||||||
|
VirtualGuest result = Iterables.get(productOrderReceipt.getOrderDetails().getVirtualGuests(), 0);
|
||||||
|
|
||||||
|
|
||||||
VirtualGuest result = Iterables.getOnlyElement(findVirtualGuests(name, group));
|
Credentials credentials = new Credentials(null, null);
|
||||||
Credentials credentials = new Credentials(null,null);
|
|
||||||
|
|
||||||
// This information is not always available.
|
// This information is not always available.
|
||||||
OperatingSystem os = result.getOperatingSystem();
|
OperatingSystem os = result.getOperatingSystem();
|
||||||
if(os!=null) {
|
if (os != null) {
|
||||||
Set<Password> passwords = os.getPasswords();
|
Set<Password> passwords = os.getPasswords();
|
||||||
if(passwords.size()>0) {
|
if (passwords.size() > 0) {
|
||||||
Password pw = Iterables.get(passwords,0);
|
Password pw = Iterables.get(passwords, 0);
|
||||||
credentials = new Credentials(pw.getUsername(),pw.getPassword());
|
credentials = new Credentials(pw.getUsername(), pw.getPassword());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
credentialStore.put("node#"+result.getId(),credentials);
|
credentialStore.put("node#" + result.getId(), credentials);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Iterable<VirtualGuest> findVirtualGuests(String hostname,String domain) {
|
|
||||||
checkNotNull(hostname,"hostname");
|
|
||||||
checkNotNull(domain,"domain");
|
|
||||||
|
|
||||||
Set<VirtualGuest> result = Sets.newLinkedHashSet();
|
|
||||||
|
|
||||||
for( VirtualGuest guest : client.getVirtualGuestClient().listVirtualGuests()) {
|
|
||||||
if ( guest.getHostname().equals(hostname) && guest.getDomain().equals(domain)) {
|
|
||||||
result.add(guest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Iterable<ProductItemPrice> getPrices(Template template) {
|
private Iterable<ProductItemPrice> getPrices(Template template) {
|
||||||
Set<ProductItemPrice> result = Sets.newLinkedHashSet();
|
Set<ProductItemPrice> result = Sets.newLinkedHashSet();
|
||||||
|
@ -151,7 +113,7 @@ public class SoftLayerComputeServiceAdapter implements
|
||||||
result.add(ProductItemPrice.builder().id(imageId).build());
|
result.add(ProductItemPrice.builder().id(imageId).build());
|
||||||
|
|
||||||
Iterable<String> hardwareIds = Splitter.on(",").split(template.getHardware().getId());
|
Iterable<String> hardwareIds = Splitter.on(",").split(template.getHardware().getId());
|
||||||
for(String hardwareId: hardwareIds) {
|
for (String hardwareId : hardwareIds) {
|
||||||
int id = Integer.parseInt(hardwareId);
|
int id = Integer.parseInt(hardwareId);
|
||||||
result.add(ProductItemPrice.builder().id(id).build());
|
result.add(ProductItemPrice.builder().id(id).build());
|
||||||
}
|
}
|
||||||
|
@ -161,32 +123,32 @@ public class SoftLayerComputeServiceAdapter implements
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterable<Set<ProductItem>> listHardwareProfiles() {
|
public Iterable<Set<ProductItem>> listHardwareProfiles() {
|
||||||
ProductPackage productPackage = getProductPackage();
|
ProductPackage productPackage = getProductPackage();
|
||||||
Set<ProductItem> items = productPackage.getItems();
|
Set<ProductItem> items = productPackage.getItems();
|
||||||
|
|
||||||
Iterable<ProductItem> cpuItems = Iterables.filter(items, units("PRIVATE_CORE"));
|
Iterable<ProductItem> cpuItems = Iterables.filter(items, units("PRIVATE_CORE"));
|
||||||
Iterable<ProductItem> ramItems = Iterables.filter(items,categoryCode("ram"));
|
Iterable<ProductItem> ramItems = Iterables.filter(items, categoryCode("ram"));
|
||||||
Iterable<ProductItem> sanItems = Iterables.filter(items, Predicates.and(matches(SAN_DESCRIPTION_REGEX),categoryCode("one_time_charge")));
|
Iterable<ProductItem> sanItems = Iterables.filter(items, Predicates.and(matches(SAN_DESCRIPTION_REGEX),
|
||||||
|
categoryCode("one_time_charge")));
|
||||||
|
|
||||||
Map<Float, ProductItem> cpuMap = Maps.uniqueIndex(cpuItems, ProductItems.capacity());
|
Map<Float, ProductItem> cpuMap = Maps.uniqueIndex(cpuItems, ProductItems.capacity());
|
||||||
Map<Float, ProductItem> ramMap = Maps.uniqueIndex(ramItems, ProductItems.capacity());
|
Map<Float, ProductItem> ramMap = Maps.uniqueIndex(ramItems, ProductItems.capacity());
|
||||||
Map<Float, ProductItem> sanMap = Maps.uniqueIndex(sanItems, ProductItems.capacity());
|
Map<Float, ProductItem> sanMap = Maps.uniqueIndex(sanItems, ProductItems.capacity());
|
||||||
|
|
||||||
final ProductItem bootVolume = sanMap.get(BOOT_VOLUME_CAPACITY);
|
final ProductItem bootVolume = sanMap.get(BOOT_VOLUME_CAPACITY);
|
||||||
assert bootVolume!=null : "Boot volume capacity not found:"+BOOT_VOLUME_CAPACITY+", available:"+sanItems;
|
assert bootVolume != null : "Boot volume capacity not found:" + BOOT_VOLUME_CAPACITY + ", available:" + sanItems;
|
||||||
|
|
||||||
Set<Set<ProductItem>> result = Sets.newLinkedHashSet();
|
Set<Set<ProductItem>> result = Sets.newLinkedHashSet();
|
||||||
for(Map.Entry<Float, ProductItem> coresEntry : cpuMap.entrySet()) {
|
for (Map.Entry<Float, ProductItem> coresEntry : cpuMap.entrySet()) {
|
||||||
Float cores = coresEntry.getKey();
|
Float cores = coresEntry.getKey();
|
||||||
ProductItem ramItem = ramMap.get(cores);
|
ProductItem ramItem = ramMap.get(cores);
|
||||||
//Amount of RAM and number of cores must match.
|
// Amount of RAM and number of cores must match.
|
||||||
if(ramItem==null) continue;
|
if (ramItem == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
result.add(ImmutableSet.of(coresEntry.getValue(),ramItem,bootVolume));
|
result.add(ImmutableSet.of(coresEntry.getValue(), ramItem, bootVolume));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -211,7 +173,7 @@ public class SoftLayerComputeServiceAdapter implements
|
||||||
AccountClient accountClient = client.getAccountClient();
|
AccountClient accountClient = client.getAccountClient();
|
||||||
ProductPackageClient productPackageClient = client.getProductPackageClient();
|
ProductPackageClient productPackageClient = client.getProductPackageClient();
|
||||||
|
|
||||||
ProductPackage p = Iterables.find(accountClient.getActivePackages(),named(virtualGuestPackageName));
|
ProductPackage p = Iterables.find(accountClient.getActivePackages(), named(virtualGuestPackageName));
|
||||||
return productPackageClient.getProductPackage(p.getId());
|
return productPackageClient.getProductPackage(p.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,10 +186,12 @@ public class SoftLayerComputeServiceAdapter implements
|
||||||
@Override
|
@Override
|
||||||
public void destroyNode(String id) {
|
public void destroyNode(String id) {
|
||||||
VirtualGuest guest = getNode(id);
|
VirtualGuest guest = getNode(id);
|
||||||
if(guest==null) return;
|
if (guest == null)
|
||||||
|
return;
|
||||||
|
|
||||||
BillingItemVirtualGuest billingItem = guest.getBillingItem();
|
BillingItemVirtualGuest billingItem = guest.getBillingItem();
|
||||||
if (billingItem==null) return;
|
if (billingItem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
client.getVirtualGuestClient().cancelService(billingItem.getId());
|
client.getVirtualGuestClient().cancelService(billingItem.getId());
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,30 +31,38 @@ public class ProductOrderReceipt implements Comparable<ProductOrderReceipt> {
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private int orderId = -1;
|
private int orderId = -1;
|
||||||
|
private ProductOrder orderDetails;
|
||||||
|
|
||||||
public Builder orderId(int orderId) {
|
public Builder orderId(int orderId) {
|
||||||
this.orderId = orderId;
|
this.orderId = orderId;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder orderDetails(ProductOrder orderDetails) {
|
||||||
|
this.orderDetails = orderDetails;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public ProductOrderReceipt build() {
|
public ProductOrderReceipt build() {
|
||||||
return new ProductOrderReceipt(orderId);
|
return new ProductOrderReceipt(orderId,orderDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder fromAddress(ProductOrderReceipt in) {
|
public static Builder fromAddress(ProductOrderReceipt in) {
|
||||||
return ProductOrderReceipt.builder().orderId(in.getOrderId());
|
return ProductOrderReceipt.builder().orderId(in.getOrderId()).orderDetails(in.getOrderDetails());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int orderId = -1;
|
private int orderId = -1;
|
||||||
|
private ProductOrder orderDetails;
|
||||||
|
|
||||||
// for deserializer
|
// for deserializer
|
||||||
ProductOrderReceipt() {
|
ProductOrderReceipt() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProductOrderReceipt(int orderId) {
|
public ProductOrderReceipt(int orderId,ProductOrder orderDetails) {
|
||||||
this.orderId = orderId;
|
this.orderId = orderId;
|
||||||
|
this.orderDetails = orderDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -69,6 +77,16 @@ public class ProductOrderReceipt implements Comparable<ProductOrderReceipt> {
|
||||||
return orderId;
|
return orderId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a copy of the SoftLayer_Container_Product_Order
|
||||||
|
* which holds all the data related to an order.
|
||||||
|
* This will only return when an order is processed successfully.
|
||||||
|
* It will contain all the items in an order as well as the order totals.
|
||||||
|
*/
|
||||||
|
public ProductOrder getOrderDetails() {
|
||||||
|
return orderDetails;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder toBuilder() {
|
public Builder toBuilder() {
|
||||||
return Builder.fromAddress(this);
|
return Builder.fromAddress(this);
|
||||||
}
|
}
|
||||||
|
@ -97,7 +115,7 @@ public class ProductOrderReceipt implements Comparable<ProductOrderReceipt> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[orderId=" + orderId + "]";
|
return "[orderId=" + orderId + ", orderDetails="+orderDetails+"]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,12 @@ 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";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* number of milliseconds to wait for an order to arrive on the api.
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY_SOFTLAYER_VIRTUALGUEST_ORDER_DELAY = "jclouds.softlayer.virtualguest.order-delay";
|
||||||
|
|
||||||
public static final Set<ProductItemPrice> DEFAULT_VIRTUAL_GUEST_PRICES = ImmutableSet.<ProductItemPrice>builder()
|
public static final Set<ProductItemPrice> DEFAULT_VIRTUAL_GUEST_PRICES = ImmutableSet.<ProductItemPrice>builder()
|
||||||
.add(ProductItemPrice.builder().id(1639).build()) // 100 GB (SAN)
|
.add(ProductItemPrice.builder().id(1639).build()) // 100 GB (SAN)
|
||||||
.add(ProductItemPrice.builder().id(21).build()) // 1 IP Address
|
.add(ProductItemPrice.builder().id(21).build()) // 1 IP Address
|
||||||
|
|
|
@ -24,6 +24,7 @@ import static org.testng.Assert.assertEquals;
|
||||||
import static org.testng.Assert.assertFalse;
|
import static org.testng.Assert.assertFalse;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jclouds.compute.domain.ExecResponse;
|
import org.jclouds.compute.domain.ExecResponse;
|
||||||
|
@ -66,7 +67,7 @@ public class SoftLayerComputeServiceAdapterLiveTest extends BaseSoftLayerClientL
|
||||||
@Test
|
@Test
|
||||||
public void testCreateNodeWithGroupEncodedIntoNameThenStoreCredentials() {
|
public void testCreateNodeWithGroupEncodedIntoNameThenStoreCredentials() {
|
||||||
String group = "foo";
|
String group = "foo";
|
||||||
String name = "foo-ef4";
|
String name = "node"+new Random().nextInt();
|
||||||
Template template = computeContext.getComputeService().templateBuilder()
|
Template template = computeContext.getComputeService().templateBuilder()
|
||||||
.locationId("3") // the default (singapore) doesn't work.
|
.locationId("3") // the default (singapore) doesn't work.
|
||||||
.build();
|
.build();
|
||||||
|
|
Loading…
Reference in New Issue