You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2015/03/03 16:45:46 UTC

[1/3] incubator-brooklyn git commit: Fix DynamicCluster.replaceMember for MultiLocation

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master d2e1487fb -> e5800c9cb


Fix DynamicCluster.replaceMember for MultiLocation

- When using replaceMember with a SoftwareProcess, the member had
  two locations (the MachineProvisioningLocation and the 
  SshMachineLocation). This was causing an exception to be thrown.

Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/5a939a2b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/5a939a2b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/5a939a2b

Branch: refs/heads/master
Commit: 5a939a2bb4386a403938d8a50d870fb57239d8df
Parents: 217bd01
Author: Aled Sage <al...@gmail.com>
Authored: Fri Feb 20 14:23:58 2015 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Fri Feb 20 14:23:58 2015 +0000

----------------------------------------------------------------------
 .../entity/group/DynamicClusterImpl.java        |  37 +++---
 ...rWithAvailabilityZonesMultiLocationTest.java | 114 +++++++++++++++++++
 2 files changed, 138 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5a939a2b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
index b21f9fc..0b77910 100644
--- a/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
+++ b/core/src/main/java/brooklyn/entity/group/DynamicClusterImpl.java
@@ -23,6 +23,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -50,6 +51,7 @@ import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.entity.trait.Startable;
 import brooklyn.entity.trait.StartableMethods;
 import brooklyn.location.Location;
+import brooklyn.location.MachineProvisioningLocation;
 import brooklyn.location.basic.Locations;
 import brooklyn.location.cloud.AvailabilityZoneExtension;
 import brooklyn.management.Task;
