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/10/21 11:12:32 UTC

[1/3] incubator-brooklyn git commit: Adds SetHostnameCustomizer

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 238816f88 -> 9790e8b6b


Adds SetHostnameCustomizer


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

Branch: refs/heads/master
Commit: f792fdfb3a4bccbf863997f401212657619317ef
Parents: 76b098b
Author: Aled Sage <al...@gmail.com>
Authored: Mon Oct 19 18:13:44 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Oct 21 09:50:30 2015 +0100

----------------------------------------------------------------------
 .../core/effector/ssh/SshEffectorTasks.java     |   7 +
 .../entity/machine/SetHostnameCustomizer.java   | 201 +++++++++++++++++++
 .../machine/SetHostnameCustomizerLiveTest.java  | 143 +++++++++++++
 .../machine/SetHostnameCustomizerTest.java      |  67 +++++++
 4 files changed, 418 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f792fdfb/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
index 37e582d..a8e427c 100644
--- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
+++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshEffectorTasks.java
@@ -200,6 +200,13 @@ public class SshEffectorTasks {
         }
     }
 
+    /**
+     * @since 0.9.0
+     */
+    public static SshEffectorTaskFactory<Integer> ssh(SshMachineLocation machine, String ...commands) {
+        return new SshEffectorTaskFactory<Integer>(machine, commands);
+    }
+
     public static SshEffectorTaskFactory<Integer> ssh(String ...commands) {
         return new SshEffectorTaskFactory<Integer>(commands);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f792fdfb/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..7fdb86a
--- /dev/null
+++ b/software/base/src/main/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizer.java
@@ -0,0 +1,201 @@
+/*
+ * 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.entity.machine;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import org.apache.brooklyn.api.location.BasicMachineLocationCustomizer;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks;
+import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks.SshEffectorTaskFactory;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.util.core.text.TemplateProcessor;
+import org.apache.brooklyn.util.net.Networking;
+import org.apache.brooklyn.util.ssh.BashCommands;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+/**
+ * Sets the hostname on an ssh'able machine. Currently only CentOS and RHEL are supported.
+ * <p>
+ * The customizer can be configured with a hard-coded hostname, or with a freemarker template
+ * whose value (after substitutions) will be used for the hostname. 
+ */
+public class SetHostnameCustomizer extends BasicMachineLocationCustomizer {
+
+    public static final Logger log = LoggerFactory.getLogger(SetHostnameCustomizer.class);
+    
+    public static final ConfigKey<String> FIXED_HOSTNAME = ConfigKeys.newStringConfigKey(
+            "hostname.fixed",
+            "The statically defined hostname to be set on the machine (if non-null)");
+    
+    public static final ConfigKey<String> FIXED_DOMAIN = ConfigKeys.newStringConfigKey(
+            "domain.fixed",
+            "The statically defined domain name to be set on the machine (if non-null)");
+    
+    // the  var??  tests if it exists, passing value to ?string(if_present,if_absent)
+    // the ! provides a default value afterwards, which is never used, but is required for parsing
+    // when the config key is not available;
+    // thus the below prefers the first private address, then the first public address, and then
+    // substitutes dots for dashes.
+    public static final ConfigKey<String> HOSTNAME_TEMPLATE = ConfigKeys.newStringConfigKey(
+            "hostname.templated",
+            "The hostname template, to be resolved and then set on the machine (if non-null). "
+                    +"Assumed to be in free-marker format.",
+            "ip-"
+                    + "${((location.privateAddresses[0]??)?string("
+                            + "location.privateAddresses[0]!'X', location.publicAddresses[0]))"
+                    + "?replace(\".\",\"-\")}"
+                    + "-${location.id}");
+
+    public static final ConfigKey<String> LOCAL_HOSTNAME = ConfigKeys.newStringConfigKey(
+            "hostname.local.hostname",
+            "Host name, as known on the local box. Config is set on the location.");
+    
+    public static final ConfigKey<String> LOCAL_IP = ConfigKeys.newStringConfigKey(
+            "hostname.local.address",
+            "Host address, as known on the local box. Config is set on the location.");
+    
+    private final ConfigBag config;
+
+    public SetHostnameCustomizer(ConfigBag config) {
+        // TODO Any checks that they've given us sufficient configuration?
+        this.config = config;
+    }
+    
+    @Override
+    public void customize(MachineLocation machine) {
+        String localHostname = setLocalHostname((SshMachineLocation) machine);
+        machine.config().set(LOCAL_HOSTNAME, localHostname);
+
+        String localIp = execHostnameMinusI((SshMachineLocation) machine);
+        machine.config().set(LOCAL_IP, localIp);
+    }
+
+    protected String generateHostname(SshMachineLocation machine) {
+        String hostnameTemplate = config.get(HOSTNAME_TEMPLATE);
+        if (Strings.isNonBlank(hostnameTemplate)) {
+            return TemplateProcessor.processTemplateContents(hostnameTemplate, machine, ImmutableMap.<String, Object>of());
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Sets the machine's hostname to the value controlled by fixed_hostname and hostname_template. 
+     * If these are blank (and fixed_domain is blank), then just return the current hostname of 
+     * the machine.
+     */
+    public String setLocalHostname(SshMachineLocation machine) {
+        String hostFixed = config.get(FIXED_HOSTNAME);
+        String domainFixed = config.get(FIXED_DOMAIN);
+        String hostnameTemplate = config.get(HOSTNAME_TEMPLATE);
+        
+        String hostname;
+        if (Strings.isNonBlank(hostFixed)) {
+            hostname = hostFixed;
+        } else {
+            if (Strings.isNonBlank(hostnameTemplate)) {
+                hostname = generateHostname(machine);
+            } else {
+                hostname = execHostname(machine);
+                if (Strings.isBlank(domainFixed)) {
+                    return hostname;
+                }
+            }
+        }
+        
+        return setLocalHostname(machine, hostname, domainFixed);
+    }
+    
+    /**
+     * Sets the machine's hostname to the given value, ensuring /etc/hosts and /etc/sysconfig/network are both 
+     * correctly updated.
+     */
+    public String setLocalHostname(SshMachineLocation machine, String hostName, String domainFixed) {
+        log.info("Setting local hostname of " + machine + " to " + hostName 
+                + (Strings.isNonBlank(domainFixed) ? ", " + domainFixed : ""));
+        
+        boolean hasDomain = Strings.isNonBlank(domainFixed);
+        String fqdn = hasDomain ? hostName+"."+domainFixed : hostName;
+        
+        exec(machine, true, 
+                BashCommands.sudo(String.format("sed -i.bak -e '1i127.0.0.1 %s %s' -e '/^127.0.0.1/d' /etc/hosts", fqdn, hostName)),
+                BashCommands.sudo(String.format("sed -i.bak -e 's/^HOSTNAME=.*$/HOSTNAME=%s/' /etc/sysconfig/network", fqdn)),
+                BashCommands.sudo(String.format("hostname %s", fqdn)));
+
+        return hostName;
+    }
+    
+    protected void registerEtcHosts(SshMachineLocation machine, String ip, Iterable<String> hostnames) {
+        log.info("Updating /etc/hosts of "+machine+": adding "+ip+" = "+hostnames);
+        
+        checkArgument(Strings.isNonBlank(ip) && Networking.isValidIp4(ip), "invalid IPv4 address %s", ip);
+        if (Strings.isBlank(ip) || Iterables.isEmpty(hostnames)) return;
+        String line = ip+" "+Joiner.on(" ").join(hostnames);
+        exec(machine, true, "echo " + line + " >> /etc/hosts");
+    }
+    
+    protected String execHostname(SshMachineLocation machine) {
+        if (log.isDebugEnabled()) log.debug("Retrieve `hostname` via ssh for {}", machine);
+        
+        ProcessTaskWrapper<Integer> cmd = exec(machine, false, "echo hostname=`hostname`");
+//        ProcessTaskWrapper<Integer> cmd = DynamicTasks.queue(SshEffectorTasks.ssh(machine, "echo hostname=`hostname`")
+//                .summary("getHostname"))
+//                .block();
+
+        for (String line : cmd.getStdout().split("\n")) {
+            if (line.contains("hostname=") && !line.contains("`hostname`")) {
+                return line.substring(line.indexOf("hostname=") + "hostname=".length()).trim();
+            }
+        }
+        log.info("No hostname found for {} (got {}; {})", new Object[] {machine, cmd.getStdout(), cmd.getStderr()});
+        return null;
+    }
+
+    protected String execHostnameMinusI(SshMachineLocation machine) {
+        if (log.isDebugEnabled()) log.debug("Retrieve `hostname -I` via ssh for {}", machine);
+        
+        ProcessTaskWrapper<Integer> cmd = exec(machine, false, "echo localip=`hostname -I`");
+
+        for (String line : cmd.getStdout().split("\n")) {
+            if (line.contains("localip=") && !line.contains("`hostname -I`")) {
+                return line.substring(line.indexOf("localip=") + "localip=".length()).trim();
+            }
+        }
+        log.info("No local ip found for {} (got {}; {})", new Object[] {machine, cmd.getStdout(), cmd.getStderr()});
+        return null;
+    }
+    
+    protected ProcessTaskWrapper<Integer> exec(SshMachineLocation machine, boolean asRoot, String... cmds) {
+        SshEffectorTaskFactory<Integer> taskFactory = SshEffectorTasks.ssh(machine, cmds);
+        if (asRoot) taskFactory.runAsRoot();
+        return DynamicTasks.queue(taskFactory).block();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f792fdfb/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerLiveTest.java
----------------------------------------------------------------------
diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerLiveTest.java
new file mode 100644
index 0000000..7e139f3
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerLiveTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.entity.machine;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.internal.BrooklynProperties;
+import org.apache.brooklyn.core.location.Machines;
+import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
+import org.apache.brooklyn.entity.AbstractSoftlayerLiveTest;
+import org.apache.brooklyn.location.jclouds.JcloudsLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class SetHostnameCustomizerLiveTest extends BrooklynAppLiveTestSupport {
+
+    public static final String PROVIDER = AbstractSoftlayerLiveTest.PROVIDER;
+    public static final String REGION = "ams01";
+    public static final String PROVIDER_IMAGE_ID = "CENTOS_6_64";
+    public static final String LOCATION_SPEC = PROVIDER + ":" + REGION;
+    
+    public static final int MAX_TAG_LENGTH = AbstractSoftlayerLiveTest.MAX_TAG_LENGTH;
+    public static final int MAX_VM_NAME_LENGTH = AbstractSoftlayerLiveTest.MAX_VM_NAME_LENGTH;
+
+    protected BrooklynProperties brooklynProperties;
+    
+    protected MachineProvisioningLocation<SshMachineLocation> loc;
+    
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+        List<String> propsToRemove = ImmutableList.of("imageId", "imageDescriptionRegex", "imageNameRegex", "inboundPorts", "hardwareId", "minRam");
+
+        // Don't let any defaults from brooklyn.properties (except credentials) interfere with test
+        brooklynProperties = BrooklynProperties.Factory.newDefault();
+        for (String propToRemove : propsToRemove) {
+            for (String propVariant : ImmutableList.of(propToRemove, CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, propToRemove))) {
+                brooklynProperties.remove("brooklyn.locations.jclouds."+PROVIDER+"."+propVariant);
+                brooklynProperties.remove("brooklyn.locations."+propVariant);
+                brooklynProperties.remove("brooklyn.jclouds."+PROVIDER+"."+propVariant);
+                brooklynProperties.remove("brooklyn.jclouds."+propVariant);
+            }
+        }
+
+        // Also removes scriptHeader (e.g. if doing `. ~/.bashrc` and `. ~/.profile`, then that can cause "stdin: is not a tty")
+        brooklynProperties.remove("brooklyn.ssh.config.scriptHeader");
+        
+        mgmt = new LocalManagementContext(brooklynProperties);
+        
+        super.setUp();
+        
+        loc = (MachineProvisioningLocation<SshMachineLocation>) mgmt.getLocationRegistry().resolve(LOCATION_SPEC);
+    }
+
+    @Test(groups = {"Live"})
+    public void testSetFixedHostname() throws Exception {
+        SetHostnameCustomizer customizer = new SetHostnameCustomizer(ConfigBag.newInstance()
+                .configure(SetHostnameCustomizer.FIXED_HOSTNAME, "myhostname"));
+        
+        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))
+                .configure(MachineEntity.PROVISIONING_PROPERTIES.subKey(JcloudsLocation.IMAGE_ID.getName()), PROVIDER_IMAGE_ID));
+
+
+        app.start(ImmutableList.of(loc));
+        
+        SshMachineLocation machine = Machines.findUniqueMachineLocation(entity.getLocations(), SshMachineLocation.class).get();
+        
+        assertEquals(getHostname(machine), "myhostname");
+    }
+    
+    @Test(groups = {"Live"})
+    public void testSetAutogeneratedHostname() throws Exception {
+        SetHostnameCustomizer customizer = new SetHostnameCustomizer(ConfigBag.newInstance());
+
+        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))
+                .configure(MachineEntity.PROVISIONING_PROPERTIES.subKey(JcloudsLocation.IMAGE_ID.getName()), "CENTOS_6_64"));
+
+
+        app.start(ImmutableList.of(loc));
+        
+        SshMachineLocation machine = Machines.findUniqueMachineLocation(entity.getLocations(), SshMachineLocation.class).get();
+        
+        String ip;
+        if (machine.getPrivateAddresses().isEmpty()) {
+            ip = Iterables.get(machine.getPublicAddresses(), 0);
+        } else {
+            ip = Iterables.get(machine.getPrivateAddresses(), 0);
+        }
+        String expected = "ip-"+(ip.replace(".", "-")+"-"+machine.getId());
+        assertEquals(getHostname(machine), expected);
+    }
+    
+    protected String getHostname(SshMachineLocation machine) {
+        ByteArrayOutputStream outstream = new ByteArrayOutputStream();
+        ByteArrayOutputStream errstream = new ByteArrayOutputStream();
+        int result = machine.execScript(ImmutableMap.of("out", outstream, "err", errstream), "getHostname", ImmutableList.of("echo hostname=`hostname`"));
+        assertEquals(result, 0);
+        
+        String out = new String(outstream.toByteArray());
+        String err = new String(errstream.toByteArray());
+        for (String line : out.split("\n")) {
+            if (line.contains("hostname=") && !line.contains("`hostname`")) {
+                return line.substring(line.indexOf("hostname=") + "hostname=".length()).trim();
+            }
+        }
+        throw new IllegalStateException(String.format("No hostname found for %s (got %s; %s)", machine, out, err));
+    }        
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f792fdfb/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerTest.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..5ffdcdc
--- /dev/null
+++ b/software/base/src/test/java/org/apache/brooklyn/entity/machine/SetHostnameCustomizerTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.entity.machine;
+
+import static org.testng.Assert.assertEquals;
+
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class SetHostnameCustomizerTest extends BrooklynAppUnitTestSupport {
+
+    private SetHostnameCustomizer customizer;
+
+    @BeforeMethod(alwaysRun=true)
+    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)
+                .configure("privateAddresses", ImmutableList.of("1.2.3.4", "5.6.7.8"))
+                .configure("address", "4.3.2.1"));
+        
+        assertEquals(customizer.generateHostname(machine), "ip-1-2-3-4-"+machine.getId());
+    }
+    
+    @Test
+    public void testGeneratedHostnameUsesPublicIpIfNoPrivate() throws Exception {
+        SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("address", "4.3.2.1"));
+        
+        assertEquals(customizer.generateHostname(machine), "ip-4-3-2-1-"+machine.getId());
+    }
+    
+    @Test
+    public void testGeneratedHostnameUsesPublicIpIfEmptyListOfPrivate() throws Exception {
+        SshMachineLocation machine = mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+                .configure("privateAddresses", ImmutableList.of())
+                .configure("address", "4.3.2.1"));
+        
+        assertEquals(customizer.generateHostname(machine), "ip-4-3-2-1-"+machine.getId());
+    }
+}


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

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


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

