/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.provisionr.amazon; import com.google.common.base.Stopwatch; import java.io.IOException; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import net.schmizz.sshj.SSHClient; import net.schmizz.sshj.connection.channel.direct.Session; import org.apache.provisionr.amazon.options.ProviderOptions; import org.apache.provisionr.api.Provisionr; import org.apache.provisionr.api.access.AdminAccess; import org.apache.provisionr.api.hardware.Hardware; import org.apache.provisionr.api.network.Network; import org.apache.provisionr.api.network.Protocol; import org.apache.provisionr.api.network.Rule; import org.apache.provisionr.api.pool.Machine; import org.apache.provisionr.api.pool.Pool; import org.apache.provisionr.api.provider.Provider; import org.apache.provisionr.api.software.Software; import org.apache.provisionr.core.PoolStatus; import org.apache.provisionr.core.Ssh; import org.apache.provisionr.core.templates.PoolTemplate; import static org.apache.provisionr.test.KarafTests.installProvisionrFeatures; import static org.apache.provisionr.test.KarafTests.installProvisionrTestSupportBundle; import static org.apache.provisionr.test.KarafTests.passThroughAllSystemPropertiesWithPrefix; import static org.apache.provisionr.test.KarafTests.useDefaultKarafAsInProjectWithJunitBundles; import org.apache.provisionr.test.ProvisionrLiveTestSupport; import static org.junit.Assert.assertTrue; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.junit.Configuration; import org.ops4j.pax.exam.junit.ExamReactorStrategy; import org.ops4j.pax.exam.junit.JUnit4TestRunner; import org.ops4j.pax.exam.spi.reactors.AllConfinedStagedReactorFactory; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @RunWith(JUnit4TestRunner.class) @ExamReactorStrategy(AllConfinedStagedReactorFactory.class) public class AmazonProvisionrLiveTest extends ProvisionrLiveTestSupport { public static final Logger LOG = LoggerFactory.getLogger(AmazonProvisionrLiveTest.class); public static final int TEST_POOL_SIZE = 2; public static final String TEST_SPOT_BID_PRICE = "0.04"; public static final String TEST_INSTANCE_TYPE = "t1.micro"; public static final String TEST_POOL_TEMPLATE = "jenkins"; public AmazonProvisionrLiveTest() { super(AmazonProvisionr.ID); } @Configuration public Option[] configuration() throws Exception { return new Option[]{ useDefaultKarafAsInProjectWithJunitBundles(), passThroughAllSystemPropertiesWithPrefix("test.amazon."), installProvisionrFeatures("provisionr-amazon", "provisionr-examples"), installProvisionrTestSupportBundle() }; } @Test public void startProvisioningProcessForOnDemandInstances() throws Exception { startProvisioningProcess(null); } @Test public void startProvisioningProcessForSpotInstances() throws Exception { startProvisioningProcess(TEST_SPOT_BID_PRICE); } private void startProvisioningProcess(String spotBid) throws Exception { waitForProcessDeployment(AmazonProvisionr.MANAGEMENT_PROCESS_KEY); final Provisionr provisionr = getOsgiService(Provisionr.class, 5000); Provider provider = collectProviderCredentialsFromSystemProperties() .option(ProviderOptions.REGION, getProviderProperty( ProviderOptions.REGION, ProviderOptions.DEFAULT_REGION)) .createProvider(); if (spotBid != null) { provider = provider.toBuilder() .option(ProviderOptions.SPOT_BID, spotBid) .createProvider(); } final Network network = Network.builder().addRules( Rule.builder().anySource().icmp().createRule(), Rule.builder().anySource().port(22).protocol(Protocol.TCP).createRule() ).createNetwork(); final Hardware hardware = Hardware.builder().type(TEST_INSTANCE_TYPE).createHardware(); final AdminAccess adminAccess = AdminAccess.builder().asCurrentUser().createAdminAccess(); final String destinationPath = "/home/" + adminAccess.getUsername() + "/provisionr.html"; final Software software = Software.builder() .imageId("default") .file("http://provisionr.incubator.apache.org", destinationPath) .createSoftware(); PoolTemplate template = getPoolTemplateWithId(TEST_POOL_TEMPLATE, 5000); final Pool pool = template.apply(Pool.builder() .provider(provider) .network(network) .adminAccess(adminAccess) .software(software) .hardware(hardware) .minSize(TEST_POOL_SIZE) .expectedSize(TEST_POOL_SIZE) .createPool()); final String businessKey = "j-" + UUID.randomUUID().toString(); String processInstanceId = provisionr.startPoolManagementProcess(businessKey, pool); try { waitForPoolStatus(provisionr, businessKey, PoolStatus.READY); List<Machine> machines = provisionr.getMachines(businessKey); assertTrue(machines.size() >= TEST_POOL_SIZE && machines.size() <= TEST_POOL_SIZE); for (Machine machine : machines) { assertSshCommand(machine, adminAccess, "test -f " + destinationPath); /* These are added through the Jenkins Debian template */ assertSshCommand(machine, adminAccess, "hash git >/dev/null 2>&1"); assertSshCommand(machine, adminAccess, "hash java >/dev/null 2>&1"); assertSshCommand(machine, adminAccess, "test -f /etc/apt/sources.list.d/jenkins.list"); } } finally { provisionr.destroyPool(businessKey); waitForPoolStatus(provisionr, businessKey, PoolStatus.TERMINATED); waitForProcessEnd(processInstanceId); } } private PoolTemplate getPoolTemplateWithId(String templateId, int timeoutInMilliseconds) throws TimeoutException, InterruptedException { ServiceTracker tracker = new ServiceTracker(bundleContext, PoolTemplate.class.getCanonicalName(), null); tracker.open(true); try { Stopwatch stopwatch = new Stopwatch().start(); while (stopwatch.elapsedMillis() < timeoutInMilliseconds) { for (Object candidate : tracker.getServices()) { if (PoolTemplate.class.cast(candidate).getId().equals(templateId)) { return PoolTemplate.class.cast(candidate); } } TimeUnit.MILLISECONDS.sleep(100); } throw new TimeoutException(String.format("Status check timed out after %d milliseconds", stopwatch.elapsedMillis())); } finally { tracker.close(); } } private void assertSshCommand(Machine machine, AdminAccess adminAccess, String bashCommand) throws IOException { LOG.info("Checking return code for command '{}' on machine {}", bashCommand, machine.getExternalId()); SSHClient client = Ssh.newClient(machine, adminAccess); try { Session session = client.startSession(); try { session.allocateDefaultPTY(); Session.Command command = session.exec(bashCommand); command.join(); assertTrue("Exit code was " + command.getExitStatus() + " for command " + bashCommand, command.getExitStatus() == 0); } finally { session.close(); } } finally { client.close(); } } private void waitForPoolStatus(Provisionr provisionr, String businessKey, String expectedStatus) throws InterruptedException, TimeoutException { String status; for (int i = 0; i < 120; i++) { try { status = provisionr.getStatus(businessKey); } catch (NoSuchElementException e) { LOG.info(String.format("Pool management process not found with key %s. " + "Assuming process terminated as expected.", businessKey)); return; /* The process ended as expected */ } if (status.equals(expectedStatus)) { LOG.info("Pool status is '{}'. Advancing.", status); return; } else { LOG.info("Pool status is '{}'. Waiting 10s for '{}'. Try {}/120", new Object[]{status, expectedStatus, i}); TimeUnit.SECONDS.sleep(10); } } throw new TimeoutException("Status check timed out after 20 minutes"); } }