/**
* 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.ibm.smartcloud;
import static com.google.common.collect.Iterables.filter;
import static org.jclouds.ibm.smartcloud.options.CreateInstanceOptions.Builder.authorizePublicKey;
import static org.jclouds.ibm.smartcloud.options.CreateInstanceOptions.Builder.secondaryIP;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpResponseException;
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
import org.jclouds.ibm.smartcloud.domain.Address;
import org.jclouds.ibm.smartcloud.domain.Image;
import org.jclouds.ibm.smartcloud.domain.Instance;
import org.jclouds.ibm.smartcloud.domain.Instance.Software;
import org.jclouds.ibm.smartcloud.domain.InstanceType;
import org.jclouds.ibm.smartcloud.domain.Key;
import org.jclouds.ibm.smartcloud.domain.Location;
import org.jclouds.ibm.smartcloud.domain.Offering;
import org.jclouds.ibm.smartcloud.domain.StorageOffering;
import org.jclouds.ibm.smartcloud.domain.StorageOffering.Format;
import org.jclouds.ibm.smartcloud.domain.Volume;
import org.jclouds.ibm.smartcloud.predicates.AddressFree;
import org.jclouds.ibm.smartcloud.predicates.InstanceActive;
import org.jclouds.ibm.smartcloud.predicates.InstanceActiveOrFailed;
import org.jclouds.ibm.smartcloud.predicates.InstanceRemovedOrNotFound;
import org.jclouds.ibm.smartcloud.predicates.VolumeUnmounted;
import org.jclouds.net.IPSocket;
import org.jclouds.predicates.InetSocketAddressConnect;
import org.jclouds.predicates.RetryablePredicate;
import org.jclouds.ssh.SshClient;
import org.jclouds.ssh.SshException;
import org.jclouds.sshj.SshjSshClient;
import org.testng.annotations.AfterTest;
import org.testng.annotations.Test;
import com.google.common.base.Predicate;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
/**
* Tests behavior of {@code IBMSmartCloudClient}
*
* @author Adrian Cole
*/
@Test(groups = "live", singleThreaded = true, testName = "IBMSmartCloudClientLiveTest")
public class IBMSmartCloudClientLiveTest extends BaseIBMSmartCloudClientLiveTest {
private static final String OS = "SUSE Linux Enterprise Server";
private static final String VERSION = "11 SP1";
private static final String PLATFORM = OS + "/" + VERSION;
private static final ImmutableSet<Software> SOFTWARE = ImmutableSet.<Software> of(new Software(OS, "OS", VERSION));
private static String FORMAT = "EXT3";
private Location location;
private Address ip;
private Key key;
private Volume volume;
private Instance instance2;
private Instance instance;
private InstanceType instanceType;
private Image image;
private StorageOffering cheapestStorage;
private static final String GROUP = System.getProperty("user.name");
@Test
public void testGetLocation() throws Exception {
Set<? extends Location> response = connection.listLocations();
assertNotNull(response);
if (response.size() > 0) {
location = Iterables.find(response, new Predicate<Location>() {
@Override
public boolean apply(Location arg0) {
// ontario is the least used
return "101".equals(arg0.getId());
}
});
assertEquals(connection.getLocation(location.getId()).getId(), location.getId());
}
}
@Test(dependsOnMethods = "testGetLocation")
public void testAddPublicKey() throws Exception {
try {
connection.addPublicKey(GROUP, keyPair.get("public"));
key = connection.getKey(GROUP);
try {
assert key.getInstanceIds().equals(ImmutableSet.<String> of()) : key;
} catch (AssertionError e) {
// inconsistency in the key api when recreating a key
// http://www-180.ibm.com/cloud/enterprise/beta/ram/community/discussionTopic.faces?guid={DA689AEE-783C-6FE7-6F9F-DFEE9763F806}&v=1&fid=1068&tid=1528
}
} catch (IllegalStateException e) {
// must have been found
connection.updatePublicKey(GROUP, keyPair.get("public"));
key = connection.getKey(GROUP);
for (String instanceId : key.getInstanceIds()) {
Instance instance = connection.getInstance(instanceId);
if (instance.getStatus() == Instance.Status.FAILED || instance.getStatus() == Instance.Status.ACTIVE) {
killInstance(instance.getId());
}
}
}
assertEquals(key.getName(), GROUP);
assert keyPair.get("public").indexOf(key.getKeyMaterial()) > 0;
assertNotNull(key.getLastModifiedTime());
}
@Test(dependsOnMethods = "resolveImageAndInstanceType")
public void testAllocateIpAddress() throws Exception {
Offering offering = Iterables.find(connection.listAddressOfferings(), new Predicate<Offering>() {
@Override
public boolean apply(Offering arg0) {
return arg0.getLocation().equals(location.getId());
}
});
try {
ip = connection.allocateAddressInLocation(location.getId(), offering.getId());
System.out.println(ip);
assertEquals(ip.getIP(), null);
// wait up to 30 seconds for this to become "free"
new RetryablePredicate<Address>(new AddressFree(connection), 30, 2, TimeUnit.SECONDS).apply(ip);
refreshIpAndReturnAllAddresses();
assertEquals(ip.getInstanceId(), null);
} catch (IllegalStateException e) {
if (HttpResponseException.class.cast(e.getCause()).getResponse().getStatusCode() == 409) {
ip = Iterables.find(connection.listAddresses(), new Predicate<Address>() {
@Override
public boolean apply(Address input) {
return input.getState() == Address.State.FREE;
}
});
} else {
throw e;
}
}
assertEquals(ip.getInstanceId(), null);
assertEquals(ip.getLocation(), location.getId());
Set<? extends Address> allAddresses = refreshIpAndReturnAllAddresses();
assert (allAddresses.contains(ip)) : String.format("ip %s not in %s", ip, allAddresses);
}
@Test(dependsOnMethods = "testGetLocation")
public void testResolveVolumeOffering() throws Exception {
Ordering<StorageOffering> cheapestOrdering = new Ordering<StorageOffering>() {
public int compare(StorageOffering left, StorageOffering right) {
return ComparisonChain.start().compare(left.getPrice().getRate(), right.getPrice().getRate()).result();
}
}.reverse();
Iterable<? extends StorageOffering> storageOfferingsThatAreInOurLocationAndCorrectFormat = filter(connection
.listStorageOfferings(), new Predicate<StorageOffering>() {
@Override
public boolean apply(StorageOffering arg0) {
return arg0.getLocation().equals(location.getId())
&& Iterables.any(arg0.getFormats(), new Predicate<StorageOffering.Format>() {
@Override
public boolean apply(Format arg0) {
return arg0.getId().equals(FORMAT);
}
});
}
});
cheapestStorage = cheapestOrdering.max(storageOfferingsThatAreInOurLocationAndCorrectFormat);
System.out.println(cheapestStorage);
}
@Test(dependsOnMethods = "testResolveVolumeOffering")
public void testCreateVolume() throws Exception {
try {
volume = connection.createVolumeInLocation(location.getId(), GROUP, FORMAT, cheapestStorage.getName(),
cheapestStorage.getId());
// wait up to 5 minutes for this to become "unmounted"
assert new RetryablePredicate<Volume>(new VolumeUnmounted(connection), 300, 5, TimeUnit.SECONDS).apply(volume);
} catch (IllegalStateException e) {
int code = HttpResponseException.class.cast(e.getCause()).getResponse().getStatusCode();
if (code == 409 || code == 500) {
Set<? extends Volume> volumes = connection.listVolumes();
try {
volume = Iterables.find(volumes, new Predicate<Volume>() {
@Override
public boolean apply(Volume input) {
return input.getState() == Volume.State.UNMOUNTED;
}
});
} catch (NoSuchElementException ex) {
killInstance(GROUP + 1);
}
} else {
throw e;
}
}
assertEquals(volume.getInstanceId(), null);
assertEquals(volume.getLocation(), location.getId());
final String id = volume.getId();
Set<? extends Volume> allVolumes = connection.listVolumes();
// refresh volume as it may have been just created
volume = Iterables.find(allVolumes, new Predicate<Volume>() {
@Override
public boolean apply(Volume input) {
return input.getId().equals(id);
}
});
assert (allVolumes.contains(volume)) : String.format("volume %s not in %s", volume, volume);
}
@Test(dependsOnMethods = "testGetLocation")
public void resolveImageAndInstanceType() throws Exception {
Iterable<? extends Image> imagesThatAreInOurLocationAndNotBYOL = filter(connection.listImages(),
new Predicate<Image>() {
@Override
public boolean apply(Image arg0) {
return arg0.getLocation().equals(location.getId()) && arg0.getPlatform().equals(PLATFORM)
&& arg0.getName().startsWith(OS);
}
});
Ordering<InstanceType> cheapestOrdering = new Ordering<InstanceType>() {
public int compare(InstanceType left, InstanceType right) {
return ComparisonChain.start().compare(left.getPrice().getRate(), right.getPrice().getRate()).result();
}
}.reverse();
Set<InstanceType> instanceTypes = Sets.newLinkedHashSet();
for (Image image : imagesThatAreInOurLocationAndNotBYOL)
Iterables.addAll(instanceTypes, image.getSupportedInstanceTypes());
instanceType = cheapestOrdering.max(instanceTypes);
final InstanceType cheapestInstanceType = instanceType;
System.err.println(cheapestInstanceType);
image = Iterables.find(imagesThatAreInOurLocationAndNotBYOL, new Predicate<Image>() {
@Override
public boolean apply(Image arg0) {
return arg0.getSupportedInstanceTypes().contains(cheapestInstanceType);
}
});
System.err.println(image);
System.err.println(connection.getManifestOfImage(image.getId()));
}
@Test(dependsOnMethods = { "testAddPublicKey", "resolveImageAndInstanceType" })
public void testCreateInstance() throws Exception {
killInstance(GROUP);
instance = connection.createInstanceInLocation(location.getId(), GROUP, image.getId(), instanceType.getId(),
authorizePublicKey(key.getName()).isMiniEphemeral(true));
assertBeginState(instance, GROUP);
assertIpHostNullAndStatusNEW(instance);
blockUntilRunning(instance);
instance = assertRunning(instance, GROUP);
sshAndDf(new IPSocket(instance.getPrimaryIP().getIP(), 22), new Credentials("idcuser", key.getKeyMaterial()));
}
private void killInstance(final String nameToKill) {
Set<? extends Instance> instances = connection.listInstances();
try {
Instance instance = Iterables.find(instances, new Predicate<Instance>() {
@Override
public boolean apply(Instance input) {
return input.getName().equals(nameToKill);
}
});
if (instance.getStatus() != Instance.Status.DEPROVISIONING
&& instance.getStatus() != Instance.Status.DEPROVISION_PENDING) {
System.out.println("deleting instance: " + instance);
int timeout = (instance.getStatus() == Instance.Status.NEW || instance.getStatus() == Instance.Status.PROVISIONING) ? 300
: 30;
assert new RetryablePredicate<Instance>(new InstanceActiveOrFailed(connection), timeout, 2,
TimeUnit.SECONDS).apply(instance) : instance;
connection.deleteInstance(instance.getId());
}
assert new RetryablePredicate<Instance>(new InstanceRemovedOrNotFound(connection), 120, 2, TimeUnit.SECONDS)
.apply(instance) : instance;
} catch (NoSuchElementException ex) {
}
}
private Instance assertRunning(Instance instance, String name) throws AssertionError {
instance = connection.getInstance(instance.getId());
try {
assertIpHostAndStatusACTIVE(instance);
assertConsistent(instance, name);
} catch (NullPointerException e) {
System.err.println(instance);
throw e;
} catch (AssertionError e) {
System.err.println(instance);
throw e;
}
System.err.println("RUNNING: " + instance);
return instance;
}
private void blockUntilRunning(Instance instance) {
long start = System.currentTimeMillis();
assert new RetryablePredicate<Instance>(new InstanceActive(connection), 15 * 60 * 1000).apply(instance) : connection
.getInstance(instance.getId());
System.out.println(((System.currentTimeMillis() - start) / 1000) + " seconds");
}
private void assertBeginState(Instance instance, String name) throws AssertionError {
try {
assertConsistent(instance, name);
} catch (NullPointerException e) {
System.err.println(instance);
throw e;
} catch (AssertionError e) {
killInstance(instance.getId());
throw e;
}
}
private void assertConsistent(Instance instance, String name) {
assert (instance.getId() != null) : instance;
assertEquals(instance.getName(), name);
assertEquals(instance.getInstanceType(), instanceType.getId());
assertEquals(instance.getLocation(), location.getId());
assertEquals(instance.getImageId(), image.getId());
assertEquals(instance.getSoftware(), SOFTWARE);
assertEquals(instance.getKeyName(), key.getName());
assertNotNull(instance.getLaunchTime());
assertNotNull(instance.getExpirationTime());
assertEquals(instance.getOwner(), identity);
assertEquals(instance.getProductCodes(), ImmutableSet.<String> of());
assertEquals(instance.getRequestName(), name);
assertNotNull(instance.getRequestId());
}
private void assertIpHostNullAndStatusNEW(Instance instance) {
assertEquals(instance.getPrimaryIP(), null);
assertEquals(instance.getStatus(), Instance.Status.NEW);
}
private void assertIpHostAndStatusNEW(Instance instance) {
assertNotNull(instance.getPrimaryIP());
assertEquals(instance.getStatus(), Instance.Status.NEW);
}
private void assertIpHostAndStatusACTIVE(Instance instance) {
assertNotNull(instance.getPrimaryIP());
assertEquals(instance.getStatus(), Instance.Status.ACTIVE);
}
@Test(dependsOnMethods = { "testAddPublicKey", "testAllocateIpAddress", "testCreateVolume",
"resolveImageAndInstanceType" })
public void testCreateInstanceWithIpAndVolume() throws Exception {
String name = GROUP + "1";
killInstance(name);
instance2 = connection.createInstanceInLocation(location.getId(), name, image.getId(), instanceType.getId(),
secondaryIP(ip.getId()).isMiniEphemeral(true).authorizePublicKey(key.getName()).mountVolume(
volume.getId(), "/mnt"));
assertBeginState(instance2, name);
assertIpHostAndStatusNEW(instance2);
blockUntilRunning(instance2);
instance2 = assertRunning(instance2, name);
volume = connection.getVolume(volume.getId());
assertEquals(volume.getInstanceId(), instance2.getId());
refreshIpAndReturnAllAddresses();
assertEquals(ip.getInstanceId(), instance2.getId());
assertEquals(ip.getIP(), instance2.getPrimaryIP().getIP());
sshAndDf(new IPSocket(instance2.getPrimaryIP().getIP(), 22), new Credentials("idcuser", keyPair.get("private")));
}
private Set<? extends Address> refreshIpAndReturnAllAddresses() {
Set<? extends Address> allAddresses = connection.listAddresses();
final String id = ip.getId();
// refresh address as it may have been just created
ip = Iterables.find(allAddresses, new Predicate<Address>() {
@Override
public boolean apply(Address input) {
return input.getId().equals(id);
}
});
return allAddresses;
}
@AfterTest(groups = { "live" })
void tearDown() {
if (volume != null)
try {
connection.deleteVolume(volume.getId());
} catch (Exception e) {
}
if (ip != null)
try {
connection.releaseAddress(ip.getId());
} catch (Exception e) {
}
if (key != null)
try {
connection.deleteKey(key.getName());
} catch (Exception e) {
}
if (instance != null)
try {
connection.deleteInstance(instance.getId());
} catch (Exception e) {
}
if (instance2 != null)
try {
connection.deleteInstance(instance2.getId());
} catch (Exception e) {
}
}
private void sshAndDf(IPSocket socket, Credentials credentials) throws IOException {
for (int i = 0; i < 5; i++) {// retry loop TODO replace with predicate.
try {
_sshAndDf(socket, credentials);
return;
} catch (SshException e) {
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e1) {
}
continue;
}
}
}
private void _sshAndDf(IPSocket socket, Credentials credentials) {
RetryablePredicate<IPSocket> socketOpen = new RetryablePredicate<IPSocket>(new InetSocketAddressConnect(), 180,
5, TimeUnit.SECONDS);
socketOpen.apply(socket);
SshClient ssh = new SshjSshClient(new BackoffLimitedRetryHandler(), socket, 60000, credentials.identity, null,
credentials.credential.getBytes());
try {
ssh.connect();
ExecResponse hello = ssh.exec("echo hello");
assertEquals(hello.getOutput().trim(), "hello");
ExecResponse exec = ssh.exec("df");
assertTrue(exec.getOutput().contains("Filesystem"),
"The output should've contained filesystem information, but it didn't. Output: " + exec);
} finally {
if (ssh != null)
ssh.disconnect();
}
}
}