Branch: refs/heads/master
Commit: 9790e8b6b643a3cd4ebf2a98a1b4b89cd4bfd284
Parents: 238816f f792fdf
Author: Aled Sage <al...@gmail.com>
Authored: Wed Oct 21 10:12:20 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Oct 21 10:12:20 2015 +0100

----------------------------------------------------------------------
 .../core/effector/ssh/SshEffectorTasks.java     |   7 +
 .../util/core/text/TemplateProcessor.java       | 161 ++++++++++++++-
 .../util/core/text/TemplateProcessorTest.java   |  18 ++
 .../entity/machine/SetHostnameCustomizer.java   | 201 +++++++++++++++++++
 .../machine/SetHostnameCustomizerLiveTest.java  | 143 +++++++++++++
 .../machine/SetHostnameCustomizerTest.java      |  67 +++++++
 6 files changed, 586 insertions(+), 11 deletions(-)
----------------------------------------------------------------------



[2/3] incubator-brooklyn git commit: TemplateProcessor: for location

Posted by al...@apache.org.
TemplateProcessor: for location


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

Branch: refs/heads/master
Commit: 76b098bd1f01732647fb5f85ac2603cd0afb5c03
Parents: c2494dc
Author: Aled Sage <al...@gmail.com>
Authored: Tue Oct 20 14:59:58 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Wed Oct 21 09:50:30 2015 +0100