@@ -71,6 +73,7 @@ import brooklyn.util.text.Strings;
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -455,23 +458,31 @@ public class DynamicClusterImpl extends AbstractGroupImpl implements DynamicClus
             if (isAvailabilityZoneEnabled()) {
                 // this member's location could be a machine provisioned by a sub-location, or the actual sub-location
                 List<Location> subLocations = findSubLocations(getLocation());
-                Location actualMemberLoc = checkNotNull(Iterables.getOnlyElement(member.getLocations()), "member's location (%s)", member);
-                Location contenderMemberLoc = actualMemberLoc;
+                Collection<Location> actualMemberLocs = member.getLocations();
                 boolean foundMatch = false;
-                do {
-                    if (subLocations.contains(contenderMemberLoc)) {
-                        memberLoc = contenderMemberLoc;
-                        foundMatch = true;
-                        LOG.debug("In {} replacing member {} ({}), inferred its sub-location is {}", new Object[] {this, memberId, member, memberLoc});
-                    }
-                    contenderMemberLoc = contenderMemberLoc.getParent();
-                } while (!foundMatch && contenderMemberLoc != null);
+                for (Iterator<Location> iter = actualMemberLocs.iterator(); !foundMatch && iter.hasNext();) {
+                    Location actualMemberLoc = iter.next();
+                    Location contenderMemberLoc = actualMemberLoc;
+                    do {
+                        if (subLocations.contains(contenderMemberLoc)) {
+                            memberLoc = contenderMemberLoc;
+                            foundMatch = true;
+                            LOG.debug("In {} replacing member {} ({}), inferred its sub-location is {}", new Object[] {this, memberId, member, memberLoc});
+                        }
+                        contenderMemberLoc = contenderMemberLoc.getParent();
+                    } while (!foundMatch && contenderMemberLoc != null);
+                }
                 if (!foundMatch) {
-                    memberLoc = actualMemberLoc;
-                    LOG.warn("In {} replacing member {} ({}), could not find matching sub-location; falling back to its actual location: {}", new Object[] {this, memberId, member, memberLoc});
+                    if (actualMemberLocs.isEmpty()) {
+                        memberLoc = subLocations.get(0);
+                        LOG.warn("In {} replacing member {} ({}), has no locations; falling back to first availability zone: {}", new Object[] {this, memberId, member, memberLoc});
+                    } else {
+                        memberLoc = Iterables.tryFind(actualMemberLocs, Predicates.instanceOf(MachineProvisioningLocation.class)).or(Iterables.getFirst(actualMemberLocs, null));
+                        LOG.warn("In {} replacing member {} ({}), could not find matching sub-location; falling back to its actual location: {}", new Object[] {this, memberId, member, memberLoc});
+                    }
                 } else if (memberLoc == null) {
                     // impossible to get here, based on logic above!
-                    throw new IllegalStateException("Unexpected condition! cluster="+this+"; member="+member+"; actualMemberLoc="+actualMemberLoc);
+                    throw new IllegalStateException("Unexpected condition! cluster="+this+"; member="+member+"; actualMemberLocs="+actualMemberLocs);
                 }
             } else {
                 memberLoc = getLocation();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5a939a2b/software/base/src/test/java/brooklyn/entity/group/DynamicClusterWithAvailabilityZonesMultiLocationTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/brooklyn/entity/group/DynamicClusterWithAvailabilityZonesMultiLocationTest.java b/software/base/src/test/java/brooklyn/entity/group/DynamicClusterWithAvailabilityZonesMultiLocationTest.java
new file mode 100644
index 0000000..9115d62
--- /dev/null
+++ b/software/base/src/test/java/brooklyn/entity/group/DynamicClusterWithAvailabilityZonesMultiLocationTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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 brooklyn.entity.group;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.util.List;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EmptySoftwareProcess;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.MachineLocation;
+import brooklyn.location.MachineProvisioningLocation;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.location.basic.MultiLocation;
+import brooklyn.test.Asserts;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/**
+ * Uses {@link SoftwareProcess}, so test can't be in core project.
+ * 
+ * Different from {@link DynamicClusterWithAvailabilityZonesTest} in the use of {@link MultiLocation}.
+ * However, the difference is important: the {@link SoftwareProcess} entity has two locations
+ * (the {@link MachineProvisioningLocation} and the {@link MachineLocation}, which was perviously
+ * causing a failure - now fixed and tested here.
+ */
+public class DynamicClusterWithAvailabilityZonesMultiLocationTest extends BrooklynAppUnitTestSupport {
+    
+    private DynamicCluster cluster;
+    
+    private LocalhostMachineProvisioningLocation subLoc1;
+    private LocalhostMachineProvisioningLocation subLoc2;
+    private MultiLocation<?> multiLoc;
+    
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+                .configure(DynamicCluster.ENABLE_AVAILABILITY_ZONES, true)
+                .configure(DynamicCluster.INITIAL_SIZE, 0)
+                .configure(DynamicCluster.MEMBER_SPEC, EntitySpec.create(EmptySoftwareProcess.class)));
+        
+        subLoc1 = app.newLocalhostProvisioningLocation(ImmutableMap.of("displayName", "loc1"));
+        subLoc2 = app.newLocalhostProvisioningLocation(ImmutableMap.of("displayName", "loc2"));
+        multiLoc = mgmt.getLocationManager().createLocation(LocationSpec.create(MultiLocation.class)
+                .configure(MultiLocation.SUB_LOCATIONS, ImmutableList.<MachineProvisioningLocation<?>>of(subLoc1, subLoc2)));
+    }
+
+    @Test
+    public void testReplacesEntityInSameZone() throws Exception {
+        ((EntityLocal)cluster).config().set(DynamicCluster.ENABLE_AVAILABILITY_ZONES, true);
+        cluster.start(ImmutableList.of(multiLoc));
+        
+        cluster.resize(4);
+        List<String> locsUsed = getLocationNames(getLocationsOf(cluster.getMembers(), Predicates.instanceOf(MachineProvisioningLocation.class)));
+        Asserts.assertEqualsIgnoringOrder(locsUsed, ImmutableList.of("loc1", "loc1", "loc2", "loc2"));
+
+        String idToRemove = Iterables.getFirst(cluster.getMembers(), null).getId();
+        String idAdded = cluster.replaceMember(idToRemove);
+        locsUsed = getLocationNames(getLocationsOf(cluster.getMembers(), Predicates.instanceOf(MachineProvisioningLocation.class)));
+        Asserts.assertEqualsIgnoringOrder(locsUsed, ImmutableList.of("loc1", "loc1", "loc2", "loc2"));
+        assertNull(Iterables.find(cluster.getMembers(), EntityPredicates.idEqualTo(idToRemove), null));
+        assertNotNull(Iterables.find(cluster.getMembers(), EntityPredicates.idEqualTo(idAdded), null));
+    }
+    
+    protected List<Location> getLocationsOf(Iterable<? extends Entity> entities, Predicate<? super Location> filter) {
+        List<Location> result = Lists.newArrayList();
+        for (Entity entity : entities) {
+            Iterables.addAll(result, Iterables.filter(entity.getLocations(), filter));
+        }
+        return result;
+    }
+
+    protected List<String> getLocationNames(Iterable<? extends Location> locs) {
+        List<String> result = Lists.newArrayList();
+        for (Location subLoc : locs) {
+            result.add(subLoc.getDisplayName());
+        }
+        return result;
+    }
+}


