From 474d95ba223cb0746198b215cd94386db5dcc36d Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Thu, 22 Oct 2015 20:58:08 +0100 Subject: [PATCH 1/2] Adds Exceptions.getFirstThrowableMatching --- .../brooklyn/util/exceptions/Exceptions.java | 5 + .../util/exceptions/ExceptionsTest.java | 97 ++++++++++++++++++- 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java index c185890bd2..9439faaf89 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/exceptions/Exceptions.java @@ -124,6 +124,11 @@ public static T getFirstThrowableOfType(Throwable from, Cl return (T) Iterables.tryFind(getCausalChain(from), instanceOf(clazz)).orNull(); } + /** returns the first exception that matches the filter, or null */ + public static Throwable getFirstThrowableMatching(Throwable from, Predicate filter) { + return Iterables.tryFind(getCausalChain(from), filter).orNull(); + } + /** returns the first exception in the call chain which is not of common uninteresting types * (ie excluding ExecutionException and PropagatedRuntimeExceptions); * or the original throwable if all are uninteresting diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java index 13e077f7a9..0e7d590552 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/exceptions/ExceptionsTest.java @@ -18,22 +18,113 @@ */ package org.apache.brooklyn.util.exceptions; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + import java.util.ConcurrentModificationException; import java.util.concurrent.ExecutionException; import org.apache.brooklyn.util.collections.MutableSet; -import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.exceptions.ExceptionsTest; -import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.Test; +import com.google.common.base.Predicates; + public class ExceptionsTest { private static final Logger log = LoggerFactory.getLogger(ExceptionsTest.class); + @Test + public void testPropagateRuntimeException() throws Exception { + NullPointerException tothrow = new NullPointerException("simulated"); + try { + throw Exceptions.propagate(tothrow); + } catch (NullPointerException e) { + assertEquals(e, tothrow); + } + } + + @Test + public void testPropagateCheckedException() throws Exception { + Exception tothrow = new Exception("simulated"); + try { + throw Exceptions.propagate(tothrow); + } catch (RuntimeException e) { + assertEquals(e.getCause(), tothrow); + } + } + + @Test + public void testPropagateIfFatalPropagatesInterruptedException() throws Exception { + InterruptedException tothrow = new InterruptedException("simulated"); + try { + Exceptions.propagateIfFatal(tothrow); + fail(); + } catch (RuntimeException e) { + assertTrue(Thread.interrupted()); // note this clears the interrupted flag as well + assertEquals(e.getCause(), tothrow); + } + } + + @Test + public void testPropagateIfFatalPropagatesRuntimeInterruptedException() throws Exception { + RuntimeInterruptedException tothrow = new RuntimeInterruptedException(new InterruptedException("simulated")); + try { + Exceptions.propagateIfFatal(tothrow); + fail(); + } catch (RuntimeInterruptedException e) { + assertTrue(Thread.interrupted()); // note this clears the interrupted flag as well + assertEquals(e, tothrow); + } + } + + @Test + public void testPropagateIfFatalPropagatesError() throws Exception { + Error tothrow = new Error(); + try { + Exceptions.propagateIfFatal(tothrow); + fail(); + } catch (Error e) { + assertEquals(e, tothrow); + } + } + + @Test + public void testPropagateIfFatalDoesNotPropagatesNormalException() throws Exception { + Exception e = new Exception(); + Exceptions.propagateIfFatal(e); + + RuntimeException re = new RuntimeException(); + Exceptions.propagateIfFatal(re); + + Throwable t = new Throwable(); + Exceptions.propagateIfFatal(t); + } + + @Test + public void testGetFirstThrowableOfType() throws Exception { + NullPointerException npe = new NullPointerException("simulated"); + IllegalStateException ise = new IllegalStateException("simulated", npe); + + assertEquals(Exceptions.getFirstThrowableOfType(ise, IllegalStateException.class), ise); + assertEquals(Exceptions.getFirstThrowableOfType(ise, NullPointerException.class), npe); + assertNull(Exceptions.getFirstThrowableOfType(ise, IndexOutOfBoundsException.class)); + } + + @Test + public void testGetFirstThrowableMatching() throws Exception { + NullPointerException npe = new NullPointerException("simulated"); + IllegalStateException ise = new IllegalStateException("simulated", npe); + + assertEquals(Exceptions.getFirstThrowableMatching(ise, Predicates.instanceOf(IllegalStateException.class)), ise); + assertEquals(Exceptions.getFirstThrowableMatching(ise, Predicates.instanceOf(NullPointerException.class)), npe); + assertNull(Exceptions.getFirstThrowableMatching(ise, Predicates.alwaysFalse())); + } + @Test public void test12CollapseCompound() throws Exception { RuntimeException e = Exceptions.create("test1", MutableSet.of(new IllegalStateException("test2"), new IllegalStateException("test3"))); From 790f55cb0120b47fe942519b90b1bb554a367dab Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Thu, 22 Oct 2015 20:59:15 +0100 Subject: [PATCH 2/2] Adds SetHostnameCustomizer.MACHINE_FILTER config Defaults to only executing against machines of type SshMachineLocation. But can be set to exclude other machines as needed. --- .../entity/machine/SetHostnameCustomizer.java | 38 ++++++++-- .../machine/SetHostnameCustomizerTest.java | 74 ++++++++++++++++++- 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java index 56d2bcdbff..54ffa723ed 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java @@ -20,6 +20,8 @@ import static com.google.common.base.Preconditions.checkArgument; +import java.util.Arrays; + import org.apache.brooklyn.api.location.BasicMachineLocationCustomizer; import org.apache.brooklyn.api.location.MachineLocation; import org.apache.brooklyn.config.ConfigKey; @@ -38,8 +40,11 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import com.google.common.reflect.TypeToken; /** * Sets the hostname on an ssh'able machine. Currently only CentOS and RHEL are supported. @@ -86,6 +91,12 @@ public class SetHostnameCustomizer extends BasicMachineLocationCustomizer { "hostname.local.address", "Host address, as known on the local box. Config is set on the location."); + public static final ConfigKey> MACHINE_FILTER = ConfigKeys.newConfigKey( + new TypeToken>() {}, + "machineFilter", + "A filter to say which machines this should be applied to", + Predicates.instanceOf(SshMachineLocation.class)); + private final ConfigBag config; public SetHostnameCustomizer(ConfigBag config) { @@ -95,11 +106,23 @@ public SetHostnameCustomizer(ConfigBag config) { @Override public void customize(MachineLocation machine) { - String localHostname = setLocalHostname((SshMachineLocation) machine); - machine.config().set(LOCAL_HOSTNAME, localHostname); + if (config.get(MACHINE_FILTER).apply(machine)) { + log.info("SetHostnameCustomizer setting hostname on "+machine); + } else { + log.info("SetHostnameCustomizer ignoring non-ssh machine "+machine); + return; + } - String localIp = execHostnameMinusI((SshMachineLocation) machine); - machine.config().set(LOCAL_IP, localIp); + try { + String localHostname = setLocalHostname((SshMachineLocation) machine); + machine.config().set(LOCAL_HOSTNAME, localHostname); + + String localIp = execHostnameMinusI((SshMachineLocation) machine); + machine.config().set(LOCAL_IP, localIp); + } catch (Exception e) { + log.info("SetHostnameCustomizer failed to set hostname on "+machine+" (rethrowing)", e); + throw e; + } } protected String generateHostname(SshMachineLocation machine) { @@ -200,6 +223,11 @@ protected String execHostnameMinusI(SshMachineLocation machine) { protected ProcessTaskWrapper exec(SshMachineLocation machine, boolean asRoot, String... cmds) { SshEffectorTaskFactory taskFactory = SshEffectorTasks.ssh(machine, cmds); if (asRoot) taskFactory.runAsRoot(); - return DynamicTasks.queue(taskFactory).block(); + ProcessTaskWrapper result = DynamicTasks.queue(taskFactory).block(); + if (result.get() != 0) { + throw new IllegalStateException("SetHostnameCustomizer got exit code "+result.get()+" executing on machine "+machine + +"; cmds="+Arrays.asList(cmds)+"; stdout="+result.getStdout()+"; stderr="+result.getStderr()); + } + return result; } } diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerTest.java index 11981ce1e8..2a360170da 100644 --- a/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerTest.java +++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerTest.java @@ -19,17 +19,32 @@ package org.apache.brooklyn.entity.machine; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.fail; -import java.net.InetAddress; +import java.util.List; +import java.util.Map; import java.util.Set; +import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.LocationSpec; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.core.entity.BrooklynConfigKeys; +import org.apache.brooklyn.core.location.Machines; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation; +import org.apache.brooklyn.location.jclouds.JcloudsLocation; import org.apache.brooklyn.location.ssh.SshMachineLocation; +import org.apache.brooklyn.location.winrm.WinRmMachineLocation; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.StringPredicates; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import com.google.common.base.Functions; +import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -42,7 +57,7 @@ public void setUp() throws Exception { super.setUp(); customizer = new SetHostnameCustomizer(ConfigBag.newInstance()); } - + @Test public void testGeneratedHostnameUsesPrivateIp() throws Exception { SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class) @@ -76,6 +91,52 @@ public void testGeneratedHostnameIfNoIps() throws Exception { assertEquals(customizer.generateHostname(machine), "ip-none-"+machine.getId()); } + + @Test + public void testCustomizerIgnoresNonSshMachines() throws Exception { + WinRmMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(WinRmMachineLocation.class) + .configure("address", "4.3.2.1")); + + // Confirm not called (as that would cause error); and visual inspection that logs a nice message. + customizer.customize(machine); + } + + @Test + public void testCustomizerIgnoresNonMatchingMachine() throws Exception { + SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocationThrowsException.class) + .configure("address", "4.3.2.1")); + + customizer = new SetHostnameCustomizer(ConfigBag.newInstance() + .configure(SetHostnameCustomizer.MACHINE_FILTER, Predicates.not(Predicates.equalTo(machine)))); + + // Confirm not called (as that would cause error); and visual inspection that logs a nice message. + customizer.customize(machine); + } + + @Test + public void testCustomizerPropagatesException() throws Exception { + SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocationThrowsException.class) + .configure("address", "4.3.2.1")); + + FixedListMachineProvisioningLocation provisioningLoc = mgmt.getLocationManager().createLocation(LocationSpec.create(FixedListMachineProvisioningLocation.class) + .configure("machines", MutableSet.of(machine))); + + MachineEntity entity = app.createAndManageChild(EntitySpec.create(MachineEntity.class) + .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true) + .configure(MachineEntity.PROVISIONING_PROPERTIES.subKey(JcloudsLocation.MACHINE_LOCATION_CUSTOMIZERS.getName()), ImmutableSet.of(customizer))); + + try { + entity.start(ImmutableList.of(provisioningLoc)); + fail(); + } catch (RuntimeException e) { + if (Exceptions.getFirstThrowableMatching(e, Predicates.compose(StringPredicates.containsLiteral("simulated failure"), Functions.toStringFunction())) == null) { + throw e; + }; + assertFalse(Machines.findUniqueMachineLocation(entity.getLocations(), SshMachineLocation.class).isPresent()); + } + + } + public static class SshMachineLocationWithNoPublicIps extends SshMachineLocation { public SshMachineLocationWithNoPublicIps() { } @@ -84,4 +145,13 @@ public Set getPublicAddresses() { return ImmutableSet.of(); } } + + public static class SshMachineLocationThrowsException extends SshMachineLocation { + public SshMachineLocationThrowsException() { + } + @Override + public int execScript(Map props, String summaryForLogging, List commands, Map env) { + throw new RuntimeException("simulated failure"); + } + } }