brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From grk...@apache.org
Subject [3/4] git commit: BindDnsServer improvements
Date Fri, 31 Oct 2014 14:36:39 GMT
BindDnsServer improvements

* Rebinds
* Supports CNAME records
* Integration tests and more live tests


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/3644e198
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/3644e198
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/3644e198

Branch: refs/heads/master
Commit: 3644e1980701faa03b17f1d3478d9b629ecbf7d3
Parents: 70ca25c
Author: Sam Corbett <sam.corbett@cloudsoftcorp.com>
Authored: Wed Oct 29 12:07:40 2014 +0000
Committer: Sam Corbett <sam.corbett@cloudsoftcorp.com>
Committed: Wed Oct 29 12:07:40 2014 +0000

----------------------------------------------------------------------
 .../entity/network/bind/BindDnsServer.java      |  41 ++-
 .../network/bind/BindDnsServerDriver.java       |   7 +-
 .../entity/network/bind/BindDnsServerImpl.java  | 267 +++++++++++--------
 .../network/bind/BindDnsServerSshDriver.java    |  23 ++
 .../brooklyn/entity/network/bind/domain.zone    |  13 +-
 .../brooklyn/entity/network/bind/reverse.zone   |   5 +-
 .../bind/BindDnsServerIntegrationTest.java      | 136 ++++++++++
 .../network/bind/BindDnsServerLiveTest.java     |  72 ++++-
 .../entity/network/bind/BindDnsServerTest.java  |  67 -----
 .../bind/DoNothingSoftwareProcessDriver.java    |  56 ++++
 .../network/bind/PrefixAndIdEnricher.java       |  57 ++++
 .../network/bind/TestBindDnsServerImpl.java     |  60 +++++
 12 files changed, 602 insertions(+), 202 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServer.java
----------------------------------------------------------------------
diff --git a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServer.java b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServer.java
index c4e5d83..0e7e7c1 100644
--- a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServer.java
+++ b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServer.java
@@ -18,6 +18,7 @@
  */
 package brooklyn.entity.network.bind;
 
+import java.lang.Long;
 import java.util.Map;
 
 import brooklyn.catalog.Catalog;
@@ -26,6 +27,7 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.annotation.Effector;
 import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.DynamicGroup;
 import brooklyn.entity.basic.SoftwareProcess;
 import brooklyn.entity.proxying.ImplementedBy;
 import brooklyn.event.AttributeSensor;
@@ -39,6 +41,8 @@ import brooklyn.util.net.Cidr;
 
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.Multimap;
 import com.google.common.reflect.TypeToken;
 
 /**
@@ -48,7 +52,6 @@ import com.google.common.reflect.TypeToken;
 @ImplementedBy(BindDnsServerImpl.class)
 public interface BindDnsServer extends SoftwareProcess {
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
     @SetFromFlag("filter")
     ConfigKey<Predicate<? super Entity>> ENTITY_FILTER = ConfigKeys.newConfigKey(new TypeToken<Predicate<? super Entity>>() {},
             "bind.entity.filter", "Filter for entities which will use the BIND DNS service for name resolution",
@@ -66,10 +69,9 @@ public interface BindDnsServer extends SoftwareProcess {
     ConfigKey<String> MANAGEMENT_CIDR = ConfigKeys.newStringConfigKey(
             "bind.access.cidr", "Subnet CIDR or ACL allowed to access DNS", "0.0.0.0/0");
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
     @SetFromFlag("hostnameSensor")
     ConfigKey<AttributeSensor<String>> HOSTNAME_SENSOR = ConfigKeys.newConfigKey(new TypeToken<AttributeSensor<String>>() {},
-            "bind.sensor.hostname", "Sensor on managed entities that reports the hostname", Attributes.HOSTNAME);
+            "bind.sensor.hostname", "Sensor on managed entities that reports the hostname");
 
     PortAttributeSensorAndConfigKey DNS_PORT =
             new PortAttributeSensorAndConfigKey("bind.port", "BIND DNS port for TCP and UDP", PortRanges.fromString("53"));
@@ -116,16 +118,33 @@ public interface BindDnsServer extends SoftwareProcess {
             "bind.template.resolv-conf", "The resolver configuration file for clients (as FreeMarker template)",
             "classpath://brooklyn/entity/network/bind/resolv.conf");
 
-    /**
-     * @return the hostname to IP mappings stored in this DNS server's conf file
-     */
-    @Effector(description="Gets the Hostname to IP mappings stored in this DNS server's conf file")
-    public Map<String,String> getAddressMappings();
+    AttributeSensor<DynamicGroup> ENTITIES = Sensors.newSensor(DynamicGroup.class,
+            "bind.entities", "The entities being managed by this server");
+
+    AttributeSensor<Multimap<String, String>> ADDRESS_MAPPINGS = Sensors.newSensor(new TypeToken<Multimap<String, String>>() {},
+            "bind.mappings", "All address mappings maintained by the server, in form address -> [names]");
+
+    AttributeSensor<Map<String, String>> A_RECORDS = Sensors.newSensor(new TypeToken<Map<String, String>>() {},
+            "bind.records.a", "All A records for the server, in form name -> address");
+
+    AttributeSensor<Multimap<String, String>> CNAME_RECORDS = Sensors.newSensor(new TypeToken<Multimap<String, String>>() {},
+            "bind.records.cname", "All CNAME records for the server, in form name -> [names]");
+
+    AttributeSensor<Map<String, String>> PTR_RECORDS = Sensors.newSensor(new TypeToken<Map<String, String>>() {},
+            "bind.records.ptr", "All PTR records for the server, in form address -> name. Entries will be in REVERSE_LOOKUP_CIDR. " +
+                    "Entries are guaranteed to have an inverse mapping in A_RECORDS.");
+
+    AttributeSensor<Long> SERIAL = Sensors.newLongSensor(
+            "bind.serial", "A serial number guaranteed to be valid for use in a modified domain.zone or reverse.zone file");
+
+    public Multimap<String, String> getAddressMappings();
 
     /**
-     * @return the IP to hostname mappings stoed in this DNS server's conf file
+     * @return the IP to hostname mappings stored in this DNS server's conf file
+     * @deprecated since 0.7.0 use {@link #PTR_RECORDS} instead.
      */
