/* * 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.ssh; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.apache.brooklyn.api.entity.EntityInitializer; 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.Attributes; import org.apache.brooklyn.core.entity.EntityAsserts; 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.ssh.SshFeed; import org.apache.brooklyn.feed.ssh.SshFeedIntegrationTest; import org.apache.brooklyn.feed.ssh.SshPollConfig; import org.apache.brooklyn.feed.ssh.SshPollValue; import org.apache.brooklyn.feed.ssh.SshValueFunctions; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.stream.Streams; import org.apache.brooklyn.util.text.StringFunctions; import org.apache.brooklyn.util.text.StringPredicates; 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 org.apache.brooklyn.location.ssh.SshMachineLocation; import com.google.common.base.Function; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; public class SshFeedIntegrationTest extends BrooklynAppUnitTestSupport { private static final Logger log = LoggerFactory.getLogger(SshFeedIntegrationTest.class); final static AttributeSensor<String> SENSOR_STRING = Sensors.newStringSensor("aString", ""); final static AttributeSensor<Integer> SENSOR_INT = Sensors.newIntegerSensor("aLong", ""); private LocalhostMachineProvisioningLocation loc; private SshMachineLocation machine; private TestEntity entity; private SshFeed feed; @BeforeMethod(alwaysRun=true) @Override public void setUp() throws Exception { super.setUp(); loc = app.newLocalhostProvisioningLocation(); machine = loc.obtain(); 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); } /** this is one of the most common pattern */ @Test(groups="Integration") public void testReturnsSshStdoutAndInfersMachine() throws Exception { final TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class) // inject the machine location, because the app was started with a provisioning location // and TestEntity doesn't provision .location(machine)); feed = SshFeed.builder() .entity(entity2) .poll(new SshPollConfig<String>(SENSOR_STRING) .command("echo hello") .onSuccess(SshValueFunctions.stdout())) .build(); EntityAsserts.assertAttributeEventuallyNonNull(entity2, SENSOR_STRING); String val = entity2.getAttribute(SENSOR_STRING); Assert.assertTrue(val.contains("hello"), "val="+val); Assert.assertEquals(val.trim(), "hello"); } @Test(groups="Integration") public void testFeedDeDupe() throws Exception { testReturnsSshStdoutAndInfersMachine(); entity.addFeed(feed); log.info("Feed 0 is: "+feed); testReturnsSshStdoutAndInfersMachine(); 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()); } @Test(groups="Integration") public void testReturnsSshExitStatus() throws Exception { feed = SshFeed.builder() .entity(entity) .machine(machine) .poll(new SshPollConfig<Integer>(SENSOR_INT) .command("exit 123") .checkSuccess(Predicates.alwaysTrue()) .onSuccess(SshValueFunctions.exitStatus())) .build(); EntityAsserts.assertAttributeEqualsEventually(entity, SENSOR_INT, 123); } @Test(groups="Integration") public void testReturnsSshStdout() throws Exception { feed = SshFeed.builder() .entity(entity) .machine(machine) .poll(new SshPollConfig<String>(SENSOR_STRING) .command("echo hello") .onSuccess(SshValueFunctions.stdout())) .build(); EntityAsserts.assertAttributeEventually(entity, SENSOR_STRING, Predicates.compose(Predicates.equalTo("hello"), StringFunctions.trim())); } @Test(groups="Integration") public void testReturnsSshStderr() throws Exception { final String cmd = "thiscommanddoesnotexist"; feed = SshFeed.builder() .entity(entity) .machine(machine) .poll(new SshPollConfig<String>(SENSOR_STRING) .command(cmd) .onFailure(SshValueFunctions.stderr())) .build(); EntityAsserts.assertAttributeEventually(entity, SENSOR_STRING, StringPredicates.containsLiteral(cmd)); } @Test(groups="Integration") public void testFailsOnNonZero() throws Exception { feed = SshFeed.builder() .entity(entity) .machine(machine) .poll(new SshPollConfig<String>(SENSOR_STRING) .command("exit 123") .onFailure(new Function<SshPollValue, String>() { @Override public String apply(SshPollValue input) { return "Exit status " + input.getExitStatus(); }})) .build(); EntityAsserts.assertAttributeEventually(entity, SENSOR_STRING, StringPredicates.containsLiteral("Exit status 123")); } @Test(groups="Integration") public void testAddedEarly() throws Exception { final TestEntity entity2 = app.addChild(EntitySpec.create(TestEntity.class) .location(machine) .addInitializer(new EntityInitializer() { @Override public void apply(EntityLocal entity) { SshFeed.builder() .entity(entity) .onlyIfServiceUp() .poll(new SshPollConfig<String>(SENSOR_STRING) .command("echo hello") .onSuccess(SshValueFunctions.stdout())) .build(); } })); // TODO would be nice to hook in and assert no errors EntityAsserts.assertAttributeEqualsContinually(entity2, SENSOR_STRING, null); entity2.sensors().set(Attributes.SERVICE_UP, true); EntityAsserts.assertAttributeEventually(entity2, SENSOR_STRING, StringPredicates.containsLiteral("hello")); } @Test(groups="Integration") public void testDynamicEnvAndCommandSupplier() throws Exception { final TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class).location(machine)); final AtomicInteger count = new AtomicInteger(); Supplier<Map<String, String>> envSupplier = new Supplier<Map<String,String>>() { @Override public Map<String, String> get() { return MutableMap.of("COUNT", ""+count.incrementAndGet()); } }; Supplier<String> cmdSupplier = new Supplier<String>() { @Override public String get() { return "echo count-"+count.incrementAndGet()+"-$COUNT"; } }; feed = SshFeed.builder() .entity(entity2) .poll(new SshPollConfig<String>(SENSOR_STRING) .env(envSupplier) .command(cmdSupplier) .onSuccess(SshValueFunctions.stdout())) .build(); EntityAsserts.assertAttributeEventuallyNonNull(entity2, SENSOR_STRING); final String val1 = assertDifferentOneInOutput(entity2); EntityAsserts.assertAttributeEventually(entity2, SENSOR_STRING, Predicates.not(Predicates.equalTo(val1))); final String val2 = assertDifferentOneInOutput(entity2); log.info("vals from dynamic sensors are: "+val1.trim()+" and "+val2.trim()); } private String assertDifferentOneInOutput(final TestEntity entity2) { String val = entity2.getAttribute(SENSOR_STRING); Assert.assertTrue(val.startsWith("count"), "val="+val); try { String[] fields = val.trim().split("-"); int field1 = Integer.parseInt(fields[1]); int field2 = Integer.parseInt(fields[2]); Assert.assertEquals(Math.abs(field2-field1), 1, "expected difference of 1"); } catch (Throwable t) { Exceptions.propagateIfFatal(t); Assert.fail("Wrong output from sensor, got '"+val.trim()+"', giving error: "+t); } return val; } }