/* * 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.brooklyn.feed.shell; import static org.testng.Assert.assertTrue; import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.entity.EntityInternal.FeedSupport; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.feed.function.FunctionFeedTest; import org.apache.brooklyn.feed.shell.ShellFeed; import org.apache.brooklyn.feed.shell.ShellFeedIntegrationTest; import org.apache.brooklyn.feed.shell.ShellPollConfig; import org.apache.brooklyn.feed.ssh.SshPollValue; import org.apache.brooklyn.feed.ssh.SshValueFunctions; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.test.EntityTestUtils; import org.apache.brooklyn.util.stream.Streams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; public class ShellFeedIntegrationTest extends BrooklynAppUnitTestSupport { private static final Logger log = LoggerFactory.getLogger(ShellFeedIntegrationTest.class); final static AttributeSensor<String> SENSOR_STRING = Sensors.newStringSensor("aString", ""); final static AttributeSensor<Integer> SENSOR_INT = Sensors.newIntegerSensor("anInt", ""); final static AttributeSensor<Long> SENSOR_LONG = Sensors.newLongSensor("aLong", ""); private LocalhostMachineProvisioningLocation loc; private EntityLocal entity; private ShellFeed feed; @BeforeMethod(alwaysRun=true) @Override public void setUp() throws Exception { super.setUp(); loc = new LocalhostMachineProvisioningLocation(); entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)); app.start(ImmutableList.of(loc)); } @AfterMethod(alwaysRun=true) @Override public void tearDown() throws Exception { if (feed != null) feed.stop(); super.tearDown(); if (loc != null) Streams.closeQuietly(loc); } @Test(groups="Integration") public void testReturnsShellExitStatus() throws Exception { feed = ShellFeed.builder() .entity(entity) .poll(new ShellPollConfig<Integer>(SENSOR_INT) .command("exit 123") .onFailure(SshValueFunctions.exitStatus())) .build(); EntityTestUtils.assertAttributeEqualsEventually(entity, SENSOR_INT, 123); } @Test(groups="Integration") public void testFeedDeDupe() throws Exception { testReturnsShellExitStatus(); entity.addFeed(feed); log.info("Feed 0 is: "+feed); testReturnsShellExitStatus(); log.info("Feed 1 is: "+feed); entity.addFeed(feed); FeedSupport feeds = ((EntityInternal)entity).feeds(); Assert.assertEquals(feeds.getFeeds().size(), 1, "Wrong feed count: "+feeds.getFeeds()); } // TODO timeout no longer supported; would be nice to have a generic task-timeout feature, // now that the underlying impl uses SystemProcessTaskFactory @Test(enabled=false, groups={"Integration", "WIP"}) public void testShellTimesOut() throws Exception { feed = ShellFeed.builder() .entity(entity) .poll(new ShellPollConfig<String>(SENSOR_STRING) .command("sleep 10") .timeout(1, TimeUnit.MILLISECONDS) .onException(new FunctionFeedTest.ToStringFunction())) .build(); Asserts.succeedsEventually(new Runnable() { public void run() { String val = entity.getAttribute(SENSOR_STRING); assertTrue(val != null && val.contains("timed out after 1ms"), "val=" + val); }}); } @Test(groups="Integration") public void testShellUsesEnv() throws Exception { feed = ShellFeed.builder() .entity(entity) .poll(new ShellPollConfig<String>(SENSOR_STRING) .env(ImmutableMap.of("MYENV", "MYVAL")) .command("echo hello $MYENV") .onSuccess(SshValueFunctions.stdout())) .build(); Asserts.succeedsEventually(new Runnable() { public void run() { String val = entity.getAttribute(SENSOR_STRING); assertTrue(val != null && val.contains("hello MYVAL"), "val="+val); }}); } @Test(groups="Integration") public void testReturnsShellStdout() throws Exception { feed = ShellFeed.builder() .entity(entity) .poll(new ShellPollConfig<String>(SENSOR_STRING) .command("echo hello") .onSuccess(SshValueFunctions.stdout())) .build(); Asserts.succeedsEventually(new Runnable() { public void run() { String val = entity.getAttribute(SENSOR_STRING); assertTrue(val != null && val.contains("hello"), "val="+val); }}); } @Test(groups="Integration") public void testReturnsShellStderr() throws Exception { final String cmd = "thiscommanddoesnotexist"; feed = ShellFeed.builder() .entity(entity) .poll(new ShellPollConfig<String>(SENSOR_STRING) .command(cmd) .onFailure(SshValueFunctions.stderr())) .build(); Asserts.succeedsEventually(new Runnable() { public void run() { String val = entity.getAttribute(SENSOR_STRING); assertTrue(val != null && val.contains(cmd), "val="+val); }}); } @Test(groups="Integration") public void testFailsOnNonZero() throws Exception { feed = ShellFeed.builder() .entity(entity) .poll(new ShellPollConfig<String>(SENSOR_STRING) .command("exit 123") .onSuccess(new Function<SshPollValue, String>() { @Override public String apply(SshPollValue input) { return "Exit status (on success) " + input.getExitStatus(); }}) .onFailure(new Function<SshPollValue, String>() { @Override public String apply(SshPollValue input) { return "Exit status (on failure) " + input.getExitStatus(); }})) .build(); Asserts.succeedsEventually(new Runnable() { public void run() { String val = entity.getAttribute(SENSOR_STRING); assertTrue(val != null && val.contains("Exit status (on failure) 123"), "val="+val); }}); } // Example in ShellFeed javadoc @Test(groups="Integration") public void testDiskUsage() throws Exception { feed = ShellFeed.builder() .entity(entity) .poll(new ShellPollConfig<Long>(SENSOR_LONG) .command("df -P | tail -1") .onSuccess(new Function<SshPollValue, Long>() { public Long apply(SshPollValue input) { String[] parts = input.getStdout().split("[ \\t]+"); System.out.println("input="+input+"; parts="+Arrays.toString(parts)); return Long.parseLong(parts[2]); }})) .build(); Asserts.succeedsEventually(new Runnable() { public void run() { Long val = entity.getAttribute(SENSOR_LONG); assertTrue(val != null && val >= 0, "val="+val); }}); } }