org.apache.brooklyn.entity.machine.SetHostnameCustomizer Maven / Gradle / Ivy
Show all versions of brooklyn-software-base Show documentation
/*
* 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 java.util.Arrays;
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.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.
*
* 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.
*/
// TODO basic hostname setting is done by JcloudsLocation, combine that code with this
public class SetHostnameCustomizer extends BasicMachineLocationCustomizer {
public static final Logger log = LoggerFactory.getLogger(SetHostnameCustomizer.class);
public static final ConfigKey FIXED_HOSTNAME = ConfigKeys.newStringConfigKey(
"hostname.fixed",
"The statically defined hostname to be set on the machine (if non-null)");
public static final ConfigKey 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 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]??)?string("
+ "location.publicAddresses[0]!'X', "
+ "\"none\"))"
+ ")"
+ "?replace(\".\",\"-\")}"
+ "-${location.id}");
public static final ConfigKey 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 LOCAL_IP = ConfigKeys.newStringConfigKey(
"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) {
// TODO Any checks that they've given us sufficient configuration?
this.config = config;
}
@Override
public void customize(MachineLocation machine) {
if (config.get(MACHINE_FILTER).apply(machine)) {
log.info("SetHostnameCustomizer setting hostname on "+machine);
} else {
log.info("SetHostnameCustomizer ignoring non-ssh machine "+machine);
return;
}
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) {
String hostnameTemplate = config.get(HOSTNAME_TEMPLATE);
if (Strings.isNonBlank(hostnameTemplate)) {
return TemplateProcessor.processTemplateContents(hostnameTemplate, machine, ImmutableMap.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 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 cmd = exec(machine, false, "echo hostname=`hostname`");
// ProcessTaskWrapper 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 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 exec(SshMachineLocation machine, boolean asRoot, String... cmds) {
SshEffectorTaskFactory taskFactory = SshEffectorTasks.ssh(machine, cmds);
if (asRoot) taskFactory.runAsRoot();
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;
}
}