Repository: incubator-brooklyn Updated Branches: refs/heads/master c886d42dd -> efaceed3d
Don't use hostname when obtaining machines By default Softlayer machines will have a hostname of the form xxxx.local.brooklyncentral.com which resolves to a parked domain, not the public IP of the machine. When the machine has privte IP only the hostname was used instead of the private IP which caused a failure. This commit removes the hostname fallback - I don't see a valid reason why it should be included. Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/229a9124 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/229a9124 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/229a9124 Branch: refs/heads/master Commit: 229a912431a428299e808b6fe68aafa15f7f8a7a Parents: b2d3f33 Author: Svetoslav Neykov <[email protected]> Authored: Wed Jul 15 17:22:04 2015 +0300 Committer: Svetoslav Neykov <[email protected]> Committed: Thu Jul 16 11:54:41 2015 +0300 ---------------------------------------------------------------------- .../brooklyn/util/BrooklynMavenArtifacts.java | 2 +- .../location/jclouds/JcloudsLocation.java | 26 ++- usage/qa/pom.xml | 9 + .../SoftlayerObtainPrivateLiveTest.java | 225 +++++++++++++++++++ 4 files changed, 252 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/229a9124/core/src/main/java/brooklyn/util/BrooklynMavenArtifacts.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/util/BrooklynMavenArtifacts.java b/core/src/main/java/brooklyn/util/BrooklynMavenArtifacts.java index f75f8ba..50a5879 100644 --- a/core/src/main/java/brooklyn/util/BrooklynMavenArtifacts.java +++ b/core/src/main/java/brooklyn/util/BrooklynMavenArtifacts.java @@ -35,7 +35,7 @@ public class BrooklynMavenArtifacts { public static MavenArtifact artifact(String subgroupUnderIoBrooklyn, String artifactId, String packaging, String classifier) { return new MavenArtifact( - Strings.isEmpty(subgroupUnderIoBrooklyn) ? "io.brooklyn" : "io.brooklyn."+subgroupUnderIoBrooklyn, + Strings.isEmpty(subgroupUnderIoBrooklyn) ? "org.apache.brooklyn" : "org.apache.brooklyn."+subgroupUnderIoBrooklyn, artifactId, packaging, classifier, BrooklynVersion.get()); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/229a9124/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java index 214f946..8393d1c 100644 --- a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java +++ b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java @@ -181,7 +181,6 @@ import com.google.common.collect.Sets.SetView; import com.google.common.io.Files; import com.google.common.net.HostAndPort; import com.google.common.primitives.Ints; -import com.google.common.reflect.TypeToken; /** * For provisioning and managing VMs in a particular provider/region, using jclouds. @@ -233,6 +232,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } @Override + @Deprecated public JcloudsLocation configure(Map<?,?> properties) { super.configure(properties); @@ -255,7 +255,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im if (maxConcurrent == null || maxConcurrent < 1) { throw new IllegalStateException(MAX_CONCURRENT_MACHINE_CREATIONS.getName() + " must be >= 1, but was "+maxConcurrent); } - setConfig(MACHINE_CREATION_SEMAPHORE, new Semaphore(maxConcurrent, true)); + config().set(MACHINE_CREATION_SEMAPHORE, new Semaphore(maxConcurrent, true)); } return this; } @@ -441,7 +441,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } public void setDefaultImageId(String val) { - setConfig(DEFAULT_IMAGE_ID, val); + config().set(DEFAULT_IMAGE_ID, val); } // TODO remove tagMapping, or promote it @@ -454,6 +454,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im // TODO Decide on semantics. If I give "TomcatServer" and "Ubuntu", then must I get back an image that matches both? // Currently, just takes first match that it finds... + @Override public Map<String,Object> getProvisioningFlags(Collection<String> tags) { Map<String,Object> result = Maps.newLinkedHashMap(); Collection<String> unmatchedTags = Lists.newArrayList(); @@ -542,6 +543,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im node); } + @Override public MachineMetadata getMachineMetadata(MachineLocation l) { if (l instanceof JcloudsSshMachineLocation) { return getMachineMetadata( ((JcloudsSshMachineLocation)l).node ); @@ -586,6 +588,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im * as well as ACCESS_IDENTITY and ACCESS_CREDENTIAL, * plus any further properties to specify e.g. images, hardware profiles, accessing user * (for initial login, and a user potentially to create for subsequent ie normal access) */ + @Override public MachineLocation obtain(Map<?,?> flags) throws NoMachinesAvailableException { ConfigBag setup = ConfigBag.newInstanceExtending(config().getBag(), flags); Integer attempts = setup.get(MACHINE_CREATE_ATTEMPTS); @@ -1292,7 +1295,6 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im if (optionsMap.isEmpty()) return; Class<? extends TemplateOptions> clazz = options.getClass(); - Iterable<Method> methods = Arrays.asList(clazz.getMethods()); for(final Map.Entry<String, Object> option : optionsMap.entrySet()) { Maybe<?> result = MethodCoercions.tryFindAndInvokeBestMatchingMethod(options, option.getKey(), option.getValue()); if(result.isAbsent()) { @@ -2537,7 +2539,7 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im return inferredHostAndPort.getHostText(); } else { LOG.warn("Error querying aws-ec2 instance "+node.getId()+"@"+node.getLocation()+" over ssh for its hostname; falling back to jclouds metadata for address", e); - } + } } } } @@ -2547,12 +2549,18 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im } private String getPublicHostnameGeneric(NodeMetadata node, @Nullable ConfigBag setup) { - //prefer the public address to the hostname because hostname is sometimes wrong/abbreviated - //(see that javadoc; also e.g. on rackspace, the hostname lacks the domain) + // JcloudsUtil.getFirstReachableAddress() already succeeded so at least one of the provided + // public and private IPs is reachable. Prefer the public IP. Don't use hostname as a fallback + // from the public address - if public address is missing why would hostname resolve to a + // public IP? It is sometimes wrong/abbreviated, resolving to the wrong IP, also e.g. on + // rackspace, the hostname lacks the domain. + // + // TODO Some of the private addresses might not be reachable, should check connectivity before + // making a choice. + // TODO Choose an IP once and stick to it - multiple places call JcloudsUtil.getFirstReachableAddress(), + // could even get different IP on each call. if (groovyTruth(node.getPublicAddresses())) { return node.getPublicAddresses().iterator().next(); - } else if (groovyTruth(node.getHostname())) { - return node.getHostname(); } else if (groovyTruth(node.getPrivateAddresses())) { return node.getPrivateAddresses().iterator().next(); } else { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/229a9124/usage/qa/pom.xml ---------------------------------------------------------------------- diff --git a/usage/qa/pom.xml b/usage/qa/pom.xml index c138345..7652f4e 100644 --- a/usage/qa/pom.xml +++ b/usage/qa/pom.xml @@ -41,6 +41,14 @@ </dependency> <dependency> + <groupId>org.apache.brooklyn</groupId> + <artifactId>brooklyn-dist</artifactId> + <classifier>dist</classifier> + <type>tar.gz</type> + <version>${project.version}</version> + </dependency> + + <dependency> <groupId>net.sf.jopt-simple</groupId> <artifactId>jopt-simple</artifactId> </dependency> @@ -93,6 +101,7 @@ </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> + <excludeArtifactIds>brooklyn-dist</excludeArtifactIds> </configuration> </execution> </executions> http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/229a9124/usage/qa/src/test/java/brooklyn/qa/brooklynnode/SoftlayerObtainPrivateLiveTest.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/test/java/brooklyn/qa/brooklynnode/SoftlayerObtainPrivateLiveTest.java b/usage/qa/src/test/java/brooklyn/qa/brooklynnode/SoftlayerObtainPrivateLiveTest.java new file mode 100644 index 0000000..16b2e3a --- /dev/null +++ b/usage/qa/src/test/java/brooklyn/qa/brooklynnode/SoftlayerObtainPrivateLiveTest.java @@ -0,0 +1,225 @@ +/* + * 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.qa.brooklynnode; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.basic.BrooklynObjectInternal.ConfigurationSupportInternal; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.basic.ServiceStateLogic; +import brooklyn.entity.brooklynnode.BrooklynEntityMirror; +import brooklyn.entity.brooklynnode.BrooklynNode; +import brooklyn.entity.brooklynnode.BrooklynNode.DeployBlueprintEffector; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.location.Location; +import brooklyn.location.jclouds.JcloudsLocationConfig; +import brooklyn.management.ManagementContext; +import brooklyn.test.EntityTestUtils; +import brooklyn.test.entity.LocalManagementContextForTests; +import brooklyn.test.entity.TestApplication; +import brooklyn.util.BrooklynMavenArtifacts; +import brooklyn.util.collections.MutableList; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.maven.MavenRetriever; +import brooklyn.util.text.Strings; +import brooklyn.util.time.Duration; + +import com.google.common.base.Joiner; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * Tests obtaining a machine with a private IP only. For the machine to be + * accessible we should have a gateway machine already running in the same + * network. + * + * Starts a BrooklynNode with a public IP and on it starts two machines - + * one with public and one with private only IP. + * + * The test lives here so it has access to the dist archive. + */ +public class SoftlayerObtainPrivateLiveTest { + + // Expects that the location is already configured in brooklyn.properties + private static final String LOCATION_SPEC = "jclouds:aws-ec2"; + + + private static final Logger log = LoggerFactory.getLogger(SoftlayerObtainPrivateLiveTest.class); + + private static final ImmutableMap<String, Duration> TIMEOUT = ImmutableMap.of("timeout", Duration.ONE_HOUR); + // Should this be a black list instead? + private Set<String> LOCATION_CONFIG_WHITE_LIST = ImmutableSet.of( + JcloudsLocationConfig.CLOUD_REGION_ID.getName(), + JcloudsLocationConfig.ACCESS_IDENTITY.getName(), + JcloudsLocationConfig.ACCESS_CREDENTIAL.getName(), + JcloudsLocationConfig.IMAGE_ID.getName(), + JcloudsLocationConfig.HARDWARE_ID.getName(), + JcloudsLocationConfig.TEMPLATE_OPTIONS.getName()); + + private static final String NAMED_LOCATION_PREFIX = "brooklyn.location.named."; + private static final String TEST_LOCATION = "test-location"; + private static final String TEST_LOCATION_PRIVATE = TEST_LOCATION + "-private"; + private static final String TEST_LOCATION_PUBLIC = TEST_LOCATION + "-public"; + + private BrooklynLauncher launcher; + private ManagementContext mgmt; + private TestApplication app; + private Location loc; + + @BeforeMethod(alwaysRun=true) + public void setUp() { + mgmt = LocalManagementContextForTests.builder(true) + .useDefaultProperties() + .build(); + launcher = BrooklynLauncher + .newInstance() + .managementContext(mgmt) + .start(); + app = mgmt.getEntityManager().createEntity(EntitySpec.create(TestApplication.class)); + mgmt.getEntityManager().manage(app); + loc = createLocation(); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() { + Entities.destroyAll(mgmt); + launcher.terminate(); + } + + private Location createLocation() { + return mgmt.getLocationRegistry().resolve(LOCATION_SPEC); + } + + @Test(groups="Live") + public void testObtain() { + String localUrl = MavenRetriever.localUrl(BrooklynMavenArtifacts.artifact("", "brooklyn-dist", "tar.gz", "dist")); + String userName = "admin"; + String userPassword = Strings.makeRandomId(6); + String remoteConfig = Joiner.on('\n').join(MutableList.of( + "brooklyn.webconsole.security.users=" + userName, + "brooklyn.webconsole.security.user.admin.password=" + userPassword) + .appendAll(getLocationConfig()) + .append("\n")); + + log.info("Using distribution {}", localUrl); + log.info("Remote credentials are {}:{}", userName, userPassword); + log.info("Remote config \n{}", remoteConfig); + + EntitySpec<BrooklynNode> nodeSpec = EntitySpec.create(BrooklynNode.class) + .configure(BrooklynNode.DISTRO_UPLOAD_URL, localUrl) + .configure(BrooklynNode.MANAGEMENT_USER, userName) + .configure(BrooklynNode.MANAGEMENT_PASSWORD, userPassword) + .configure(BrooklynNode.BROOKLYN_LOCAL_PROPERTIES_CONTENTS, remoteConfig); + + BrooklynNode node = app.createAndManageChild(nodeSpec); + app.start(ImmutableList.of(loc)); + try { + // TODO Assumes that the second-level machines will be in the same private network as the BrooklynNode machine. + // The private network id can be set explicitly in templateOptions.primaryBackendNetworkComponentNetworkVlanId. + BrooklynEntityMirror publicApp = deployTestApp(node, true); + BrooklynEntityMirror privateApp = deployTestApp(node, false); + + EntityTestUtils.assertAttributeEventually(TIMEOUT, publicApp, ServiceStateLogic.SERVICE_STATE_ACTUAL, + Predicates.in(ImmutableList.of(Lifecycle.RUNNING, Lifecycle.ON_FIRE))); + EntityTestUtils.assertAttributeEventually(TIMEOUT, privateApp, ServiceStateLogic.SERVICE_STATE_ACTUAL, + Predicates.in(ImmutableList.of(Lifecycle.RUNNING, Lifecycle.ON_FIRE))); + + EntityTestUtils.assertAttributeEquals(publicApp, ServiceStateLogic.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + EntityTestUtils.assertAttributeEquals(privateApp, ServiceStateLogic.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING); + + EntityTestUtils.assertAttributeEqualsEventually(publicApp, Attributes.SERVICE_UP, Boolean.TRUE); + EntityTestUtils.assertAttributeEqualsEventually(privateApp, Attributes.SERVICE_UP, Boolean.TRUE); + } finally { + node.invoke(BrooklynNode.STOP_NODE_AND_KILL_APPS, ImmutableMap.<String, String>of()).getUnchecked(); + } + } + + private BrooklynEntityMirror deployTestApp(BrooklynNode node, boolean hasPublicNetwork) { + String entityId = node.invoke(BrooklynNode.DEPLOY_BLUEPRINT, ImmutableMap.of(DeployBlueprintEffector.BLUEPRINT_CAMP_PLAN.getName(), getBlueprintPlan(hasPublicNetwork))).getUnchecked(); + return node.addChild(EntitySpec.create(BrooklynEntityMirror.class) + .configure(BrooklynEntityMirror.MIRRORED_ENTITY_ID, entityId) + .configure(BrooklynEntityMirror.MIRRORED_ENTITY_URL, node.getAttribute(BrooklynNode.WEB_CONSOLE_URI).toString() + "/v1/applications/"+entityId+"/entities/"+entityId)); + } + + private Collection<String> getLocationConfig() { + Map<String, Object> config = MutableMap.copyOf(((ConfigurationSupportInternal)loc.config()).getBag().getAllConfig()); + config.putAll(customizeSharedLocation()); + return MutableList.<String>of() + .appendAll(createLocationConfig(NAMED_LOCATION_PREFIX + TEST_LOCATION, (String)config.get("spec.original"), config)) + .appendAll(createLocationConfig(NAMED_LOCATION_PREFIX + TEST_LOCATION_PUBLIC, "named:" + TEST_LOCATION, customizePublicLocation())) + .appendAll(createLocationConfig(NAMED_LOCATION_PREFIX + TEST_LOCATION_PRIVATE, "named:" + TEST_LOCATION, customizePrivateLocation())); + } + + private Collection<String> createLocationConfig(String prefix, String parent, Map<String, ?> config) { + return MutableList.<String>of() + .append(prefix + "=" + parent) + .appendAll(locationConfigToProperties(prefix, config)); + } + + protected Collection<String> locationConfigToProperties(String prefix, Map<String, ?> config) { + Collection<String> loc = new ArrayList<String>(); + for (String key : config.keySet()) { + if (LOCATION_CONFIG_WHITE_LIST.contains(key)) { + loc.add(prefix + "." + key + "=" + config.get(key)); + } + } + return loc; + } + + protected Map<String, String> customizeSharedLocation() { + return ImmutableMap.of(); + } + + protected Map<String, String> customizePublicLocation() { + return ImmutableMap.of(); + } + + protected Map<String, String> customizePrivateLocation() { + return ImmutableMap.<String, String>of( + "templateOptions", "{privateNetworkOnlyFlag: true}"); + } + + protected String getBlueprintPlan(boolean hasPublicNetwork) { + return Joiner.on('\n').join(ImmutableList.of( + "location: " + getTestLocation(hasPublicNetwork), + "services:", + "- type: brooklyn.entity.machine.MachineEntity", + " name: " + (hasPublicNetwork ? "Public" : "Private") + )); + } + + private static String getTestLocation(boolean hasPublicNetwork) { + return hasPublicNetwork ? TEST_LOCATION_PUBLIC : TEST_LOCATION_PRIVATE; + } + +}