[3/3] incubator-brooklyn git commit: This closes #525

Posted by al...@apache.org.
This closes #525


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/e5800c9c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/e5800c9c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/e5800c9c

Branch: refs/heads/master
Commit: e5800c9cb3a2532141c8ddfc3f90b99b092d75c3
Parents: d2e1487 8e4e9e8
Author: Aled Sage <al...@gmail.com>
Authored: Tue Mar 3 15:45:25 2015 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Mar 3 15:45:25 2015 +0000

----------------------------------------------------------------------
 .../entity/group/DynamicClusterImpl.java        |  37 ++++--
 .../util/ssh/BashCommandsIntegrationTest.java   | 120 ++++++++++++++++++-
 .../basic/AbstractSoftwareProcessSshDriver.java |  22 ++++
 .../java/JavaSoftwareProcessSshDriver.java      |  18 +--
 ...rWithAvailabilityZonesMultiLocationTest.java | 114 ++++++++++++++++++
 .../java/brooklyn/util/ssh/BashCommands.java    |  87 ++++++++++++++
 6 files changed, 376 insertions(+), 22 deletions(-)
----------------------------------------------------------------------



[2/3] incubator-brooklyn git commit: Add BashCommands.setHostname

Posted by al...@apache.org.
Add BashCommands.setHostname

- A good implementation for CentOS/RHEL, and a poor implementation
  for everything else (because not persisted on reboot).
- Also adds BashCommands.prependToEtcHosts and .appendToEtcHosts
- checkJavaHostnameBug() also checks for no hostname.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/8e4e9e8b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/8e4e9e8b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/8e4e9e8b

Branch: refs/heads/master
Commit: 8e4e9e8bed6ebb2e1fb77d1500cd892d459c17e0
Parents: 5a939a2
Author: Aled Sage <al...@gmail.com>
Authored: Mon Feb 23 17:29:15 2015 +0000
Committer: Aled Sage <al...@gmail.com>
Committed: Tue Mar 3 15:44:55 2015 +0000

----------------------------------------------------------------------
 .../util/ssh/BashCommandsIntegrationTest.java   | 120 ++++++++++++++++++-
 .../basic/AbstractSoftwareProcessSshDriver.java |  22 ++++
 .../java/JavaSoftwareProcessSshDriver.java      |  18 +--
 .../java/brooklyn/util/ssh/BashCommands.java    |  87 ++++++++++++++
 4 files changed, 238 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8e4e9e8b/core/src/test/java/brooklyn/util/ssh/BashCommandsIntegrationTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/ssh/BashCommandsIntegrationTest.java b/core/src/test/java/brooklyn/util/ssh/BashCommandsIntegrationTest.java
index 523ae14..966856c 100644
--- a/core/src/test/java/brooklyn/util/ssh/BashCommandsIntegrationTest.java
+++ b/core/src/test/java/brooklyn/util/ssh/BashCommandsIntegrationTest.java
@@ -18,6 +18,7 @@
  */
 package brooklyn.util.ssh;
 
+import static brooklyn.util.ssh.BashCommands.sudo;
 import static java.lang.String.format;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
@@ -29,6 +30,7 @@ import java.io.File;
 import java.io.IOException;
 import java.net.ServerSocket;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 
 import org.apache.commons.io.FileUtils;
