You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by gr...@apache.org on 2014/10/31 15:36:37 UTC
[1/4] git commit: Mark Cidr class Serializable
Repository: incubator-brooklyn
Updated Branches:
refs/heads/master 63f29bd70 -> 298ca6ce2
Mark Cidr class Serializable
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/70ca25c0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/70ca25c0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/70ca25c0
Branch: refs/heads/master
Commit: 70ca25c014146b19eee43dc4b943dbca813dcdad
Parents: 6324768
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Tue Oct 21 11:45:16 2014 +0100
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Mon Oct 27 21:35:31 2014 +0000
----------------------------------------------------------------------
utils/common/src/main/java/brooklyn/util/net/Cidr.java | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/70ca25c0/utils/common/src/main/java/brooklyn/util/net/Cidr.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/net/Cidr.java b/utils/common/src/main/java/brooklyn/util/net/Cidr.java
index aa57d8f..64768f4 100644
--- a/utils/common/src/main/java/brooklyn/util/net/Cidr.java
+++ b/utils/common/src/main/java/brooklyn/util/net/Cidr.java
@@ -19,6 +19,7 @@
package brooklyn.util.net;
+import java.io.Serializable;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
@@ -31,7 +32,9 @@ import brooklyn.util.text.Strings;
import com.google.common.collect.ImmutableList;
/** represents a CIDR (classless inter-domain routing) token, i.e. 10.0.0.0/8 or 192.168.4.0/24 */
-public class Cidr {
+public class Cidr implements Serializable {
+
+ private static final long serialVersionUID = -4605909101590811958L;
/** 0.0.0.0/0 -- matches all addresses */
public static final Cidr UNIVERSAL = new Cidr();
@@ -53,7 +56,7 @@ public class Cidr {
public static final List<Cidr> NON_PUBLIC_CIDRS = ImmutableList.<Cidr>builder().addAll(PRIVATE_NETWORKS_RFC_1918).add(LINK_LOCAL).add(LOOPBACK).build();
-
+
final int[] subnetBytes = new int[] { 0, 0, 0, 0 };
final int length;
[3/4] git commit: BindDnsServer improvements
Posted by gr...@apache.org.
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 <sa...@cloudsoftcorp.com>
Authored: Wed Oct 29 12:07:40 2014 +0000
Committer: Sam Corbett <sa...@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);
+ }
+}
[2/4] git commit: BROOKLYN-81: Refuse to reload security provider if
none set
Posted by gr...@apache.org.
BROOKLYN-81: Refuse to reload security provider if none set
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/6324768f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/6324768f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/6324768f
Branch: refs/heads/master
Commit: 6324768f834038ab89f54e7c1fad72babf181bc3
Parents: 5b37ae0
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Fri Oct 17 17:49:06 2014 +0100
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Mon Oct 27 21:35:31 2014 +0000
----------------------------------------------------------------------
.../java/brooklyn/rest/BrooklynWebConfig.java | 48 +++++++++++---------
.../provider/DelegatingSecurityProvider.java | 6 +++
.../brooklyn/rest/BrooklynRestApiLauncher.java | 4 +-
.../main/java/brooklyn/test/HttpTestUtils.java | 2 +-
4 files changed, 36 insertions(+), 24 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6324768f/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java b/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java
index 17867a9..67fd069 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/BrooklynWebConfig.java
@@ -22,7 +22,7 @@ import brooklyn.config.ConfigKey;
import brooklyn.config.ConfigMap;
import brooklyn.config.ConfigPredicates;
import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.rest.security.provider.DelegatingSecurityProvider;
import brooklyn.rest.security.provider.ExplicitUsersSecurityProvider;
public class BrooklynWebConfig {
@@ -30,51 +30,57 @@ public class BrooklynWebConfig {
public final static String BASE_NAME = "brooklyn.webconsole";
public final static String BASE_NAME_SECURITY = BASE_NAME+".security";
- /** e.g. brooklyn.webconsole.security.provider=brooklyn.rest.security.provider.AnyoneSecurityProvider will allow anyone to log in;
- * default is explicitly named users, using SECURITY_PROVIDER_EXPLICIT_USERS */
- public final static ConfigKey<String> SECURITY_PROVIDER_CLASSNAME = new BasicConfigKey<String>(String.class,
+ /**
+ * The security provider to be loaded by {@link DelegatingSecurityProvider}.
+ * e.g. <code>brooklyn.webconsole.security.provider=brooklyn.rest.security.provider.AnyoneSecurityProvider</code>
+ * will allow anyone to log in.
+ */
+ public final static ConfigKey<String> SECURITY_PROVIDER_CLASSNAME = ConfigKeys.newStringConfigKey(
BASE_NAME_SECURITY+".provider", "class name of a Brooklyn SecurityProvider",
ExplicitUsersSecurityProvider.class.getCanonicalName());
- /** explicitly set the users/passwords, e.g. in brooklyn.properties:
+ /**
+ * Explicitly set the users/passwords, e.g. in brooklyn.properties:
* brooklyn.webconsole.security.users=admin,bob
* brooklyn.webconsole.security.user.admin.password=password
* brooklyn.webconsole.security.user.bob.password=bobspass
*/
-
- public final static ConfigKey<String> USERS = new BasicConfigKey<String>(String.class,
- BASE_NAME_SECURITY+".users");
+ public final static ConfigKey<String> USERS = ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY+".users");
public final static ConfigKey<String> PASSWORD_FOR_USER(String user) {
- return new BasicConfigKey<String>(String.class, BASE_NAME_SECURITY+".user."+user+".password");
+ return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".password");
}
public final static ConfigKey<String> SALT_FOR_USER(String user) {
- return new BasicConfigKey<String>(String.class, BASE_NAME_SECURITY+".user."+user+".salt");
+ return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".salt");
}
public final static ConfigKey<String> SHA256_FOR_USER(String user) {
- return new BasicConfigKey<String>(String.class, BASE_NAME_SECURITY+".user."+user+".sha256");
+ return ConfigKeys.newStringConfigKey(BASE_NAME_SECURITY + ".user." + user + ".sha256");
}
- public final static ConfigKey<String> LDAP_URL = new BasicConfigKey<String>(String.class,
+ public final static ConfigKey<String> LDAP_URL = ConfigKeys.newStringConfigKey(
BASE_NAME_SECURITY+".ldap.url");
- public final static ConfigKey<String> LDAP_REALM = new BasicConfigKey<String>(String.class,
+ public final static ConfigKey<String> LDAP_REALM = ConfigKeys.newStringConfigKey(
BASE_NAME_SECURITY+".ldap.realm");
- public final static ConfigKey<Boolean> HTTPS_REQUIRED = ConfigKeys.newBooleanConfigKey(BASE_NAME+".security.https.required",
+ public final static ConfigKey<Boolean> HTTPS_REQUIRED = ConfigKeys.newBooleanConfigKey(
+ BASE_NAME+".security.https.required",
"Whether HTTPS is required", false);
- public final static ConfigKey<String> KEYSTORE_URL = ConfigKeys.newStringConfigKey(BASE_NAME+".security.keystore.url",
- "Keystore from which to take the certificate to present when running HTTPS; "
- + "note that normally the password is also required, and an alias for the certificate if the keystore has more than one");
+ public final static ConfigKey<String> KEYSTORE_URL = ConfigKeys.newStringConfigKey(
+ BASE_NAME+".security.keystore.url",
+ "Keystore from which to take the certificate to present when running HTTPS; "
+ + "note that normally the password is also required, and an alias for the certificate if the keystore has more than one");
- public final static ConfigKey<String> KEYSTORE_PASSWORD = ConfigKeys.newStringConfigKey(BASE_NAME+".security.keystore.password",
- "Password for the "+KEYSTORE_URL);
+ public final static ConfigKey<String> KEYSTORE_PASSWORD = ConfigKeys.newStringConfigKey(
+ BASE_NAME+".security.keystore.password",
+ "Password for the "+KEYSTORE_URL);
- public final static ConfigKey<String> KEYSTORE_CERTIFICATE_ALIAS = ConfigKeys.newStringConfigKey(BASE_NAME+".security.keystore.certificate.alias",
- "Alias in "+KEYSTORE_URL+" for the certificate to use; defaults to the first if not supplied");
+ public final static ConfigKey<String> KEYSTORE_CERTIFICATE_ALIAS = ConfigKeys.newStringConfigKey(
+ BASE_NAME+".security.keystore.certificate.alias",
+ "Alias in "+KEYSTORE_URL+" for the certificate to use; defaults to the first if not supplied");
public final static boolean hasNoSecurityOptions(ConfigMap config) {
return config.submap(ConfigPredicates.startingWith(BASE_NAME_SECURITY)).isEmpty();
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6324768f/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
index d14e582..7b49bdd 100644
--- a/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
+++ b/usage/rest-server/src/main/java/brooklyn/rest/security/provider/DelegatingSecurityProvider.java
@@ -66,6 +66,12 @@ public class DelegatingSecurityProvider implements SecurityProvider {
StringConfigMap brooklynProperties = mgmt.getConfig();
String className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME);
+
+ if (delegate != null && BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig())) {
+ log.debug("{} refusing to change from {}: No security provider set in reloaded properties.",
+ this, delegate);
+ return delegate;
+ }
log.info("REST using security provider " + className);
try {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6324768f/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java b/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java
index 7fb26b9..4d12d62 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/BrooklynRestApiLauncher.java
@@ -186,8 +186,8 @@ public class BrooklynRestApiLauncher {
}
if (securityProvider != null) {
- ((BrooklynProperties) mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME,
- securityProvider.getName());
+ ((BrooklynProperties) mgmt.getConfig()).put(
+ BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME, securityProvider.getName());
}
if (forceUseOfDefaultCatalogWithJavaClassPath) {
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6324768f/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java
----------------------------------------------------------------------
diff --git a/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java b/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java
index 6142f78..ceee367 100644
--- a/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java
+++ b/usage/test-support/src/main/java/brooklyn/test/HttpTestUtils.java
@@ -331,7 +331,7 @@ public class HttpTestUtils {
throw Throwables.propagate(e);
}
}
-
+
/**
* Schedules (with the given executor) a poller that repeatedly accesses the given url, to confirm it always gives
* back the expected status code.
[4/4] git commit: This closes #263
Posted by gr...@apache.org.
This closes #263
* github/pr/263:
BindDnsServer improvements
Mark Cidr class Serializable
BROOKLYN-81: Refuse to reload security provider if none set
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/298ca6ce
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/298ca6ce
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/298ca6ce
Branch: refs/heads/master
Commit: 298ca6ce29864eb88f1217995870295cb5a2d9e4
Parents: 63f29bd 3644e19
Author: Andrew Kennedy <gr...@apache.org>
Authored: Fri Oct 31 14:36:31 2014 +0000
Committer: Andrew Kennedy <gr...@apache.org>
Committed: Fri Oct 31 14:36:31 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 +++++
.../java/brooklyn/rest/BrooklynWebConfig.java | 48 ++--
.../provider/DelegatingSecurityProvider.java | 6 +
.../brooklyn/rest/BrooklynRestApiLauncher.java | 4 +-
.../main/java/brooklyn/test/HttpTestUtils.java | 2 +-
.../src/main/java/brooklyn/util/net/Cidr.java | 7 +-
17 files changed, 643 insertions(+), 228 deletions(-)
----------------------------------------------------------------------