/* * 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.location.byon; import static com.google.common.base.Preconditions.checkArgument; import java.net.InetAddress; import java.util.List; import java.util.Locale; import java.util.Map; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationRegistry; import org.apache.brooklyn.api.location.LocationSpec; 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.config.Sanitizer; import org.apache.brooklyn.core.location.AbstractLocationResolver; import org.apache.brooklyn.core.mgmt.internal.LocalLocationManager; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.flags.TypeCoercions; import org.apache.brooklyn.util.net.UserAndHostAndPort; import org.apache.brooklyn.util.text.WildcardGlobs; import org.apache.brooklyn.util.text.WildcardGlobs.PhraseTreatment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.net.HostAndPort; /** * Examples of valid specs: * <ul> * <li>byon:(hosts=myhost) * <li>byon:(hosts=myhost,myhost2) * <li>byon:(hosts="myhost, myhost2") * <li>byon:(hosts=myhost,myhost2, name=abc) * <li>byon:(hosts="myhost, myhost2", name="my location name") * </ul> * * @author aled */ @SuppressWarnings({"rawtypes"}) public class ByonLocationResolver extends AbstractLocationResolver { public static final Logger log = LoggerFactory.getLogger(ByonLocationResolver.class); public static final String BYON = "byon"; public static final ConfigKey<String> OS_FAMILY = ConfigKeys.newStringConfigKey("osFamily", "OS Family of the machine, either windows or linux", "linux"); /** * @todo Reimplement via a registry: * {@link org.apache.brooklyn.location.winrm.WinRmMachineLocation} * {@link org.apache.brooklyn.location.ssh.SshMachineLocation} */ public static final Map<String, String> OS_TO_MACHINE_LOCATION_TYPE = ImmutableMap.of( "windows", "org.apache.brooklyn.location.winrm.WinRmMachineLocation", "linux", "org.apache.brooklyn.location.ssh.SshMachineLocation"); @Override public String getPrefix() { return BYON; } @Override protected Class<? extends Location> getLocationType() { return FixedListMachineProvisioningLocation.class; } @Override protected SpecParser getSpecParser() { return new AbstractLocationResolver.SpecParser(getPrefix()).setExampleUsage("\"byon(hosts='addr1,addr2')\""); } @Override protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, LocationRegistry registry) { ConfigBag config = super.extractConfig(locationFlags, spec, registry); Object hosts = config.getStringKey("hosts"); config.remove("hosts"); String user = (String) config.getStringKey("user"); Integer port = (Integer) TypeCoercions.coerce(config.getStringKey("port"), Integer.class); Class<? extends MachineLocation> locationClass = getLocationClass(config.get(OS_FAMILY)); MutableMap<String, Object> defaultProps = MutableMap.of(); defaultProps.addIfNotNull("user", user); defaultProps.addIfNotNull("port", port); List<String> hostAddresses; if (hosts instanceof String) { if (((String) hosts).isEmpty()) { hostAddresses = ImmutableList.of(); } else { hostAddresses = WildcardGlobs.getGlobsAfterBraceExpansion("{"+hosts+"}", true /* numeric */, /* no quote support though */ PhraseTreatment.NOT_A_SPECIAL_CHAR, PhraseTreatment.NOT_A_SPECIAL_CHAR); } } else if (hosts instanceof Iterable) { hostAddresses = ImmutableList.copyOf((Iterable<String>)hosts); } else { throw new IllegalArgumentException("Invalid location '"+spec+"'; at least one host must be defined"); } if (hostAddresses.isEmpty()) { throw new IllegalArgumentException("Invalid location '"+spec+"'; at least one host must be defined"); } List<MachineLocation> machines = Lists.newArrayList(); for (Object host : hostAddresses) { LocationSpec<? extends MachineLocation> machineSpec; if (host instanceof String) { machineSpec = parseMachine((String)host, locationClass, defaultProps, spec); } else if (host instanceof Map) { machineSpec = parseMachine((Map<String, ?>)host, locationClass, defaultProps, spec); } else { throw new IllegalArgumentException("Expected machine to be String or Map, but was "+host.getClass().getName()+" ("+host+")"); } machineSpec.configureIfNotNull(LocalLocationManager.CREATE_UNMANAGED, config.get(LocalLocationManager.CREATE_UNMANAGED)); MachineLocation machine = managementContext.getLocationManager().createLocation(machineSpec); machines.add(machine); } config.putStringKey("machines", machines); return config; } protected LocationSpec<? extends MachineLocation> parseMachine(Map<String, ?> vals, Class<? extends MachineLocation> locationClass, Map<String, ?> defaults, String specForErrMsg) { Map<String, Object> valSanitized = Sanitizer.sanitize(vals); Map<String, Object> machineConfig = MutableMap.copyOf(vals); String osFamily = (String) machineConfig.remove(OS_FAMILY.getName()); String ssh = (String) machineConfig.remove("ssh"); String winrm = (String) machineConfig.remove("winrm"); Map<Integer, String> tcpPortMappings = (Map<Integer, String>) machineConfig.get("tcpPortMappings"); checkArgument(ssh != null ^ winrm != null, "Must specify exactly one of 'ssh' or 'winrm' for machine: %s", valSanitized); UserAndHostAndPort userAndHostAndPort; String host; int port; if (ssh != null) { userAndHostAndPort = parseUserAndHostAndPort((String)ssh, 22); } else { userAndHostAndPort = parseUserAndHostAndPort((String)winrm, 5985); } // If there is a tcpPortMapping defined for the connection-port, then use that for ssh/winrm machine port = userAndHostAndPort.getHostAndPort().getPort(); if (tcpPortMappings != null && tcpPortMappings.containsKey(port)) { String override = tcpPortMappings.get(port); HostAndPort hostAndPortOverride = HostAndPort.fromString(override); if (!hostAndPortOverride.hasPort()) { throw new IllegalArgumentException("Invalid portMapping ('"+override+"') for port "+port+" in "+specForErrMsg); } port = hostAndPortOverride.getPort(); host = hostAndPortOverride.getHostText().trim(); } else { host = userAndHostAndPort.getHostAndPort().getHostText().trim(); } machineConfig.put("address", host); try { InetAddress.getByName(host); } catch (Exception e) { throw new IllegalArgumentException("Invalid host '"+host+"' specified in '"+specForErrMsg+"': "+e); } if (userAndHostAndPort.getUser() != null) { checkArgument(!vals.containsKey("user"), "Must not specify user twice for machine: %s", valSanitized); machineConfig.put("user", userAndHostAndPort.getUser()); } if (userAndHostAndPort.getHostAndPort().hasPort()) { checkArgument(!vals.containsKey("port"), "Must not specify port twice for machine: %s", valSanitized); machineConfig.put("port", port); } for (Map.Entry<String, ?> entry : defaults.entrySet()) { if (!machineConfig.containsKey(entry.getKey())) { machineConfig.put(entry.getKey(), entry.getValue()); } } Class<? extends MachineLocation> locationClassHere = locationClass; if (osFamily != null) { locationClassHere = getLocationClass(osFamily); } return LocationSpec.create(locationClassHere).configure(machineConfig); } private Class<? extends MachineLocation> getLocationClass(String osFamily) { try { if (osFamily != null) { return Class.forName(OS_TO_MACHINE_LOCATION_TYPE.get(osFamily.toLowerCase(Locale.ENGLISH))).asSubclass(MachineLocation.class); } } catch (ClassNotFoundException ex) {} return null; } protected LocationSpec<? extends MachineLocation> parseMachine(String val, Class<? extends MachineLocation> locationClass, Map<String, ?> defaults, String specForErrMsg) { Map<String, Object> machineConfig = Maps.newLinkedHashMap(); UserAndHostAndPort userAndHostAndPort = parseUserAndHostAndPort(val); String host = userAndHostAndPort.getHostAndPort().getHostText().trim(); machineConfig.put("address", host); try { InetAddress.getByName(host.trim()); } catch (Exception e) { throw new IllegalArgumentException("Invalid host '"+host+"' specified in '"+specForErrMsg+"': "+e); } if (userAndHostAndPort.getUser() != null) { machineConfig.put("user", userAndHostAndPort.getUser()); } if (userAndHostAndPort.getHostAndPort().hasPort()) { machineConfig.put("port", userAndHostAndPort.getHostAndPort().getPort()); } for (Map.Entry<String, ?> entry : defaults.entrySet()) { if (!machineConfig.containsKey(entry.getKey())) { machineConfig.put(entry.getKey(), entry.getValue()); } } return LocationSpec.create(locationClass).configure(machineConfig); } private UserAndHostAndPort parseUserAndHostAndPort(String val) { String userPart = null; String hostPart = val; if (val.contains("@")) { userPart = val.substring(0, val.indexOf("@")); hostPart = val.substring(val.indexOf("@")+1); } return UserAndHostAndPort.fromParts(userPart, HostAndPort.fromString(hostPart)); } private UserAndHostAndPort parseUserAndHostAndPort(String val, int defaultPort) { UserAndHostAndPort result = parseUserAndHostAndPort(val); if (!result.getHostAndPort().hasPort()) { result = UserAndHostAndPort.fromParts(result.getUser(), result.getHostAndPort().getHostText(), defaultPort); } return result; } }