blob: 9a90b6cd7897dc608f622ee695aeb30377aaae04 [file] [log] [blame]
/*
* 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.dns;
import static org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEqualsContinually;
import static org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEventually;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.entity.ImplementedBy;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationRegistry;
import org.apache.brooklyn.api.location.LocationResolver;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityAsserts;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.location.BasicLocationRegistry;
import org.apache.brooklyn.core.location.LocationConfigKeys;
import org.apache.brooklyn.core.location.Locations;
import org.apache.brooklyn.core.location.Machines;
import org.apache.brooklyn.core.location.SimulatedLocation;
import org.apache.brooklyn.core.location.geo.HostGeoInfo;
import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport;
import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.entity.group.DynamicFabric;
import org.apache.brooklyn.entity.group.DynamicGroup;
import org.apache.brooklyn.entity.group.DynamicRegionsFabric;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.collections.CollectionFunctionals;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
public class AbstractGeoDnsServiceTest extends BrooklynAppUnitTestSupport {
public static final Logger log = LoggerFactory.getLogger(AbstractGeoDnsServiceTest.class);
private static final String WEST_IP = "100.0.0.1";
private static final String EAST_IP = "100.0.0.2";
private static final double WEST_LATITUDE = 0, WEST_LONGITUDE = -60;
private static final double EAST_LATITUDE = 0, EAST_LONGITUDE = 60;
private static final String NORTH_IP = "10.0.0.1";
private static final double NORTH_LATITUDE = 60, NORTH_LONGITUDE = 0;
private Location westParent;
private Location westChild;
private Location westChildWithLocation;
private Location eastParent;
private Location eastChild;
private Location eastChildWithLocationAndWithPrivateHostname;
private Location northParent;
private Location northChildWithLocation;
private DynamicRegionsFabric fabric;
private DynamicGroup testEntities;
private GeoDnsTestService geoDns;
@Override
@BeforeMethod(alwaysRun=true)
public void setUp() throws Exception {
super.setUp();
westParent = newSimulatedLocation("West parent", WEST_LATITUDE, WEST_LONGITUDE);
// west uses public IP for name, so is always picked up
westChild = newSshMachineLocation("West child", WEST_IP, westParent);
westChildWithLocation = newSshMachineLocation("West child with location", WEST_IP, WEST_IP, westParent, WEST_LATITUDE, WEST_LONGITUDE);
// east has public IP but private IP hostname, so should also be picked up but by a different path
eastParent = newSimulatedLocation("East parent", EAST_LATITUDE, EAST_LONGITUDE);
eastChild = newSshMachineLocation("East child", EAST_IP, eastParent);
eastChildWithLocationAndWithPrivateHostname = newSshMachineLocation("East child with location", "localhost", EAST_IP, eastParent, EAST_LATITUDE, EAST_LONGITUDE);
// north has a private IP and private hostname so should not be picked up when we turn off ADD_ANYTHING
northParent = newSimulatedLocation("North parent", NORTH_LATITUDE, NORTH_LONGITUDE);
northChildWithLocation = newSshMachineLocation("North child", "localhost", NORTH_IP, northParent, NORTH_LATITUDE, NORTH_LONGITUDE);
((BasicLocationRegistry) mgmt.getLocationRegistry()).registerResolver(new LocationResolver() {
@Override public boolean isEnabled() { return true; }
@Override
public LocationSpec<? extends Location> newLocationSpecFromString(String spec, Map<?, ?> locationFlags, LocationRegistry registry) {
if (!spec.equals("test:north")) throw new IllegalStateException("unsupported");
return newSshMachineLocationSpec("North child", "localhost", NORTH_IP, northParent, NORTH_LATITUDE, NORTH_LONGITUDE);
}
@Override public void init(ManagementContext managementContext) {}
@Override public String getPrefix() { return "test"; }
@Override
public boolean accepts(String spec, LocationRegistry registry) {
return spec.startsWith(getPrefix());
}
});
Locations.manage(westParent, mgmt);
Locations.manage(eastParent, mgmt);
Locations.manage(northParent, mgmt);
fabric = app.createAndManageChild(EntitySpec.create(DynamicRegionsFabric.class)
.configure(DynamicFabric.MEMBER_SPEC, EntitySpec.create(TestEntity.class)));
testEntities = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
.configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(TestEntity.class)));
geoDns = app.createAndManageChild(EntitySpec.create(GeoDnsTestService.class)
.configure(AbstractGeoDnsService.ENTITY_PROVIDER, testEntities));
}
private SimulatedLocation newSimulatedLocation(String name, double lat, double lon) {
return mgmt.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class)
.displayName(name)
.configure("latitude", lat)
.configure("longitude", lon));
}
private Location newSshMachineLocation(String name, String address, Location parent) {
return mgmt.getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
.parent(parent)
.displayName(name)
.configure("address", address));
}
private Location newSshMachineLocation(String name, String hostname, String address, Location parent, double lat, double lon) {
return mgmt.getLocationManager().createLocation(newSshMachineLocationSpec(name, hostname, address, parent, lat, lon));
}
private LocationSpec<SshMachineLocation> newSshMachineLocationSpec(String name, String hostname, String address, Location parent, double lat, double lon) {
return LocationSpec.create(SshMachineLocation.class)
.parent(parent)
.displayName(name)
.configure("hostname", hostname)
.configure("address", address)
.configure("latitude", lat)
.configure("longitude", lon);
}
@Test(groups="Integration")
public void testGeoInfoOnLocation() {
app.start( ImmutableList.of(westChildWithLocation, eastChildWithLocationAndWithPrivateHostname) );
publishSensors(2, true, true, true);
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(2));
assertIsTarget("West child with location");
assertIsTarget("East child with location");
}
@Test(groups="Integration")
public void testGeoInfoOnParentLocation() {
app.start( ImmutableList.of(westChild, eastChild) );
publishSensors(2, true, false, false);
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(2));
assertIsTarget("West child");
assertIsTarget("East child");
}
@Test(groups="Integration")
public void testSubscribesToHostname() {
geoDns.config().set(GeoDnsTestServiceImpl.ADD_ANYTHING, false);
app.start( ImmutableList.of(westChild, eastChildWithLocationAndWithPrivateHostname) );
Assert.assertEquals(geoDns.getTargetHostsByName().size(), 0);
publishSensors(2, true, true, true);
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(2));
Assert.assertEquals(geoDns.getTargetHostsByName().size(), 2);
assertIsTarget("West child");
assertIsTarget("East child with location");
}
protected void publishSensors(int expectedSize, boolean includeServiceUp, boolean includeHostname, boolean includeAddress) {
// First wait for the right size of group; the dynamic group gets notified asynchronously
// of nodes added/removed, so if we don't wait then might not set value for all members.
EntityAsserts.assertGroupSizeEqualsEventually(testEntities, expectedSize);
for (Entity e: testEntities.getMembers()) {
if (includeServiceUp)
e.sensors().set(Attributes.SERVICE_UP, true);
SshMachineLocation l = Machines.findUniqueMachineLocation(e.getLocations(), SshMachineLocation.class).get();
if (includeAddress)
e.sensors().set(Attributes.ADDRESS, l.getAddress().getHostAddress());
String h = (String) l.config().getBag().getStringKey("hostname");
if (h==null) h = l.getAddress().getHostName();
if (includeHostname)
e.sensors().set(Attributes.HOSTNAME, h);
}
}
@Test(groups="Integration")
public void testChildAddedLate() {
app.start( ImmutableList.of(westChild, eastChildWithLocationAndWithPrivateHostname) );
publishSensors(2, true, false, false);
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(2));
String id3 = fabric.addRegion("test:north");
publishSensors(3, true, false, false);
try {
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(3));
} catch (Throwable e) {
log.warn("Did not pick up third entity, targets are "+geoDns.getAttribute(AbstractGeoDnsService.TARGETS)+" (rethrowing): "+e);
Exceptions.propagate(e);
}
assertIsTarget("North child");
log.info("targets: "+geoDns.getTargetHostsByName());
}
@Test(groups="Integration")
public void testFiltersEntirelyPrivate() {
geoDns.config().set(GeoDnsTestServiceImpl.ADD_ANYTHING, false);
app.start( ImmutableList.of(westChild, eastChildWithLocationAndWithPrivateHostname, northChildWithLocation) );
Assert.assertEquals(geoDns.getTargetHostsByName().size(), 0);
publishSensors(3, true, true, true);
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(2));
Assert.assertEquals(geoDns.getTargetHostsByName().size(), 2);
assertIsTarget("West child");
assertIsTarget("East child with location");
assertIsNotTarget("North child");
}
// TODO BROOKLYN-272, Disabled, because fails non-deterministically in jenkins:
// org.apache.brooklyn.util.exceptions.PropagatedRuntimeException: failed succeeds-eventually, 69 attempts, 30003ms elapsed: AssertionError: val={R48yHLqg=<address-ignored>, eBHFc4Qd=<address-ignored>}
// at org.apache.brooklyn.test.Asserts.fail(Asserts.java:721)
// at org.apache.brooklyn.test.Asserts.assertTrue(Asserts.java:703)
// at org.apache.brooklyn.core.entity.EntityAsserts$2.run(EntityAsserts.java:92)
// at org.apache.brooklyn.test.Asserts$RunnableAdapter.call(Asserts.java:1277)
// at org.apache.brooklyn.test.Asserts.succeedsEventually(Asserts.java:930)
// at org.apache.brooklyn.test.Asserts.succeedsEventually(Asserts.java:854)
// at org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEventually(EntityAsserts.java:89)
// at org.apache.brooklyn.core.entity.EntityAsserts.assertAttributeEventually(EntityAsserts.java:84)
// at org.apache.brooklyn.entity.dns.AbstractGeoDnsServiceTest.testFiltersForRunningEntities(AbstractGeoDnsServiceTest.java:263)
@Test(groups={"Broken"})
public void testFiltersForRunningEntities() {
app.start(ImmutableList.of(westChildWithLocation, eastChildWithLocationAndWithPrivateHostname));
publishSensors(2, true, true, true);
TestEntity problemChild = Iterables.get(Entities.descendantsAndSelf(app, TestEntity.class), 0);
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(2));
problemChild.sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(1));
problemChild.sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.RUNNING);
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(2));
}
@Test(groups="Integration")
public void testCanDisableFilterForRunningEntities() throws Exception {
geoDns.config().set(AbstractGeoDnsService.FILTER_FOR_RUNNING, false);
app.start(ImmutableList.of(westChildWithLocation, eastChildWithLocationAndWithPrivateHostname));
publishSensors(2, true, true, true);
assertAttributeEventually(geoDns, AbstractGeoDnsService.TARGETS, CollectionFunctionals.<String>mapSizeEquals(2));
final Map<String, String> targets = ImmutableMap.copyOf(geoDns.sensors().get(AbstractGeoDnsService.TARGETS));
TestEntity problemChild = Iterables.get(Entities.descendantsAndSelf(app, TestEntity.class), 0);
problemChild.sensors().set(Attributes.SERVICE_STATE_ACTUAL, Lifecycle.ON_FIRE);
assertAttributeEqualsContinually(geoDns, AbstractGeoDnsService.TARGETS, targets);
}
private void assertIsTarget(String target) {
assertTrue(geoDns.getTargetHostsByName().containsKey(target), "targets=" + geoDns.getTargetHostsByName());
}
private void assertIsNotTarget(String target) {
assertFalse(geoDns.getTargetHostsByName().containsKey(target), "targets=" + geoDns.getTargetHostsByName());
}
@ImplementedBy(GeoDnsTestServiceImpl.class)
public static interface GeoDnsTestService extends AbstractGeoDnsService {
public Map<String, HostGeoInfo> getTargetHostsByName();
}
public static class GeoDnsTestServiceImpl extends AbstractGeoDnsServiceImpl implements GeoDnsTestService {
public Map<String, HostGeoInfo> targetHostsByName = new LinkedHashMap<String, HostGeoInfo>();
public static final ConfigKey<Boolean> ADD_ANYTHING = ConfigKeys.newBooleanConfigKey("test.add.always", "", true);
public GeoDnsTestServiceImpl() {
}
@Override
public Map<String, HostGeoInfo> getTargetHostsByName() {
synchronized (targetHostsByName) {
return ImmutableMap.copyOf(targetHostsByName);
}
}
@Override
protected boolean addTargetHost(Entity e) {
if (!getConfig(ADD_ANYTHING)) {
return super.addTargetHost(e);
} else {
//ignore geo lookup, override parent menu
if (e.getLocations().isEmpty()) {
log.info("GeoDns TestService ignoring target host {} (no location)", e);
return false;
}
Location l = Iterables.getOnlyElement(e.getLocations());
HostGeoInfo geoInfo = new HostGeoInfo("<address-ignored>", l.getDisplayName(),
l.getConfig(LocationConfigKeys.LATITUDE), l.getConfig(LocationConfigKeys.LONGITUDE));
log.info("GeoDns TestService adding target host {} {}", e, geoInfo);
targetHosts.put(e, geoInfo);
return true;
}
}
@Override
protected void reconfigureService(Collection<HostGeoInfo> targetHosts) {
synchronized (targetHostsByName) {
targetHostsByName.clear();
for (HostGeoInfo host : targetHosts) {
if (host != null) targetHostsByName.put(host.displayName, host);
}
}
}
@Override
public String getHostname() {
return "localhost";
}
}
}