-    @Effector(description="Gets the IP to hostname mappings stoed in this DNS server's conf file")
-    public Map<String,String> getReverseMappings();
+    @Deprecated
+    @Effector(description="Gets the IP to hostname mappings stored in this DNS server's conf file")
+    public Map<String, String> getReverseMappings();
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerDriver.java
----------------------------------------------------------------------
diff --git a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerDriver.java b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerDriver.java
index fce68a4..7c3d0a2 100644
--- a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerDriver.java
+++ b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerDriver.java
@@ -21,5 +21,10 @@ package brooklyn.entity.network.bind;
 import brooklyn.entity.basic.SoftwareProcessDriver;
 
 public interface BindDnsServerDriver extends SoftwareProcessDriver {
-    // Marker Interface
+
+    /**
+     * Uploads configuration files and restarts the service.
+     */
+    public void updateBindConfiguration();
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java
----------------------------------------------------------------------
diff --git a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java
index a4de54a..fc570aa 100644
--- a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java
+++ b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerImpl.java
@@ -18,58 +18,61 @@
  */
 package brooklyn.entity.network.bind;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+import java.lang.Long;
+import java.util.Collection;
 import java.util.Map;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicLong;
 
 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.Splitter;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.Multimaps;
+
 import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
 import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.entity.basic.EntityFunctions;
+import brooklyn.entity.basic.Lifecycle;
 import brooklyn.entity.basic.SoftwareProcessImpl;
 import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
 import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.trait.Startable;
-import brooklyn.location.Location;
-import brooklyn.location.MachineLocation;
+import brooklyn.location.basic.Machines;
 import brooklyn.location.basic.SshMachineLocation;
 import brooklyn.policy.PolicySpec;
-import brooklyn.util.collections.MutableMap;
+import brooklyn.util.guava.Maybe;
 import brooklyn.util.net.Cidr;
 import brooklyn.util.ssh.BashCommands;
 import brooklyn.util.text.Strings;
 
-import com.google.common.base.Joiner;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicates;
-import com.google.common.base.Splitter;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-
 /**
  * This sets up a BIND DNS server.
  * <p>
- * <b>NOTE</b> This entity has only been certified on <i>CentOS</i> and <i>RHEL</i> operating systems.
+ * <b>NOTE</b> This entity has only been certified on <i>CentOS</i>, <i>RHEL</i>,
+ * <i>Ubuntu</i> and <i>Debian</i> operating systems.
  */
 public class BindDnsServerImpl extends SoftwareProcessImpl implements BindDnsServer {
 
     private static final Logger LOG = LoggerFactory.getLogger(BindDnsServerImpl.class);
 
-    private AtomicLong serial = new AtomicLong(System.currentTimeMillis());
-    private final Object[] mutex = new Object[0];
-    private DynamicGroup entities;
-    private MemberTrackingPolicy policy;
-    private Multimap<Location, Entity> entityLocations = HashMultimap.create();
-    private ConcurrentMap<String, String> addressMappings = Maps.newConcurrentMap();
-    private ConcurrentMap<String, String> reverseMappings = Maps.newConcurrentMap();
-
     public BindDnsServerImpl() {
         super();
     }
@@ -86,8 +89,13 @@ public class BindDnsServerImpl extends SoftwareProcessImpl implements BindDnsSer
         return getConfig(DOMAIN_NAME);
     }
 
+    /**
+     * @return A serial number guaranteed to be valid for use in a modified domain.zone or reverse.zone file.
+     */
     public long getSerial() {
-        return serial.incrementAndGet();
+        long next = getAttribute(SERIAL) + 1;
+        setAttribute(SERIAL, next);
+        return next;
     }
 
     public Cidr getReverseLookupNetwork() {
@@ -98,26 +106,42 @@ public class BindDnsServerImpl extends SoftwareProcessImpl implements BindDnsSer
         return getAttribute(REVERSE_LOOKUP_DOMAIN);
     }
 
+    public DynamicGroup getEntities() {
+        return getAttribute(ENTITIES);
+    }
+
     @Override
     public void init() {
         super.init();
-        entities = addChild(EntitySpec.create(DynamicGroup.class)
+        checkNotNull(getConfig(HOSTNAME_SENSOR), "{} requires value for {}", getClass().getName(), HOSTNAME_SENSOR);
+        DynamicGroup entities = addChild(EntitySpec.create(DynamicGroup.class)
                 .configure("entityFilter", getConfig(ENTITY_FILTER)));
+        setAttribute(ENTITIES, entities);
+        setAttribute(A_RECORDS, ImmutableMap.<String, String>of());
+        setAttribute(CNAME_RECORDS, ImmutableMultimap.<String, String>of());
+        setAttribute(PTR_RECORDS, ImmutableMap.<String, String>of());
+        setAttribute(ADDRESS_MAPPINGS, ImmutableMultimap.<String, String>of());
+        setAttribute(SERIAL, System.currentTimeMillis());
     }
 
     @Override
-    public Class<BindDnsServerDriver> getDriverInterface() {
+    public void postRebind() {
+        update();
+    }
+
+    @Override
+    public Class<?> getDriverInterface() {
         return BindDnsServerDriver.class;
     }
 
     @Override
-    public Map<String, String> getAddressMappings() {
-        return addressMappings;
+    public Multimap<String, String> getAddressMappings() {
+        return getAttribute(ADDRESS_MAPPINGS);
     }
 
     @Override
     public Map<String, String> getReverseMappings() {
-        return reverseMappings;
+        return getAttribute(PTR_RECORDS);
     }
 
     @Override
@@ -140,127 +164,136 @@ public class BindDnsServerImpl extends SoftwareProcessImpl implements BindDnsSer
         String reverse = getConfig(REVERSE_LOOKUP_NETWORK);
         if (Strings.isBlank(reverse)) reverse = getAttribute(ADDRESS);
         setAttribute(REVERSE_LOOKUP_CIDR, new Cidr(reverse + "/24"));
-        String reverseLookupDomain = Joiner.on('.').join(Iterables.skip(Lists.reverse(Lists.newArrayList(Splitter.on('.').split(reverse))), 1)) + ".in-addr.arpa";
+        String reverseLookupDomain = Iterables.toString(Iterables.skip(Lists.reverse(Lists.newArrayList(Splitter.on('.').split(reverse))), 1)) + ".in-addr.arpa";
         setAttribute(REVERSE_LOOKUP_DOMAIN, reverseLookupDomain);
 
-        Map<?, ?> flags = MutableMap.builder()
-                .build();
-        policy = addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
+        addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
                 .displayName("Address tracker")
                 .configure("sensorsToTrack", ImmutableSet.of(getConfig(HOSTNAME_SENSOR)))
-                .configure("group", entities));
+                .configure("group", getEntities()));
+    }
 
-        // For any entities that have already come up
-        for (Entity member : entities.getMembers()) {
-            if (Strings.isNonBlank(member.getAttribute(getConfig(HOSTNAME_SENSOR)))) added(member); // Ignore, unless hostname set
-        }
+    @Override
+    public void postStart() {
+        update();
     }
 
     public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy {
         @Override
         protected void onEntityChange(Entity member) {
-            // TODO Should we guard to only call if service_up and if hostname set?
-            ((BindDnsServerImpl)entity).added(member);
+            if (LOG.isTraceEnabled()) {
+                LOG.trace("State of {} on change: {}", member, member.getAttribute(Attributes.SERVICE_STATE_ACTUAL).name());
+            }
+            ((BindDnsServerImpl) entity).update();
         }
         @Override
         protected void onEntityAdded(Entity member) {
-            if (Strings.isNonBlank(member.getAttribute(getConfig(HOSTNAME_SENSOR)))) {
-                ((BindDnsServerImpl)entity).added(member); // Ignore, unless hostname set
+            if (LOG.isTraceEnabled()) {
+                LOG.trace("State of {} on added: {}", member, member.getAttribute(Attributes.SERVICE_STATE_ACTUAL).name());
             }
-        }
-        @Override
-        protected void onEntityRemoved(Entity member) {
-            ((BindDnsServerImpl)entity).removed(member);
+            ((BindDnsServerImpl) entity).configureResolver(member);
         }
     }
 
-    @Override
-    public void postStart() {
-        update();
+    private class HasHostnameAndValidLifecycle implements Predicate<Entity> {
+        @Override
+        public boolean apply(Entity input) {
+            switch (input.getAttribute(Attributes.SERVICE_STATE_ACTUAL)) {
+            case STOPPED:
+            case STOPPING:
+            case DESTROYED:
+                return false;
+            }
+            return input.getAttribute(getConfig(HOSTNAME_SENSOR)) != null;
+        }
     }
 
-    public void added(Entity member) {
-        synchronized (mutex) {
-            Optional<Location> location = Iterables.tryFind(member.getLocations(), Predicates.instanceOf(SshMachineLocation.class));
-            String hostname = member.getAttribute(getConfig(HOSTNAME_SENSOR));
-            if (location.isPresent() && Strings.isNonBlank(hostname)) {
-                SshMachineLocation machine = (SshMachineLocation) location.get();
-                String address = machine.getAddress().getHostAddress();
-                if (!entityLocations.containsKey(machine)) {
-                    entityLocations.put(machine, member);
-                    addressMappings.putIfAbsent(address, hostname);
+    public void update() {
+        Lifecycle serverState = getAttribute(Attributes.SERVICE_STATE_ACTUAL);
+        if (Lifecycle.STOPPED.equals(serverState) || Lifecycle.STOPPING.equals(serverState)
+                || Lifecycle.DESTROYED.equals(serverState) || !getAttribute(Attributes.SERVICE_UP)) {
+            LOG.debug("Skipped update of {} when service state is {} and running is {}",
+                    new Object[]{this, getAttribute(Attributes.SERVICE_STATE_ACTUAL), getAttribute(SERVICE_UP)});
+            return;
+        }
+        synchronized (this) {
+            Iterable<Entity> availableEntities = FluentIterable.from(getEntities().getMembers())
+                    .filter(new HasHostnameAndValidLifecycle());
+            LOG.debug("{} updating with entities: {}", this, Iterables.toString(availableEntities));
+            ImmutableListMultimap<String, Entity> hostnameToEntity = Multimaps.index(availableEntities,
+                    EntityFunctions.attribute(getConfig(HOSTNAME_SENSOR)));
+            Map<String, String> octetToName = Maps.newHashMap();
+            BiMap<String, String> ipToARecord = HashBiMap.create();
+            Multimap<String, String> aRecordToCnames = MultimapBuilder.hashKeys().arrayListValues().build();
+            Multimap<String, String> ipToAllNames = MultimapBuilder.hashKeys().arrayListValues().build();
+
+            for (Map.Entry<String, Entity> e : hostnameToEntity.entries()) {
+                String domainName = e.getKey();
+                Maybe<SshMachineLocation> location = Machines.findUniqueSshMachineLocation(e.getValue().getLocations());
+                if (!location.isPresent()) {
+                    LOG.debug("Member {} of {} does not have an SSH location so will not be configured", e.getValue(), this);
+                    continue;
+                }
+                String address = location.get().getAddress().getHostAddress();
+                ipToAllNames.put(address, domainName);
+                if (!ipToARecord.containsKey(address)) {
+                    ipToARecord.put(address, domainName);
                     if (getReverseLookupNetwork().contains(new Cidr(address + "/32"))) {
                         String octet = Iterables.get(Splitter.on('.').split(address), 3);
-                        reverseMappings.putIfAbsent(hostname, octet);
-                    }
-                    if (getAttribute(Startable.SERVICE_UP)) {
-                        update();
+                        if (!octetToName.containsKey(octet)) octetToName.put(octet, domainName);
                     }
-                    configure(machine);
-                    LOG.info("{} added at location {} with name {}", new Object[] { member, machine, hostname });
+                } else {
+                    aRecordToCnames.put(ipToARecord.get(address), domainName);
                 }
             }
-        }
+            setAttribute(A_RECORDS, ImmutableMap.copyOf(ipToARecord.inverse()));
+            setAttribute(PTR_RECORDS, ImmutableMap.copyOf(octetToName));
+            setAttribute(CNAME_RECORDS, Multimaps.unmodifiableMultimap(aRecordToCnames));
+            setAttribute(ADDRESS_MAPPINGS, Multimaps.unmodifiableMultimap(ipToAllNames));
+
+            // Update Bind configuration files and restart the service
+            getDriver().updateBindConfiguration();
+       }
     }
 
-    public void removed(Entity member) {
-        synchronized (mutex) {
-            Location location = findLocation(member);
-            if (location != null) {
-                entityLocations.remove(location, member);
-                if (!entityLocations.containsKey(location)) {
-                    addressMappings.remove(((MachineLocation)location).getAddress().getHostAddress());
-                }
+    protected void configureResolver(Entity entity) {
+        Maybe<SshMachineLocation> machine = Machines.findUniqueSshMachineLocation(entity.getLocations());
+        if (machine.isPresent()) {
+            if (getConfig(REPLACE_RESOLV_CONF)) {
+                machine.get().copyTo(new StringReader(getConfig(RESOLV_CONF_TEMPLATE)), "/etc/resolv.conf");
+            } else {
+                appendTemplate(getConfig(INTERFACE_CONFIG_TEMPLATE), "/etc/sysconfig/network-scripts/ifcfg-eth0", machine.get());
+                machine.get().execScript("reload network", ImmutableList.of(BashCommands.sudo("service network reload")));
             }
-            update();
+            LOG.info("configured resolver on {}", machine);
+        } else {
+            LOG.debug("{} can't configure resolver at {}: no SshMachineLocation", this, entity);
         }
     }
 
-    private Location findLocation(Entity member) {
-        // don't use member.getLocations(), because when being stopped the location might not be set
-        if (entityLocations.containsValue(member)) {
-            for (Map.Entry<Location, Entity> entry : entityLocations.entries()) {
-                if (member.equals(entry.getValue())) {
-                    return entry.getKey();
-                }
-            }
-        }
-        return null;
+    protected void appendTemplate(String template, String destination, SshMachineLocation machine) {
+        String content = ((BindDnsServerSshDriver) getDriver()).processTemplate(template);
+        String temp = "/tmp/template-" + Strings.makeRandomId(6);
+        machine.copyTo(new ByteArrayInputStream(content.getBytes()), temp);
+        machine.execScript("updating file", ImmutableList.of(
+                BashCommands.sudo(String.format("tee -a %s < %s", destination, temp)),
+                String.format("rm -f %s", temp)));
     }
 
-    public void update() {
-        Optional<Location> location = Iterables.tryFind(getLocations(), Predicates.instanceOf(SshMachineLocation.class));
-        SshMachineLocation machine = (SshMachineLocation) location.get();
-        copyTemplate(getConfig(NAMED_CONF_TEMPLATE), "/etc/named.conf", machine);
-        copyTemplate(getConfig(DOMAIN_ZONE_FILE_TEMPLATE), "/var/named/domain.zone", machine);
-        copyTemplate(getConfig(REVERSE_ZONE_FILE_TEMPLATE), "/var/named/reverse.zone", machine);
-        machine.execScript("restart bind", ImmutableList.of(BashCommands.sudo("service named restart")));
-        LOG.info("updated named configuration and zone file for '{}' on {}", getDomainName(), this);
+    public Map<String, String> getAddressRecords() {
+        return getAttribute(A_RECORDS);
     }
 
-    public void configure(SshMachineLocation machine) {
-        if (getConfig(REPLACE_RESOLV_CONF)) {
-            copyTemplate(getConfig(RESOLV_CONF_TEMPLATE), "/etc/resolv.conf", machine);
-        } else {
-            appendTemplate(getConfig(INTERFACE_CONFIG_TEMPLATE), "/etc/sysconfig/network-scripts/ifcfg-eth0", machine);
-            machine.execScript("reload network", ImmutableList.of(BashCommands.sudo("service network reload")));
-        }
-        LOG.info("configured resolver on {}", machine);
+    public Multimap<String, String> getCanonicalNameRecords() {
+        return getAttribute(CNAME_RECORDS);
     }
 
-    public void copyTemplate(String template, String destination, SshMachineLocation machine) {
-        String content = ((BindDnsServerSshDriver) getDriver()).processTemplate(template);
-        String temp = "/tmp/template-" + Strings.makeRandomId(6);
-        machine.copyTo(new ByteArrayInputStream(content.getBytes()), temp);
-        machine.execScript("copying file", ImmutableList.of(BashCommands.sudo(String.format("mv %s %s", temp, destination))));
+    public Map<String, Collection<String>> getCnamesForTemplates() {
+        return getAttribute(CNAME_RECORDS).asMap();
     }
 
-    public void appendTemplate(String template, String destination, SshMachineLocation machine) {
-        String content = ((BindDnsServerSshDriver) getDriver()).processTemplate(template);
-        String temp = "/tmp/template-" + Strings.makeRandomId(6);
-        machine.copyTo(new ByteArrayInputStream(content.getBytes()), temp);
-        machine.execScript("updating file", ImmutableList.of(
-                BashCommands.sudo(String.format("tee -a %s < %s", destination, temp)),
-                String.format("rm -f %s", temp)));
+    public Map<String, String> getPointerRecords() {
+        return getAttribute(PTR_RECORDS);
     }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerSshDriver.java
----------------------------------------------------------------------
diff --git a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerSshDriver.java b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerSshDriver.java
index 757e8ca..08198f8 100644
--- a/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerSshDriver.java
+++ b/software/network/src/main/java/brooklyn/entity/network/bind/BindDnsServerSshDriver.java
@@ -18,9 +18,13 @@
  */
 package brooklyn.entity.network.bind;
 
+import java.io.ByteArrayInputStream;
 import java.util.List;
 import java.util.Map;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
 import brooklyn.entity.basic.lifecycle.ScriptHelper;
 import brooklyn.location.basic.SshMachineLocation;
@@ -31,11 +35,13 @@ import brooklyn.util.ssh.BashCommands;
 import brooklyn.util.ssh.IptablesCommands;
 import brooklyn.util.ssh.IptablesCommands.Chain;
 import brooklyn.util.ssh.IptablesCommands.Policy;
+import brooklyn.util.text.Strings;
 
 import com.google.common.collect.ImmutableList;
 
 public class BindDnsServerSshDriver extends AbstractSoftwareProcessSshDriver implements BindDnsServerDriver {
 
+    private static final Logger LOG = LoggerFactory.getLogger(BindDnsServerSshDriver.class);
     private String serviceName = "named";
 
     public BindDnsServerSshDriver(BindDnsServerImpl entity, SshMachineLocation machine) {
@@ -103,4 +109,21 @@ public class BindDnsServerSshDriver extends AbstractSoftwareProcessSshDriver imp
                 .execute();
     }
 
+    @Override
+    public void updateBindConfiguration() {
+        copyAsRoot(entity.getConfig(BindDnsServer.NAMED_CONF_TEMPLATE), "/etc/named.conf");
+        copyAsRoot(entity.getConfig(BindDnsServer.DOMAIN_ZONE_FILE_TEMPLATE), "/var/named/domain.zone");
+        copyAsRoot(entity.getConfig(BindDnsServer.REVERSE_ZONE_FILE_TEMPLATE), "/var/named/reverse.zone");
+        int result = getMachine().execScript("restart bind", ImmutableList.of(BashCommands.sudo("service "+serviceName+" restart")));
+        LOG.info("updated named configuration and zone file for '{}' on {} (exit code {}).",
+                new Object[]{entity.getConfig(BindDnsServer.DOMAIN_NAME), entity, result});
+    }
+
+    private void copyAsRoot(String template, String destination) {
+        String content = processTemplate(template);
+        String temp = "/tmp/template-" + Strings.makeRandomId(6);
+        getMachine().copyTo(new ByteArrayInputStream(content.getBytes()), temp);
+        getMachine().execScript("copying file", ImmutableList.of(BashCommands.sudo(String.format("mv %s %s", temp, destination))));
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/main/resources/brooklyn/entity/network/bind/domain.zone
----------------------------------------------------------------------
diff --git a/software/network/src/main/resources/brooklyn/entity/network/bind/domain.zone b/software/network/src/main/resources/brooklyn/entity/network/bind/domain.zone
index d380298..671d234 100644
--- a/software/network/src/main/resources/brooklyn/entity/network/bind/domain.zone
+++ b/software/network/src/main/resources/brooklyn/entity/network/bind/domain.zone
@@ -32,6 +32,15 @@
 @ IN NS ns2.${entity.domainName}.
 ns1 IN A ${driver.address}
 ns2 IN A ${driver.address}
-[#list entity.addressMappings?keys as address]
-${entity.addressMappings[address]} IN A ${address}
+
+;; Addresses
+[#list entity.addressRecords?keys as address]
+${address} IN A ${entity.addressRecords[address]}
+[/#list]
+
+;; Canonical names
+[#list entity.cnamesForTemplates?keys as aRecord]
+[#list entity.cnamesForTemplates[aRecord] as cname]
+${cname} IN CNAME ${aRecord}
+[/#list]
 [/#list]

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/main/resources/brooklyn/entity/network/bind/reverse.zone
----------------------------------------------------------------------
diff --git a/software/network/src/main/resources/brooklyn/entity/network/bind/reverse.zone b/software/network/src/main/resources/brooklyn/entity/network/bind/reverse.zone
index fa21e7e..6966e1e 100644
--- a/software/network/src/main/resources/brooklyn/entity/network/bind/reverse.zone
+++ b/software/network/src/main/resources/brooklyn/entity/network/bind/reverse.zone
@@ -31,6 +31,7 @@ ${entity.reverseLookupDomain}. IN NS ns1.${entity.domainName}.
 ${entity.reverseLookupDomain}. IN NS ns2.${entity.domainName}.
 ns1 IN A ${driver.address}
 ns2 IN A ${driver.address}
-[#list entity.reverseMappings?keys as hostname]
-${entity.reverseMappings[hostname]} IN PTR ${hostname}.${entity.domainName}.
+
+[#list entity.pointerRecords?keys as address]
+${address} IN PTR ${entity.pointerRecords[address]}.${entity.domainName}.
 [/#list]

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerIntegrationTest.java b/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerIntegrationTest.java
new file mode 100644
index 0000000..2ba4e43
--- /dev/null
+++ b/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerIntegrationTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.entity.network.bind;
+
+import static org.testng.Assert.assertEquals;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.EmptySoftwareProcess;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.StartableApplication;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.rebind.RebindTestFixture;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.policy.EnricherSpec;
+import brooklyn.test.EntityTestUtils;
+import brooklyn.test.entity.TestApplication;
+
+public class BindDnsServerIntegrationTest extends RebindTestFixture {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BindDnsServerIntegrationTest.class);
+    private BindDnsServer dns;
+    private DynamicCluster cluster;
+
+    @Override
+    protected StartableApplication createApp() {
+        TestApplication app = ApplicationBuilder.newManagedApp(TestApplication.class, origManagementContext);
+        dns = app.createAndManageChild(EntitySpec.create(BindDnsServer.class, TestBindDnsServerImpl.class)
+                .configure(BindDnsServer.ENTITY_FILTER, Predicates.instanceOf(EmptySoftwareProcess.class))
+                .configure(BindDnsServer.HOSTNAME_SENSOR, PrefixAndIdEnricher.SENSOR));
+        EntitySpec<EmptySoftwareProcess> memberSpec = EntitySpec.create(EmptySoftwareProcess.class)
+                .enricher(EnricherSpec.create(PrefixAndIdEnricher.class)
+                        .configure(PrefixAndIdEnricher.PREFIX, "dns-integration-test-")
+                        .configure(PrefixAndIdEnricher.MONITOR, Attributes.HOSTNAME));
+        cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+                .configure(DynamicCluster.MEMBER_SPEC, memberSpec)
+                .configure(DynamicCluster.INITIAL_SIZE, 3));
+        return app;
+    }
+
+    @Test(groups = "Integration")
+    public void testRebindDns() throws Exception {
+        LocationSpec.create(LocalhostMachineProvisioningLocation.class);
+        origApp.start(ImmutableList.of(new LocalhostMachineProvisioningLocation()));
+        logDnsMappings();
+        assertEquals(dns.getAttribute(BindDnsServer.ADDRESS_MAPPINGS).keySet().size(), 1);
+        assertMapSizes(3, 1, 2, 1);
+
+        rebind();
+        dns = (BindDnsServer) Iterables.getOnlyElement(Iterables.filter(newApp.getChildren(), Predicates.instanceOf(BindDnsServer.class)));
+        cluster = (DynamicCluster) Iterables.getOnlyElement(Iterables.filter(newApp.getChildren(), Predicates.instanceOf(DynamicCluster.class)));
+
+        // assert original attributes restored and the server can be updated.
+        logDnsMappings();
+        assertMapSizes(3, 1, 2, 1);
+        cluster.resize(1);
+        assertDnsEntityEventuallyHasActiveMembers(1);
+        logDnsMappings();
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, DynamicCluster.GROUP_SIZE, 1);
+        assertMapSizes(1, 1, 0, 1);
+        cluster.resize(5);
+        assertDnsEntityEventuallyHasActiveMembers(5);
+        logDnsMappings();
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, DynamicCluster.GROUP_SIZE, 5);
+        assertMapSizes(5, 1, 4, 1);
+    }
+
+    @Test(groups = "Integration")
+    public void testMapsSeveralEntitiesOnOneMachine() {
+        origApp.start(ImmutableList.of(new LocalhostMachineProvisioningLocation()));
+        EntityTestUtils.assertAttributeEqualsEventually(dns, Attributes.SERVICE_UP, true);
+        logDnsMappings();
+
+        // One host with one A, two CNAME and one PTR record
+        assertEquals(dns.getAttribute(BindDnsServer.ADDRESS_MAPPINGS).keySet().size(), 1);
+        assertMapSizes(3, 1, 2, 1);
+        String key = Iterables.getOnlyElement(dns.getAttribute(BindDnsServer.ADDRESS_MAPPINGS).keySet());
+        assertEquals(dns.getAttribute(BindDnsServer.ADDRESS_MAPPINGS).get(key).size(), 3);
+
+        Entities.dumpInfo(dns);
+    }
+
+    private void assertMapSizes(int addresses, int aRecords, int cnameRecords, int ptrRecords) {
+        assertEquals(dns.getAttribute(BindDnsServer.ADDRESS_MAPPINGS).entries().size(), addresses);
+        assertEquals(dns.getAttribute(BindDnsServer.A_RECORDS).size(), aRecords);
+        assertEquals(dns.getAttribute(BindDnsServer.CNAME_RECORDS).size(), cnameRecords);
+        assertEquals(dns.getAttribute(BindDnsServer.PTR_RECORDS).size(), ptrRecords);
+    }
+
+    private void logDnsMappings() {
+        LOG.info("A:     " + Joiner.on(", ").withKeyValueSeparator("=").join(
+                dns.getAttribute(BindDnsServer.A_RECORDS)));
+        LOG.info("CNAME: " + Joiner.on(", ").withKeyValueSeparator("=").join(
+                dns.getAttribute(BindDnsServer.CNAME_RECORDS).asMap()));
+        LOG.info("PTR:   " + Joiner.on(", ").withKeyValueSeparator("=").join(
+                dns.getAttribute(BindDnsServer.PTR_RECORDS)));
+    }
+
+    private void assertDnsEntityEventuallyHasActiveMembers(final int size) {
+        EntityTestUtils.assertPredicateEventuallyTrue(dns, new Predicate<BindDnsServer>() {
+            @Override
+            public boolean apply(BindDnsServer input) {
+                return input.getAddressMappings().size() == size;
+            }
+        });
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerLiveTest.java
----------------------------------------------------------------------
diff --git a/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerLiveTest.java b/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerLiveTest.java
index d74c118..4e9dc13 100644
--- a/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerLiveTest.java
+++ b/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerLiveTest.java
@@ -18,6 +18,9 @@
  */
 package brooklyn.entity.network.bind;
 
+import static brooklyn.test.EntityTestUtils.assertAttributeEqualsEventually;
+import static org.testng.Assert.assertEquals;
+
 import java.util.Map;
 
 import org.slf4j.Logger;
@@ -27,16 +30,25 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 
+import brooklyn.entity.Entity;
 import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.EmptySoftwareProcess;
 import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.SameServerEntity;
+import brooklyn.entity.group.DynamicCluster;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.location.Location;
 import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.policy.EnricherSpec;
 import brooklyn.test.EntityTestUtils;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.util.collections.MutableMap;
+import brooklyn.util.time.Duration;
 
 public class BindDnsServerLiveTest {
 
@@ -44,6 +56,7 @@ public class BindDnsServerLiveTest {
 
     protected TestApplication app;
     protected Location testLocation;
+    protected DynamicCluster cluster;
     protected BindDnsServer dns;
 
     @BeforeMethod(alwaysRun = true)
@@ -74,11 +87,66 @@ public class BindDnsServerLiveTest {
         Map<String, String> properties = MutableMap.of("imageId", imageId);
         testLocation = app.getManagementContext().getLocationRegistry().resolve(provider, properties);
 
-        BindDnsServer dns = app.createAndManageChild(EntitySpec.create(BindDnsServer.class));
+        BindDnsServer dns = app.createAndManageChild(EntitySpec.create(BindDnsServer.class)
+                .enricher(EnricherSpec.create(PrefixAndIdEnricher.class)
+                        .configure(PrefixAndIdEnricher.PREFIX, "dns-live-test-")
+                        .configure(PrefixAndIdEnricher.MONITOR, Attributes.HOSTNAME)));
         dns.start(ImmutableList.of(testLocation));
 
-        EntityTestUtils.assertAttributeEqualsEventually(dns, BindDnsServer.SERVICE_UP, true);
+        assertAttributeEqualsEventually(dns, BindDnsServer.SERVICE_UP, true);
+
+        assertEquals(dns.getAttribute(BindDnsServer.ADDRESS_MAPPINGS).entries().size(), 1);
+        assertEquals(dns.getAttribute(BindDnsServer.A_RECORDS).size(), 1);
+        assertEquals(dns.getAttribute(BindDnsServer.CNAME_RECORDS).size(), 0);
+        assertEquals(dns.getAttribute(BindDnsServer.PTR_RECORDS).size(), 1);
+
         Entities.dumpInfo(app);
     }
 
+    @Test(groups = "Live", dataProvider = "virtualMachineData")
+    public void testUpdateWhenNewEntities(String imageId, String provider) {
+        Map<String, String> properties = MutableMap.of("imageId", imageId);
+        testLocation = app.getManagementContext().getLocationRegistry().resolve(provider, properties);
+
+        EntitySpec<EmptySoftwareProcess> memberSpec = EntitySpec.create(EmptySoftwareProcess.class)
+                .enricher(EnricherSpec.create(PrefixAndIdEnricher.class)
+                        .configure(PrefixAndIdEnricher.PREFIX, "dns-live-test-")
+                        .configure(PrefixAndIdEnricher.MONITOR, Attributes.HOSTNAME));
+        cluster = app.createAndManageChild(EntitySpec.create(DynamicCluster.class)
+                .configure(DynamicCluster.MEMBER_SPEC, memberSpec)
+                .configure(DynamicCluster.INITIAL_SIZE, 1));
+        dns = app.createAndManageChild(EntitySpec.create(BindDnsServer.class)
+                .configure(BindDnsServer.ENTITY_FILTER, Predicates.instanceOf(EmptySoftwareProcess.class))
+                .configure(BindDnsServer.HOSTNAME_SENSOR, PrefixAndIdEnricher.SENSOR));
+
+        app.start(ImmutableList.of(testLocation));
+        assertAttributeEqualsEventually(dns, Attributes.SERVICE_UP, true);
+
+        logDnsMappings();
+        assertEquals(dns.getAttribute(BindDnsServer.ADDRESS_MAPPINGS).entries().size(), 1);
+        assertEquals(dns.getAttribute(BindDnsServer.A_RECORDS).size(), 1);
+        assertEquals(dns.getAttribute(BindDnsServer.CNAME_RECORDS).size(), 0);
+        // Harder to make assertions on PTR because the entity servers might not be in the right CIDR
+
+        cluster.resize(2);
+        logDnsMappings();
+        assertEquals(dns.getAttribute(BindDnsServer.ADDRESS_MAPPINGS).entries().size(), 2);
+        assertEquals(dns.getAttribute(BindDnsServer.A_RECORDS).size(), 2);
+        assertEquals(dns.getAttribute(BindDnsServer.CNAME_RECORDS).size(), 0);
+
+        cluster.resize(1);
+        logDnsMappings();
+        assertEquals(dns.getAttribute(BindDnsServer.ADDRESS_MAPPINGS).entries().size(), 1);
+        assertEquals(dns.getAttribute(BindDnsServer.A_RECORDS).size(), 1);
+        assertEquals(dns.getAttribute(BindDnsServer.CNAME_RECORDS).size(), 0);
+    }
+
+    private void logDnsMappings() {
+        LOG.info("A:     " + Joiner.on(", ").withKeyValueSeparator("=").join(
+                dns.getAttribute(BindDnsServer.A_RECORDS)));
+        LOG.info("CNAME: " + Joiner.on(", ").withKeyValueSeparator("=").join(
+                dns.getAttribute(BindDnsServer.CNAME_RECORDS).asMap()));
+        LOG.info("PTR:   " + Joiner.on(", ").withKeyValueSeparator("=").join(
+                dns.getAttribute(BindDnsServer.PTR_RECORDS)));
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerTest.java
----------------------------------------------------------------------
diff --git a/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerTest.java b/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerTest.java
deleted file mode 100644
index 250075e..0000000
--- a/software/network/src/test/java/brooklyn/entity/network/bind/BindDnsServerTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.entity.network.bind;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableList;
-
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.location.Location;
-import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
-import brooklyn.test.EntityTestUtils;
-import brooklyn.test.entity.TestApplication;
-
-public class BindDnsServerTest {
-
-    @SuppressWarnings("unused")
-    private static final Logger LOG = LoggerFactory.getLogger(BindDnsServerTest.class);
-
-    protected TestApplication app;
-    protected Location testLocation;
-    protected BindDnsServer dns;
-
-    @BeforeMethod(alwaysRun = true)
-    public void setup() throws Exception {
-        app = ApplicationBuilder.newManagedApp(TestApplication.class);
-        testLocation = new LocalhostMachineProvisioningLocation();
-    }
-
-    @AfterMethod(alwaysRun = true)
-    public void shutdown() {
-        Entities.destroyAll(app.getManagementContext());
-    }
-
-    // TODO this needs to be run on a slave VM where we can edit the resolver configuration
-    @Test(groups = { "WIP", "Integration" })
-    protected void testDnsEntity() throws Exception {
-        BindDnsServer dns = app.createAndManageChild(EntitySpec.create(BindDnsServer.class));
-
-        app.start(ImmutableList.<Location>of(testLocation));
-
-        EntityTestUtils.assertAttributeEqualsEventually(dns, BindDnsServer.SERVICE_UP, true);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/test/java/brooklyn/entity/network/bind/DoNothingSoftwareProcessDriver.java
----------------------------------------------------------------------
diff --git a/software/network/src/test/java/brooklyn/entity/network/bind/DoNothingSoftwareProcessDriver.java b/software/network/src/test/java/brooklyn/entity/network/bind/DoNothingSoftwareProcessDriver.java
new file mode 100644
index 0000000..c49e66b
--- /dev/null
+++ b/software/network/src/test/java/brooklyn/entity/network/bind/DoNothingSoftwareProcessDriver.java
@@ -0,0 +1,56 @@
+/*
+ * 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.entity.network.bind;
+
+import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.location.basic.SshMachineLocation;
+
+/**
+ * Implements methods in {@link brooklyn.entity.basic.AbstractSoftwareProcessSshDriver}.
+ * {@link #isRunning()} returns true.
+ */
+public class DoNothingSoftwareProcessDriver extends AbstractSoftwareProcessSshDriver {
+
+    public DoNothingSoftwareProcessDriver(EntityLocal entity, SshMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    @Override
+    public boolean isRunning() {
+        return true;
+    }
+
+    @Override
+    public void stop() {
+    }
+
+    @Override
+    public void install() {
+    }
+
+    @Override
+    public void customize() {
+    }
+
+    @Override
+    public void launch() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/test/java/brooklyn/entity/network/bind/PrefixAndIdEnricher.java
----------------------------------------------------------------------
diff --git a/software/network/src/test/java/brooklyn/entity/network/bind/PrefixAndIdEnricher.java b/software/network/src/test/java/brooklyn/entity/network/bind/PrefixAndIdEnricher.java
new file mode 100644
index 0000000..2f2b4b0
--- /dev/null
+++ b/software/network/src/test/java/brooklyn/entity/network/bind/PrefixAndIdEnricher.java
@@ -0,0 +1,57 @@
+/*
+ * 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.entity.network.bind;
+
+import com.google.common.reflect.TypeToken;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.enricher.basic.AbstractEnricher;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.SensorEvent;
+import brooklyn.event.SensorEventListener;
+import brooklyn.event.basic.Sensors;
+
+public class PrefixAndIdEnricher extends AbstractEnricher {
+
+    public static final AttributeSensor<String> SENSOR = Sensors.newStringSensor(
+            "prefixandid.sensor");
+
+    public static final ConfigKey<String> PREFIX = ConfigKeys.newStringConfigKey(
+            "prefixandid.prefix", "Sets SENSOR to prefix+entity id");
+
+    public static final ConfigKey<AttributeSensor<?>> MONITOR = ConfigKeys.newConfigKey(new TypeToken<AttributeSensor<?>>() {},
+            "prefixandid.attributetomonitor", "Changes on this sensor are monitored and the prefix/id republished");
+
+    public PrefixAndIdEnricher() {
+    }
+
+    @Override
+    public void setEntity(final EntityLocal entity) {
+        super.setEntity(entity);
+        subscribe(entity, getConfig(MONITOR), new SensorEventListener<Object>() {
+            @Override
+            public void onEvent(SensorEvent<Object> event) {
+                entity.setAttribute(SENSOR, getConfig(PREFIX) + entity.getId());
+            }
+        });
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/3644e198/software/network/src/test/java/brooklyn/entity/network/bind/TestBindDnsServerImpl.java
----------------------------------------------------------------------
diff --git a/software/network/src/test/java/brooklyn/entity/network/bind/TestBindDnsServerImpl.java b/software/network/src/test/java/brooklyn/entity/network/bind/TestBindDnsServerImpl.java
new file mode 100644
index 0000000..78f293f
--- /dev/null
+++ b/software/network/src/test/java/brooklyn/entity/network/bind/TestBindDnsServerImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.entity.network.bind;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.location.basic.SshMachineLocation;
+
+public class TestBindDnsServerImpl extends BindDnsServerImpl {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TestBindDnsServerImpl.class);
+
+    public static class TestBindDnsServerDriver extends DoNothingSoftwareProcessDriver implements BindDnsServerDriver {
+        public TestBindDnsServerDriver(EntityLocal entity, SshMachineLocation machine) {
+            super(entity, machine);
+        }
+
+        @Override
+        public void updateBindConfiguration() {
+            LOG.info("Skipped copy of Bind configuration files to server");
+            LOG.debug("Configuration:\n{}", processTemplate(entity.getConfig(BindDnsServer.NAMED_CONF_TEMPLATE)));
+            LOG.debug("domain.zone:\n{}", processTemplate(entity.getConfig(BindDnsServer.DOMAIN_ZONE_FILE_TEMPLATE)));
+            LOG.debug("reverse.zone:\n{}", processTemplate(entity.getConfig(BindDnsServer.REVERSE_ZONE_FILE_TEMPLATE)));
+        }
+    }
+
+    @Override
+    public Class<?> getDriverInterface() {
+        return TestBindDnsServerDriver.class;
+    }
+
+    @Override
+    protected void configureResolver(Entity entity) {
+        LOG.debug("Skipped configuration of resolver on {}", entity);
+    }
+
+    @Override
+    protected void appendTemplate(String template, String destination, SshMachineLocation machine) {
+        LOG.debug("Skipped append of template to {}@{}", destination, machine);
+    }
+}


Mime
View raw message