----------------------------------------------------------------------
 .../util/core/text/TemplateProcessor.java       | 161 +++++++++++++++++--
 .../util/core/text/TemplateProcessorTest.java   |  18 +++
 2 files changed, 168 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/76b098bd/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java b/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java
index 58efcd4..923f733 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java
@@ -18,6 +18,8 @@
  */
 package org.apache.brooklyn.util.core.text;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
@@ -33,6 +35,7 @@ import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.location.internal.LocationInternal;
 import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
 import org.apache.brooklyn.core.sensor.DependentConfiguration;
 import org.apache.brooklyn.core.sensor.Sensors;
@@ -115,6 +118,11 @@ public class TemplateProcessor {
         return processTemplateContents(templateContents, new EntityAndMapTemplateModel(managementContext, extraSubstitutions));
     }
 
+    /** Processes template contents according to {@link EntityAndMapTemplateModel}. */
+    public static String processTemplateContents(String templateContents, Location location, Map<String,? extends Object> extraSubstitutions) {
+        return processTemplateContents(templateContents, new LocationAndMapTemplateModel((LocationInternal)location, extraSubstitutions));
+    }
+
     /**
      * A Freemarker {@link TemplateHashModel} which will correctly handle entries of the form "a.b" in this map,
      * matching against template requests for "${a.b}".
@@ -181,7 +189,7 @@ public class TemplateProcessor {
     
     /** FreeMarker {@link TemplateHashModel} which resolves keys inside the given entity or management context.
      * Callers are required to include dots for dot-separated keys.
-     * Freemarker will only due this when in inside bracket notation in an outer map, as in <code>${outer['a.b.']}</code>; 
+     * Freemarker will only do this when in inside bracket notation in an outer map, as in <code>${outer['a.b.']}</code>; 
      * as a result this is intended only for use by {@link EntityAndMapTemplateModel} where 
      * a caller has used bracked notation, as in <code>${mgmt['key.subkey']}</code>. */
     protected static final class EntityConfigTemplateModel implements TemplateHashModel {
@@ -189,13 +197,87 @@ public class TemplateProcessor {
         protected final ManagementContext mgmt;
 
         protected EntityConfigTemplateModel(EntityInternal entity) {
-            this.entity = entity;
+            this.entity = checkNotNull(entity, "entity");
             this.mgmt = entity.getManagementContext();
         }
 
-        protected EntityConfigTemplateModel(ManagementContext mgmt) {
-            this.entity = null;
-            this.mgmt = mgmt;
+        @Override
+        public boolean isEmpty() { return false; }
+
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            try {
+                Object result = entity.getConfig(ConfigKeys.builder(Object.class).name(key).build());
+                
+                if (result==null)
+                    result = mgmt.getConfig().getConfig(ConfigKeys.builder(Object.class).name(key).build());
+                
+                if (result!=null)
+                    return wrapAsTemplateModel( result );
+                
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                throw new IllegalStateException("Error accessing config '"+key+"'"+" on "+entity+": "+e, e);
+            }
+            
+            return null;
+        }
+        
+        @Override
+        public String toString() {
+            return getClass().getName()+"["+entity+"]";
+        }
+    }
+
+    /** FreeMarker {@link TemplateHashModel} which resolves keys inside the given management context.
+     * Callers are required to include dots for dot-separated keys.
+     * Freemarker will only do this when in inside bracket notation in an outer map, as in <code>${outer['a.b.']}</code>; 
+     * as a result this is intended only for use by {@link EntityAndMapTemplateModel} where 
+     * a caller has used bracked notation, as in <code>${mgmt['key.subkey']}</code>. */
+    protected static final class MgmtConfigTemplateModel implements TemplateHashModel {
+        protected final ManagementContext mgmt;
+
+        protected MgmtConfigTemplateModel(ManagementContext mgmt) {
+            this.mgmt = checkNotNull(mgmt, "mgmt");
+        }
+
+        @Override
+        public boolean isEmpty() { return false; }
+
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            try {
+                Object result = mgmt.getConfig().getConfig(ConfigKeys.builder(Object.class).name(key).build());
+                
+                if (result!=null)
+                    return wrapAsTemplateModel( result );
+                
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                throw new IllegalStateException("Error accessing config '"+key+"': "+e, e);
+            }
+            
+            return null;
+        }
+        
+        @Override
+        public String toString() {
+            return getClass().getName()+"["+mgmt+"]";
+        }
+    }
+    
+    /** FreeMarker {@link TemplateHashModel} which resolves keys inside the given location.
+     * Callers are required to include dots for dot-separated keys.
+     * Freemarker will only do this when in inside bracket notation in an outer map, as in <code>${outer['a.b.']}</code>; 
+     * as a result this is intended only for use by {@link LocationAndMapTemplateModel} where 
+     * a caller has used bracked notation, as in <code>${mgmt['key.subkey']}</code>. */
+    protected static final class LocationConfigTemplateModel implements TemplateHashModel {
+        protected final LocationInternal location;
+        protected final ManagementContext mgmt;
+
+        protected LocationConfigTemplateModel(LocationInternal location) {
+            this.location = checkNotNull(location, "location");
+            this.mgmt = location.getManagementContext();
         }
 
         @Override
@@ -206,8 +288,8 @@ public class TemplateProcessor {
             try {
                 Object result = null;
                 
-                if (entity!=null)
-                    result = entity.getConfig(ConfigKeys.builder(Object.class).name(key).build());
+                result = location.getConfig(ConfigKeys.builder(Object.class).name(key).build());
+                
                 if (result==null && mgmt!=null)
                     result = mgmt.getConfig().getConfig(ConfigKeys.builder(Object.class).name(key).build());
                 
@@ -217,7 +299,7 @@ public class TemplateProcessor {
             } catch (Exception e) {
                 Exceptions.propagateIfFatal(e);
                 throw new IllegalStateException("Error accessing config '"+key+"'"
-                    + (entity!=null ? " on "+entity : "")+": "+e, e);
+                    + (location!=null ? " on "+location : "")+": "+e, e);
             }
             
             return null;
@@ -225,7 +307,7 @@ public class TemplateProcessor {
         
         @Override
         public String toString() {
-            return getClass().getName()+"["+entity+"]";
+            return getClass().getName()+"["+location+"]";
         }
     }
 
@@ -311,10 +393,10 @@ public class TemplateProcessor {
                 if (entity!=null)
                     return new EntityConfigTemplateModel(entity);
                 else
-                    return new EntityConfigTemplateModel(mgmt);
+                    return new MgmtConfigTemplateModel(mgmt);
             }
             if ("mgmt".equals(key)) {
-                return new EntityConfigTemplateModel(mgmt);
+                return new MgmtConfigTemplateModel(mgmt);
             }
 
             if ("driver".equals(key) && driver!=null)
@@ -351,6 +433,63 @@ public class TemplateProcessor {
         }
     }
 
+    /**
+     * Provides access to config on an entity or management context, using
+     * <code>${config['entity.config.key']}</code> or <code>${mgmt['brooklyn.properties.key']}</code> notation,
+     * and also allowing access to <code>getX()</code> methods on entity (interface) or driver
+     * using <code>${entity.x}</code> or <code><${driver.x}</code>.
+     * Optional extra properties can be supplied, treated as per {@link DotSplittingTemplateModel}.
+     */
+    protected static final class LocationAndMapTemplateModel implements TemplateHashModel {
+        protected final LocationInternal location;
+        protected final ManagementContext mgmt;
+        protected final DotSplittingTemplateModel extraSubstitutionsModel;
+
+        protected LocationAndMapTemplateModel(LocationInternal location, Map<String,? extends Object> extraSubstitutions) {
+            this.location = checkNotNull(location, "location");
+            this.mgmt = location.getManagementContext();
+            this.extraSubstitutionsModel = new DotSplittingTemplateModel(extraSubstitutions);
+        }
+
+        @Override
+        public boolean isEmpty() { return false; }
+
+        @Override
+        public TemplateModel get(String key) throws TemplateModelException {
+            if (extraSubstitutionsModel.contains(key))
+                return wrapAsTemplateModel( extraSubstitutionsModel.get(key) );
+
+            if ("location".equals(key))
+                return wrapAsTemplateModel( location );
+            if ("config".equals(key)) {
+                return new LocationConfigTemplateModel(location);
+            }
+            if ("mgmt".equals(key)) {
+                return new MgmtConfigTemplateModel(mgmt);
+            }
+
+            if (mgmt!=null) {
+                // TODO deprecated in 0.7.0, remove after next version
+                // ie not supported to access global props without qualification
+                Object result = mgmt.getConfig().getConfig(ConfigKeys.builder(Object.class).name(key).build());
+                if (result!=null) { 
+                    log.warn("Deprecated access of global brooklyn.properties value for "+key+"; should be qualified with 'mgmt.'");
+                    return wrapAsTemplateModel( result );
+                }
+            }
+            
+            if ("javaSysProps".equals(key))
+                return wrapAsTemplateModel( System.getProperties() );
+
+            return null;
+        }
+        
+        @Override
+        public String toString() {
+            return getClass().getName()+"["+location+"]";
+        }
+    }
+
     /** Processes template contents with the given items in scope as per {@link EntityAndMapTemplateModel}. */
     public static String processTemplateContents(String templateContents, final EntityInternal entity, Map<String,? extends Object> extraSubstitutions) {
         return processTemplateContents(templateContents, new EntityAndMapTemplateModel(entity, extraSubstitutions));

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/76b098bd/core/src/test/java/org/apache/brooklyn/util/core/text/TemplateProcessorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/text/TemplateProcessorTest.java b/core/src/test/java/org/apache/brooklyn/util/core/text/TemplateProcessorTest.java
index b2fd6a7..0f2d989 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/text/TemplateProcessorTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/text/TemplateProcessorTest.java
@@ -26,6 +26,8 @@ import org.apache.brooklyn.core.sensor.DependentConfiguration;
 import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
 import org.apache.brooklyn.core.test.entity.TestApplication;
 import org.apache.brooklyn.core.test.entity.TestEntity;
+import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
 import org.apache.brooklyn.test.FixedLocaleTest;
 import org.apache.brooklyn.util.core.text.TemplateProcessor;
 import org.testng.Assert;
@@ -102,6 +104,22 @@ public class TemplateProcessorTest extends BrooklynAppUnitTestSupport {
     }
     
     @Test
+    public void testLocationGetterMethod() {
+        LocalhostMachineProvisioningLocation location = app.newLocalhostProvisioningLocation();
+        String templateContents = "${location.id}";
+        String result = TemplateProcessor.processTemplateContents(templateContents, location, ImmutableMap.<String,Object>of());
+        assertEquals(result, location.getId());
+    }
+    
+    @Test
+    public void testLocationConfig() {
+        LocalhostMachineProvisioningLocation location = app.newLocalhostProvisioningLocation(ImmutableMap.of("mykey", "myval"));
+        String templateContents = "${config['mykey']}";//"+TestEntity.CONF_NAME.getName()+"']}";
+        String result = TemplateProcessor.processTemplateContents(templateContents, location, ImmutableMap.<String,Object>of());
+        assertEquals(result, "myval");
+    }
+    
+    @Test
     public void testManagementContextConfig() {
         mgmt.getBrooklynProperties().put("globalmykey", "myval");
         String templateContents = "${mgmt.globalmykey}";