@@ -124,7 +126,7 @@ public class BashCommandsIntegrationTest {
     public void testSudo() throws Exception {
         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
         ByteArrayOutputStream errStream = new ByteArrayOutputStream();
-        String cmd = BashCommands.sudo("whoami");
+        String cmd = sudo("whoami");
         int exitcode = loc.execCommands(ImmutableMap.of("out", outStream, "err", errStream), "test", ImmutableList.of(cmd));
         String outstr = new String(outStream.toByteArray());
         String errstr = new String(errStream.toByteArray());
@@ -361,6 +363,121 @@ public class BashCommandsIntegrationTest {
             serverSocket.close();
         }
     }
+
+    
+    // Disabled by default because of risk of overriding /etc/hosts in really bad way if doesn't work properly!
+    // As a manual visual inspection test, consider first manually creating /etc/hostname and /etc/sysconfig/network
+    // so that it looks like debian+ubuntu / CentOS/RHEL.
+    @Test(groups={"Integration"}, enabled=false)
+    public void testSetHostnameUnqualified() throws Exception {
+        runSetHostname("br-"+Identifiers.makeRandomId(8).toLowerCase(), null, false);
+    }
+
+    @Test(groups={"Integration"}, enabled=false)
+    public void testSetHostnameQualified() throws Exception {
+        runSetHostname("br-"+Identifiers.makeRandomId(8).toLowerCase()+".brooklyn.incubator.apache.org", null, false);
+    }
+
+    @Test(groups={"Integration"}, enabled=false)
+    public void testSetHostnameNullDomain() throws Exception {
+        runSetHostname("br-"+Identifiers.makeRandomId(8).toLowerCase(), null, true);
+    }
+
+    @Test(groups={"Integration"}, enabled=false)
+    public void testSetHostnameNonNullDomain() throws Exception {
+        runSetHostname("br-"+Identifiers.makeRandomId(8).toLowerCase(), "brooklyn.incubator.apache.org", true);
+    }
+
+    protected void runSetHostname(String newHostname, String newDomain, boolean includeDomain) throws Exception {
+        String fqdn = (includeDomain && Strings.isNonBlank(newDomain)) ? newHostname + "." + newDomain : newHostname;
+        
+        LocalManagementContextForTests mgmt = new LocalManagementContextForTests();
+        SshMachineLocation loc = mgmt.getLocationManager().createLocation(LocalhostMachineProvisioningLocation.spec()).obtain();
+
+        execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts /etc/hosts-orig-testSetHostname")).get();
+        execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/hostname", sudo("cp /etc/hostname /etc/hostname-orig-testSetHostname"))).get();
+        execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/sysconfig/network", sudo("cp /etc/sysconfig/network /etc/sysconfig/network-orig-testSetHostname"))).get();
+        
+        String origHostname = getHostnameNoArgs(loc);
+        assertTrue(Strings.isNonBlank(origHostname));
+        
+        try {
+            List<String> cmd = (includeDomain) ? BashCommands.setHostname(newHostname, newDomain) : BashCommands.setHostname(newHostname);
+            execRequiringZeroAndReturningStdout(loc, cmd).get();
+
+            String actualHostnameUnqualified = getHostnameUnqualified(loc);
+            String actualHostnameFullyQualified = getHostnameFullyQualified(loc);
+
+            // TODO On OS X at least, we aren't actually setting the domain name; we're just letting 
+            //      the user pass in what the domain name is. We do add this properly to /etc/hosts
+            //      (e.g. first line is "127.0.0.1 br-g4x5wgx8.brooklyn.incubator.apache.org br-g4x5wgx8 localhost")
+            //      but subsequent calls to `hostname -f` returns the unqualified. Similarly, `domainname` 
+            //      returns blank. Therefore we can't assert that it equals our expected val (because we just made  
+            //      it up - "brooklyn.incubator.apache.org").
+            //      assertEquals(actualHostnameFullyQualified, fqdn);
+            assertEquals(actualHostnameUnqualified, Strings.getFragmentBetween(newHostname, null, "."));
+            execRequiringZeroAndReturningStdout(loc, "ping -c1 -n -q "+actualHostnameUnqualified).get();
+            execRequiringZeroAndReturningStdout(loc, "ping -c1 -n -q "+actualHostnameFullyQualified).get();
+            
+            String result = execRequiringZeroAndReturningStdout(loc, "grep -n "+fqdn+" /etc/hosts").get();
+            assertTrue(result.contains("localhost"), "line="+result);
+            log.info("result="+result);
+            
+        } finally {
+            execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts-orig-testSetHostname /etc/hosts")).get();
+            execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/hostname-orig-testSetHostname", sudo("cp /etc/hostname-orig-testSetHostname /etc/hostname"))).get();
+            execRequiringZeroAndReturningStdout(loc, BashCommands.ifFileExistsElse0("/etc/sysconfig/network-orig-testSetHostname", sudo("cp /etc/sysconfig/network-orig-testSetHostname /etc/sysconfig/network"))).get();
+            execRequiringZeroAndReturningStdout(loc, sudo("hostname "+origHostname)).get();
+        }
+    }
+
+    // Marked disabled because not safe to run on your normal machine! It modifies /etc/hosts, which is dangerous if things go wrong!
+    @Test(groups={"Integration"}, enabled=false)
+    public void testModifyEtcHosts() throws Exception {
+        LocalManagementContextForTests mgmt = new LocalManagementContextForTests();
+        SshMachineLocation loc = mgmt.getLocationManager().createLocation(LocalhostMachineProvisioningLocation.spec()).obtain();
+
+        execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts /etc/hosts-orig-testModifyEtcHosts")).get();
+        int numLinesOrig = Integer.parseInt(execRequiringZeroAndReturningStdout(loc, "wc -l /etc/hosts").get().trim().split("\\s")[0]);
+        
+        try {
+            String cmd = BashCommands.prependToEtcHosts("1.2.3.4", "myhostnamefor1234.at.start", "myhostnamefor1234b");
+            execRequiringZeroAndReturningStdout(loc, cmd).get();
+            
+            String cmd2 = BashCommands.appendToEtcHosts("5.6.7.8", "myhostnamefor5678.at.end", "myhostnamefor5678");
+            execRequiringZeroAndReturningStdout(loc, cmd2).get();
+            
+            String grepFirst = execRequiringZeroAndReturningStdout(loc, "grep -n myhostnamefor1234 /etc/hosts").get();
+            String grepLast = execRequiringZeroAndReturningStdout(loc, "grep -n myhostnamefor5678 /etc/hosts").get();
+            int numLinesAfter = Integer.parseInt(execRequiringZeroAndReturningStdout(loc, "wc -l /etc/hosts").get().trim().split("\\s")[0]);
+            log.info("result: numLinesBefore="+numLinesOrig+"; numLinesAfter="+numLinesAfter+"; first="+grepFirst+"; last="+grepLast);
+            
+            assertTrue(grepFirst.startsWith("1:") && grepFirst.contains("1.2.3.4 myhostnamefor1234.at.start myhostnamefor1234"), "first="+grepFirst);
+            assertTrue(grepLast.startsWith((numLinesOrig+2)+":") && grepLast.contains("5.6.7.8 myhostnamefor5678.at.end myhostnamefor5678"), "last="+grepLast);
+            assertEquals(numLinesOrig + 2, numLinesAfter, "lines orig="+numLinesOrig+", after="+numLinesAfter);
+        } finally {
+            execRequiringZeroAndReturningStdout(loc, sudo("cp /etc/hosts-orig-testModifyEtcHosts /etc/hosts")).get();
+        }
+    }
+    
+    private String getHostnameNoArgs(SshMachineLocation machine) {
+        String hostnameStdout = execRequiringZeroAndReturningStdout(machine, "echo FOREMARKER; hostname; echo AFTMARKER").get();
+        return Strings.getFragmentBetween(hostnameStdout, "FOREMARKER", "AFTMARKER").trim();
+    }
+
+    private String getHostnameUnqualified(SshMachineLocation machine) {
+        String hostnameStdout = execRequiringZeroAndReturningStdout(machine, "echo FOREMARKER; hostname -s 2> /dev/null || hostname; echo AFTMARKER").get();
+        return Strings.getFragmentBetween(hostnameStdout, "FOREMARKER", "AFTMARKER").trim();
+    }
+
+    private String getHostnameFullyQualified(SshMachineLocation machine) {
+        String hostnameStdout = execRequiringZeroAndReturningStdout(machine, "echo FOREMARKER; hostname --fqdn 2> /dev/null || hostname -f; echo AFTMARKER").get();
+        return Strings.getFragmentBetween(hostnameStdout, "FOREMARKER", "AFTMARKER").trim();
+    }
+
+    private ProcessTaskWrapper<String> execRequiringZeroAndReturningStdout(SshMachineLocation loc, Collection<String> cmds) {
+        return execRequiringZeroAndReturningStdout(loc, cmds.toArray(new String[cmds.size()]));
+    }
     
     private ProcessTaskWrapper<String> execRequiringZeroAndReturningStdout(SshMachineLocation loc, String... cmds) {
         ProcessTaskWrapper<String> t = SshTasks.newSshExecTaskFactory(loc, cmds)
@@ -368,6 +485,7 @@ public class BashCommandsIntegrationTest {
         exec.submit(t);
         return t;
     }
+
     private ServerSocket openServerSocket() {
         int lowerBound = 40000;
         int upperBound = 40100;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8e4e9e8b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
index 5139a62..d731dc5 100644
--- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessSshDriver.java
@@ -38,6 +38,7 @@ import brooklyn.entity.basic.lifecycle.NaiveScriptRunner;
 import brooklyn.entity.basic.lifecycle.ScriptHelper;
 import brooklyn.entity.drivers.downloads.DownloadResolver;
 import brooklyn.entity.drivers.downloads.DownloadResolverManager;
+import brooklyn.entity.effector.EffectorTasks;
 import brooklyn.entity.software.SshEffectorTasks;
 import brooklyn.event.feed.ConfigToAttributes;
 import brooklyn.location.basic.SshMachineLocation;
@@ -53,6 +54,7 @@ import brooklyn.util.stream.ReaderInputStream;
 import brooklyn.util.stream.Streams;
 import brooklyn.util.task.DynamicTasks;
 import brooklyn.util.task.Tasks;
+import brooklyn.util.task.system.ProcessTaskWrapper;
 import brooklyn.util.text.StringPredicates;
 import brooklyn.util.text.Strings;
 import brooklyn.util.time.Duration;
@@ -603,6 +605,26 @@ public abstract class AbstractSoftwareProcessSshDriver extends AbstractSoftwareP
         return result;
     }
 
+    public void checkNoHostnameBug() {
+        try {
+            ProcessTaskWrapper<Integer> hostnameTask = DynamicTasks.queue(SshEffectorTasks.ssh("echo FOREMARKER; hostname; echo AFTMARKER")).block();
+            String stdout = Strings.getFragmentBetween(hostnameTask.getStdout(), "FOREMARKER", "AFTMARKER");
+            if (hostnameTask.getExitCode() == 0 && Strings.isNonBlank(stdout)) {
+                String hostname = stdout.trim();
+                if (hostname.equals("(none)")) {
+                    String newHostname = "br-"+getEntity().getId().toLowerCase();
+                    log.info("Detected no-hostname bug with hostname "+hostname+" for "+getEntity()+"; renaming "+getMachine()+"  to hostname "+newHostname);
+                    DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.setHostname(newHostname, null))).block();
+                }
+            } else {
+                log.debug("Hostname could not be determined for location "+EffectorTasks.findSshMachine()+"; not doing no-hostname bug check");
+            }
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            log.warn("Error checking/fixing no-hostname bug (continuing): "+e, e);
+        }
+    }
+
     public static final String INSTALLING = "installing";
     public static final String CUSTOMIZING = "customizing";
     public static final String LAUNCHING = "launching";

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8e4e9e8b/software/base/src/main/java/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
----------------------------------------------------------------------
diff --git a/software/base/src/main/java/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java b/software/base/src/main/java/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
index 3847261..420d3e7 100644
--- a/software/base/src/main/java/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
+++ b/software/base/src/main/java/brooklyn/entity/java/JavaSoftwareProcessSshDriver.java
@@ -399,6 +399,7 @@ public abstract class JavaSoftwareProcessSshDriver extends AbstractSoftwareProce
     /**
      * Checks for Java 6 or 7, installing Java 7 if neither are found. Override this method to
      * check for and install specific versions of Java.
+     * 
      * @see #checkForAndInstallJava6()
      * @see #checkForAndInstallJava7()
      * @see #checkForAndInstallJava6or7()
@@ -415,19 +416,20 @@ public abstract class JavaSoftwareProcessSshDriver extends AbstractSoftwareProce
     }
     
     public void checkJavaHostnameBug() {
+        checkNoHostnameBug();
+        
         try {
-            ProcessTaskWrapper<Integer> hostnameLen = DynamicTasks.queue(SshEffectorTasks.ssh("echo FOREMARKER; hostname -f | wc | awk '{print $3}'; echo AFTMARKER")).block();
-            String stdout = Strings.getFragmentBetween(hostnameLen.getStdout(), "FOREMARKER", "AFTMARKER");
-            if (hostnameLen.getExitCode()==0 && Strings.isNonBlank(stdout)) {
-                Integer len = Integer.parseInt(stdout.trim());
+            ProcessTaskWrapper<Integer> hostnameTask = DynamicTasks.queue(SshEffectorTasks.ssh("echo FOREMARKER; hostname -f; echo AFTMARKER")).block();
+            String stdout = Strings.getFragmentBetween(hostnameTask.getStdout(), "FOREMARKER", "AFTMARKER");
+            if (hostnameTask.getExitCode() == 0 && Strings.isNonBlank(stdout)) {
+                String hostname = stdout.trim();
+                Integer len = hostname.length();
                 if (len > 63) {
                     // likely to cause a java crash due to java bug 7089443 -- set a new short hostname
                     // http://mail.openjdk.java.net/pipermail/net-dev/2012-July/004603.html
-                    String newHostname = "br-"+getEntity().getId();
+                    String newHostname = "br-"+getEntity().getId().toLowerCase();
                     log.info("Detected likelihood of Java hostname bug with hostname length "+len+" for "+getEntity()+"; renaming "+getMachine()+"  to hostname "+newHostname);
-                    DynamicTasks.queue(SshEffectorTasks.ssh(
-                            "hostname "+newHostname,
-                            "echo 127.0.0.1 "+newHostname+" > /etc/hosts").runAsRoot()).block();
+                    DynamicTasks.queue(SshEffectorTasks.ssh(BashCommands.setHostname(newHostname, null))).block();
                 }
             } else {
                 log.debug("Hostname length could not be determined for location "+EffectorTasks.findSshMachine()+"; not doing Java hostname bug check");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8e4e9e8b/utils/common/src/main/java/brooklyn/util/ssh/BashCommands.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/ssh/BashCommands.java b/utils/common/src/main/java/brooklyn/util/ssh/BashCommands.java
index e60b332..b59d973 100644
--- a/utils/common/src/main/java/brooklyn/util/ssh/BashCommands.java
+++ b/utils/common/src/main/java/brooklyn/util/ssh/BashCommands.java
@@ -18,6 +18,9 @@
  */
 package brooklyn.util.ssh;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import static java.lang.String.format;
 
 import java.util.ArrayList;
