ByonLocationResolver: support complex machine config Adds support for each machine in BYON having more complex config, such as each having its own set of config options (where each machine has a map of config values).
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/4e625b9b Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/4e625b9b Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/4e625b9b Branch: refs/heads/master Commit: 4e625b9bf4ca6d1f847944b338be0032abf5646c Parents: 64adbb7 Author: Aled Sage <[email protected]> Authored: Fri Jun 26 14:46:31 2015 +0100 Committer: Aled Sage <[email protected]> Committed: Thu Jul 2 17:20:18 2015 +0100 ---------------------------------------------------------------------- .../location/basic/ByonLocationResolver.java | 124 ++++++++++-- docs/guide/ops/locations/index.md | 25 +++ .../camp/brooklyn/ByonLocationsYamlTest.java | 193 +++++++++++++++++++ .../brooklyn/util/net/UserAndHostAndPort.java | 4 + 4 files changed, 326 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4e625b9b/core/src/main/java/brooklyn/location/basic/ByonLocationResolver.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/location/basic/ByonLocationResolver.java b/core/src/main/java/brooklyn/location/basic/ByonLocationResolver.java index 2ce37d2..067af47 100644 --- a/core/src/main/java/brooklyn/location/basic/ByonLocationResolver.java +++ b/core/src/main/java/brooklyn/location/basic/ByonLocationResolver.java @@ -18,6 +18,8 @@ */ package brooklyn.location.basic; +import static com.google.common.base.Preconditions.checkArgument; + import java.net.InetAddress; import java.util.List; import java.util.Map; @@ -27,18 +29,23 @@ import org.slf4j.LoggerFactory; import brooklyn.config.ConfigKey; import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.Sanitizer; import brooklyn.location.Location; import brooklyn.location.LocationSpec; import brooklyn.location.MachineLocation; import brooklyn.management.internal.LocalLocationManager; -import brooklyn.util.JavaGroovyEquivalents; +import brooklyn.util.collections.MutableMap; import brooklyn.util.config.ConfigBag; +import brooklyn.util.flags.TypeCoercions; +import brooklyn.util.net.UserAndHostAndPort; import brooklyn.util.text.WildcardGlobs; import brooklyn.util.text.WildcardGlobs.PhraseTreatment; 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: @@ -86,7 +93,8 @@ public class ByonLocationResolver extends AbstractLocationResolver { Object hosts = config.getStringKey("hosts"); config.remove("hosts"); - String user = (String)config.getStringKey("user"); + String user = (String) config.getStringKey("user"); + Integer port = (Integer) TypeCoercions.coerce(config.getStringKey("port"), Integer.class); Class<? extends MachineLocation> locationClass = OS_TO_MACHINE_LOCATION_TYPE.get(config.get(OS_FAMILY)); List<String> hostAddresses; @@ -108,25 +116,17 @@ public class ByonLocationResolver extends AbstractLocationResolver { } List<MachineLocation> machines = Lists.newArrayList(); - for (String host : hostAddresses) { - String userHere = user; - String hostHere = host; - if (host.contains("@")) { - userHere = host.substring(0, host.indexOf("@")); - hostHere = host.substring(host.indexOf("@")+1); - } - try { - InetAddress.getByName(hostHere.trim()); - } catch (Exception e) { - throw new IllegalArgumentException("Invalid host '"+hostHere+"' specified in '"+spec+"': "+e); - } - LocationSpec<? extends MachineLocation> locationSpec = LocationSpec.create(locationClass) - .configure("address", hostHere.trim()) - .configureIfNotNull(LocalLocationManager.CREATE_UNMANAGED, config.get(LocalLocationManager.CREATE_UNMANAGED)); - if (JavaGroovyEquivalents.groovyTruth(userHere)) { - locationSpec.configure("user", userHere.trim()); + for (Object host : hostAddresses) { + LocationSpec<? extends MachineLocation> machineSpec; + if (host instanceof String) { + machineSpec = parseMachine((String)host, locationClass, MutableMap.of("user", user, "port", port), spec); + } else if (host instanceof Map) { + machineSpec = parseMachine((Map<String, ?>)host, locationClass, MutableMap.of("user", user, "port", port), spec); + } else { + throw new IllegalArgumentException("Expected machine to be String or Map, but was "+host.getClass().getName()+" ("+host+")"); } - MachineLocation machine = managementContext.getLocationManager().createLocation(locationSpec); + machineSpec.configureIfNotNull(LocalLocationManager.CREATE_UNMANAGED, config.get(LocalLocationManager.CREATE_UNMANAGED)); + MachineLocation machine = managementContext.getLocationManager().createLocation(machineSpec); machines.add(machine); } @@ -134,4 +134,88 @@ public class ByonLocationResolver extends AbstractLocationResolver { 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"); + checkArgument(ssh != null ^ winrm != null, "Must specify exactly one of 'ssh' or 'winrm' for machine: %s", valSanitized); + + UserAndHostAndPort userAndHostAndPort; + if (ssh != null) { + userAndHostAndPort = parseUserAndHostAndPort((String)ssh); + } else { + userAndHostAndPort = parseUserAndHostAndPort((String)winrm); + } + + String 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", userAndHostAndPort.getHostAndPort().getPort()); + } + 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 = OS_TO_MACHINE_LOCATION_TYPE.get(osfamily); + } + + return LocationSpec.create(locationClassHere).configure(machineConfig); + } + + 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)); + } } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4e625b9b/docs/guide/ops/locations/index.md ---------------------------------------------------------------------- diff --git a/docs/guide/ops/locations/index.md b/docs/guide/ops/locations/index.md index 6fc5b4b..28a238a 100644 --- a/docs/guide/ops/locations/index.md +++ b/docs/guide/ops/locations/index.md @@ -401,6 +401,31 @@ brooklyn.location.named.On-Prem\ Iron\ Example.privateKeyFile=~/.ssh/produser_id brooklyn.location.named.On-Prem\ Iron\ Example.privateKeyPassphrase=s3cr3tpassphrase {% endhighlight %} +For more complex host configuration, one can define custom config values per machine. In the example +below, there will be two machines. The first will be a machine reachable on +`ssh -i ~/.ssh/brooklyn.pem -p 8022 [email protected]`. The second is a windows machine, reachable +over WinRM. Each machine has also has a private address (e.g. for within a private network). + +{% highlight yaml %} +location: + byon: + hosts: + - ssh: 50.51.52.53:8022 + privateAddresses: [10.0.0.1] + privateKeyFile: ~/.ssh/brooklyn.pem + user: myuser + - winrm: 50.51.52.54:8985 + privateAddresses: [10.0.0.2] + password: mypassword + user: myuser + osfamily: windows +{% endhighlight %} + +The BYON location also supports a machine chooser, using the config key `byon.machineChooser`. +This allows one to plugin logic to choose from the set of available machines in the pool. For +example, additional config could be supplied for each machine. This could be used (during the call +to `location.obtain()`) to find the config that matches the requirements of the entity being +provisioned. See `brooklyn.location.basic.FixedListMachineProvisioningLocation.MACHINE_CHOOSER`. ### Other Location Topics http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4e625b9b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ByonLocationsYamlTest.java ---------------------------------------------------------------------- diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ByonLocationsYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ByonLocationsYamlTest.java new file mode 100644 index 0000000..5ce0dac --- /dev/null +++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ByonLocationsYamlTest.java @@ -0,0 +1,193 @@ +/* + * 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 io.brooklyn.camp.brooklyn; + +import static org.testng.Assert.assertEquals; + +import java.io.StringReader; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import brooklyn.entity.Entity; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.location.MachineLocation; +import brooklyn.location.basic.FixedListMachineProvisioningLocation; +import brooklyn.location.basic.LocationPredicates; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.location.basic.WinRmMachineLocation; +import brooklyn.util.net.UserAndHostAndPort; + +import com.google.api.client.repackaged.com.google.common.base.Joiner; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; + +public class ByonLocationsYamlTest extends AbstractYamlTest { + private static final Logger log = LoggerFactory.getLogger(ByonLocationsYamlTest.class); + + @Test + @SuppressWarnings("unchecked") + public void testByonSpec() throws Exception { + String yaml = Joiner.on("\n").join( + "location: byon(user=myuser,mykey=myval,hosts=\"1.1.1.1\")", + "services:", + "- serviceType: brooklyn.entity.basic.BasicApplication"); + + Entity app = createStartWaitAndLogApplication(new StringReader(yaml)); + FixedListMachineProvisioningLocation<SshMachineLocation> loc = (FixedListMachineProvisioningLocation<SshMachineLocation>) Iterables.get(app.getLocations(), 0); + + Set<SshMachineLocation> machines = loc.getAvailable(); + SshMachineLocation machine = Iterables.getOnlyElement(machines); + assertMachine(machine, UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 22), ImmutableMap.of("mykey", "myval")); + } + + @Test + @SuppressWarnings("unchecked") + public void testByonMachine() throws Exception { + String yaml = Joiner.on("\n").join( + "location:", + " byon:", + " hosts:", + " - ssh: 1.1.1.1:8022", + " privateAddresses: [10.0.0.1]", + " password: mypassword", + " user: myuser", + " mykey: myval", + "services:", + "- serviceType: brooklyn.entity.basic.BasicApplication"); + + Entity app = createStartWaitAndLogApplication(new StringReader(yaml)); + FixedListMachineProvisioningLocation<SshMachineLocation> loc = (FixedListMachineProvisioningLocation<SshMachineLocation>) Iterables.get(app.getLocations(), 0); + + Set<SshMachineLocation> machines = loc.getAvailable(); + SshMachineLocation machine = Iterables.getOnlyElement(machines); + assertMachine(machine, UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 8022), ImmutableMap.of( + SshMachineLocation.PASSWORD.getName(), "mypassword", + "mykey", "myval")); + assertEquals(machine.getPrivateAddresses(), ImmutableSet.of("10.0.0.1")); + } + + @Test + @SuppressWarnings("unchecked") + public void testByonWindowsMachine() throws Exception { + String yaml = Joiner.on("\n").join( + "location:", + " byon:", + " hosts:", + " - winrm: 1.1.1.1:8985", + " privateAddresses: [10.0.0.1]", + " password: mypassword", + " user: myuser", + " mykey: myval", + " osfamily: windows", + "services:", + "- serviceType: brooklyn.entity.basic.BasicApplication"); + + Entity app = createStartWaitAndLogApplication(new StringReader(yaml)); + FixedListMachineProvisioningLocation<WinRmMachineLocation> loc = (FixedListMachineProvisioningLocation<WinRmMachineLocation>) Iterables.get(app.getLocations(), 0); + + Set<WinRmMachineLocation> machines = loc.getAvailable(); + WinRmMachineLocation machine = Iterables.getOnlyElement(machines); + assertMachine(machine, UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 8985), ImmutableMap.of( + SshMachineLocation.PASSWORD.getName(), "mypassword", + "mykey", "myval")); + assertEquals(machine.getPrivateAddresses(), ImmutableSet.of("10.0.0.1")); + } + + @Test + @SuppressWarnings("unchecked") + public void testByonMultiMachine() throws Exception { + String yaml = Joiner.on("\n").join( + "location:", + " byon:", + " hosts:", + " - ssh: 1.1.1.1:8022", + " privateAddresses: [10.0.0.1]", + " password: mypassword", + " user: myuser", + " mykey: myval1", + " - ssh: 1.1.1.2:8022", + " privateAddresses: [10.0.0.2]", + " password: mypassword", + " user: myuser", + " mykey: myval2", + " - winrm: 1.1.1.3:8985", + " privateAddresses: [10.0.0.3]", + " password: mypassword", + " user: myuser", + " mykey: myval3", + " osfamily: windows", + "services:", + "- serviceType: brooklyn.entity.basic.BasicApplication"); + + Entity app = createStartWaitAndLogApplication(new StringReader(yaml)); + FixedListMachineProvisioningLocation<MachineLocation> loc = (FixedListMachineProvisioningLocation<MachineLocation>) Iterables.get(app.getLocations(), 0); + + Set<MachineLocation> machines = loc.getAvailable(); + assertEquals(machines.size(), 3, "machines="+machines); + SshMachineLocation machine1 = (SshMachineLocation) Iterables.find(machines, LocationPredicates.configEqualTo(ConfigKeys.newStringConfigKey("mykey"), "myval1")); + SshMachineLocation machine2 = (SshMachineLocation) Iterables.find(machines, LocationPredicates.configEqualTo(ConfigKeys.newStringConfigKey("mykey"), "myval2")); + WinRmMachineLocation machine3 = (WinRmMachineLocation) Iterables.find(machines, Predicates.instanceOf(WinRmMachineLocation.class)); + + assertMachine(machine1, UserAndHostAndPort.fromParts("myuser", "1.1.1.1", 8022), ImmutableMap.of( + SshMachineLocation.PASSWORD.getName(), "mypassword", + "mykey", "myval1")); + assertEquals(machine1.getPrivateAddresses(), ImmutableSet.of("10.0.0.1")); + + assertMachine(machine2, UserAndHostAndPort.fromParts("myuser", "1.1.1.2", 8022), ImmutableMap.of( + SshMachineLocation.PASSWORD.getName(), "mypassword", + "mykey", "myval2")); + assertEquals(machine2.getPrivateAddresses(), ImmutableSet.of("10.0.0.2")); + + assertMachine(machine3, UserAndHostAndPort.fromParts("myuser", "1.1.1.3", 8985), ImmutableMap.of( + SshMachineLocation.PASSWORD.getName(), "mypassword", + "mykey", "myval3")); + assertEquals(machine3.getPrivateAddresses(), ImmutableSet.of("10.0.0.3")); + } + + private void assertMachine(SshMachineLocation machine, UserAndHostAndPort conn, Map<String, ?> config) { + assertEquals(machine.getAddress().getHostAddress(), conn.getHostAndPort().getHostText()); + assertEquals(machine.getPort(), conn.getHostAndPort().getPort()); + assertEquals(machine.getUser(), conn.getUser()); + for (Map.Entry<String, ?> entry : config.entrySet()) { + Object actualVal = machine.getConfig(ConfigKeys.newConfigKey(Object.class, entry.getKey())); + assertEquals(actualVal, entry.getValue()); + } + } + + private void assertMachine(WinRmMachineLocation machine, UserAndHostAndPort conn, Map<String, ?> config) { + assertEquals(machine.getAddress().getHostAddress(), conn.getHostAndPort().getHostText()); + assertEquals(machine.getConfig(WinRmMachineLocation.WINRM_PORT), (Integer) conn.getHostAndPort().getPort()); + assertEquals(machine.getUser(), conn.getUser()); + for (Map.Entry<String, ?> entry : config.entrySet()) { + Object actualVal = machine.getConfig(ConfigKeys.newConfigKey(Object.class, entry.getKey())); + assertEquals(actualVal, entry.getValue()); + } + } + + @Override + protected Logger getLogger() { + return log; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4e625b9b/utils/common/src/main/java/brooklyn/util/net/UserAndHostAndPort.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/brooklyn/util/net/UserAndHostAndPort.java b/utils/common/src/main/java/brooklyn/util/net/UserAndHostAndPort.java index 49780b4..b2b67e7 100644 --- a/utils/common/src/main/java/brooklyn/util/net/UserAndHostAndPort.java +++ b/utils/common/src/main/java/brooklyn/util/net/UserAndHostAndPort.java @@ -31,6 +31,10 @@ public class UserAndHostAndPort implements Serializable { return new UserAndHostAndPort(user, HostAndPort.fromParts(host, port)); } + public static UserAndHostAndPort fromParts(String user, HostAndPort hostAndPort) { + return new UserAndHostAndPort(user, hostAndPort); + } + /** * Split a string of the form myuser@myhost:1234 into a user, host and port. *