@@ -38,6 +41,7 @@ import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 
 public class BashCommands {
 
@@ -590,4 +594,87 @@ public class BashCommands {
                 +"\n"+"EOF_"+id+"\n";
     }
 
+    public static String prependToEtcHosts(String ip, String... hostnames) {
+        String tempFileId = "bak"+Identifiers.makeRandomId(4);
+        return sudo(String.format("sed -i."+tempFileId+" -e '1i\\\n%s %s' /etc/hosts", ip, Joiner.on(" ").join(hostnames)));
+    }
+    
+    public static String appendToEtcHosts(String ip, String... hostnames) {
+        // Using sed rather than `echo ... >> /etc/hosts` because when embedded inside sudo, 
+        // the redirect doesn't get executed by sudo.
+        String tempFileId = "bak"+Identifiers.makeRandomId(4);
+        return sudo(String.format("sed -i."+tempFileId+" -e '$a\\\n%s %s' /etc/hosts", ip, Joiner.on(" ").join(hostnames)));
+    }
+
+    /**
+     * Sets the hostname, splitting the given hostname if it contains a dot to include the unqualified and fully qualified names.
+     *  
+     * @see {@link #setHostname(String, String)}
+     */
+    @Beta
+    public static List<String> setHostname(String newHostname) {
+        // See http://www.dns-sd.org/trailingdotsindomainnames.html.
+        // If we are given "abcd." then let's pass that as-is to setHostname("abcd.", null)
+        
+        if (newHostname.indexOf(".") > 0) {
+            String hostPart = newHostname.substring(0, newHostname.indexOf("."));
+            String domainPart = newHostname.substring(hostPart.length()+1);
+            return setHostname(hostPart, domainPart);
+        } else {
+            return setHostname(newHostname, null);
+        }
+    }
+    
+    /**
+     * Sets the hostname to {@code hostPart + "." + domainPart}, or if domainPart is null/empty then {code hostPart}.
+     * 
+     * @param hostPart
+     * @param domainPart
+     * @return
+     */
+    @Beta
+    public static List<String> setHostname(String hostPart, String domainPart) {
+        // See:
+        // - http://www.rackspace.com/knowledge_center/article/centos-hostname-change
+        // - https://wiki.debian.org/HowTo/ChangeHostname
+        // - http://askubuntu.com/questions/9540/how-do-i-change-the-computer-name
+        //
+        // We prepend in /etc/hosts, to ensure the right fqn appears first.
+        //    e.g. comment in http://askubuntu.com/questions/158957/how-to-set-the-fully-qualified-domain-name-in-12-04
+        //    says "It's important to note that the first domain in /etc/hosts should be your FQDN. "
+        //
+        // TODO Should we run `sudo service hostname restart` or `sudo /etc/init.d/hostname restart`?
+        //      I don't think we need to because we've run `sudo hostname <newname>`
+        //
+        // TODO What if /etc/sysconfig/network doesn't have a line for HOSTNAME=...?
+        //
+        // TODO What about hostPart ending in "." - see http://www.dns-sd.org/trailingdotsindomainnames.html
+        //      for what that means in DNS. However, hostname is not the same as the DNS name (hostnames 
+        //      predate the invention of DNS! - although frequently the DNS name has the same first portion 
+        //      as the hostname) so the link you gave is not relevant. However despite searching Google and 
+        //      man pages I [Ricard] am unable to find a reference which clearly states what characters are 
+        //      relevant in a hostname. I think it's safest to assume that the hostname is just [a-z,0-9,-] 
+        //      and no dots at all.
+        
+        checkNotNull(hostPart, "hostPart");
+        checkArgument(!hostPart.contains("."), "hostPart '%s' must not contain '.'", hostPart);
+
+        String tempFileId = "bak"+Identifiers.makeRandomId(4);
+        
+        List<String> allhostnames = Lists.newArrayList();
+        String fqdn = hostPart;
+        if (Strings.isNonBlank(domainPart)) {
+            fqdn = hostPart+"."+domainPart;
+            allhostnames.add(fqdn);
+        }
+        allhostnames.add(hostPart);
+        allhostnames.add("localhost");
+        
+        return ImmutableList.of(
+                sudo("sed -i."+tempFileId+" -e 's/^127.0.0.1/# Replaced by Brooklyn\\\n#127.0.0.1/' /etc/hosts"),
+                prependToEtcHosts("127.0.0.1", allhostnames.toArray(new String[allhostnames.size()])),
+                ifFileExistsElse0("/etc/sysconfig/network", sudo("sed -i."+tempFileId+" -e 's/^HOSTNAME=.*$/HOSTNAME="+hostPart+"/' /etc/sysconfig/network")),
+                ifFileExistsElse0("/etc/hostname", sudo("sed -i."+tempFileId+" -e 's/^[a-zA-Z_0-9].*$/"+hostPart+"/' /etc/hostname")),
+                sudo("hostname "+hostPart));
+    }
 }