You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/07 01:59:52 UTC

[1/6] incubator-brooklyn git commit: package rename to org.apache.brooklyn: sandbox

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master fc42b17ff -> 7154cc9ec


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeImpl.java b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeImpl.java
new file mode 100644
index 0000000..fadfe46
--- /dev/null
+++ b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeImpl.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.hazelcast;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.event.feed.http.HttpFeed;
+import brooklyn.event.feed.http.HttpPollConfig;
+import brooklyn.event.feed.http.HttpValueFunctions;
+import brooklyn.location.access.BrooklynAccessUtils;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Functions;
+import com.google.common.net.HostAndPort;
+
+public class HazelcastNodeImpl extends SoftwareProcessImpl implements HazelcastNode {
+    
+    private static final Logger LOG = LoggerFactory.getLogger(HazelcastNodeImpl.class);
+    
+    HttpFeed httpFeed;
+
+    @Override
+    public Class<HazelcastNodeDriver> getDriverInterface() {
+        return HazelcastNodeDriver.class;
+    }
+    
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Connecting sensors for node: {} ", getAttribute(Attributes.HOSTNAME));
+        }
+        
+        HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, getNodePort());
+
+        String nodeUri = String.format("http://%s:%d/hazelcast/rest/cluster", hp.getHostText(), hp.getPort());
+        setAttribute(Attributes.MAIN_URI, URI.create(nodeUri));
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Node {} is using {} as a main URI", this, nodeUri);
+        }
+        
+        httpFeed = HttpFeed.builder()
+                .entity(this)
+                .period(3000, TimeUnit.MILLISECONDS)
+                .baseUri(nodeUri)
+                .poll(new HttpPollConfig<Boolean>(SERVICE_UP)
+                        .onSuccess(HttpValueFunctions.responseCodeEquals(200))
+                        .onFailureOrException(Functions.constant(false)))
+                .build();
+    }
+    
+    @Override
+    protected void disconnectSensors() {
+        if (httpFeed != null) {
+            httpFeed.stop();
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Disconnecting sensors for node: {} ", getAttribute(Attributes.HOSTNAME));
+        }
+        
+        super.disconnectSensors();
+        disconnectServiceUpIsRunning();
+    }
+
+
+    @Override
+    public String getGroupName() {
+        return getConfig(HazelcastNode.GROUP_NAME);
+    }
+
+    @Override
+    public String getGroupPassword() {
+        return getConfig(HazelcastNode.GROUP_PASSWORD);
+    }
+
+    @Override
+    public String getNodeName() {
+        return getAttribute(HazelcastNode.NODE_NAME);
+    }
+
+    @Override
+    public Integer getNodePort() {
+        return getAttribute(HazelcastNode.NODE_PORT);
+    }
+
+    @Override
+    public String getHostname() { 
+        return getAttribute(HOSTNAME); 
+    }
+    
+    @Override
+    public String getHostAddress() { 
+        return getAttribute(ADDRESS); 
+    }
+    
+    @Override
+    public String getPrivateIpAddress() {
+        return getAttribute(SUBNET_ADDRESS);
+    }
+    
+    @Override
+    public String getListenAddress() {
+        String listenAddress = getPrivateIpAddress();
+        
+        if (Strings.isBlank(listenAddress)) {
+            listenAddress = getAttribute(ADDRESS);
+        }
+        
+        if (LOG.isInfoEnabled()) {
+            LOG.info("Node {} is listening on {}", this, listenAddress);
+        }
+
+         
+        return listenAddress;
+    }
+
+
+    @Override
+    public String getHeapMemorySize() {
+        return getConfig(HazelcastNode.NODE_HEAP_MEMORY_SIZE);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeSshDriver.java b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeSshDriver.java
new file mode 100644
index 0000000..1dea440
--- /dev/null
+++ b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeSshDriver.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.hazelcast;
+
+import static java.lang.String.format;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.os.Os;
+import brooklyn.util.ssh.BashCommands;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+public class HazelcastNodeSshDriver extends JavaSoftwareProcessSshDriver implements HazelcastNodeDriver {
+    
+    private static final Logger LOG = LoggerFactory.getLogger(HazelcastNodeSshDriver.class);
+
+    public HazelcastNodeSshDriver(EntityLocal entity, SshMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    @Override
+    public void preInstall() {
+        resolver = Entities.newDownloader(this);
+    }
+
+    @Override
+    public void install() {
+        List<String> urls = resolver.getTargets();
+        String saveAs = resolver.getFilename();
+        
+        List<String> commands = ImmutableList.<String>builder()
+            .add(BashCommands.installJavaLatestOrWarn())
+            .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs))
+            .build();
+        
+        newScript(INSTALLING).body.append(commands).execute();
+    }
+
+    @Override
+    public void customize() {
+        if (LOG.isInfoEnabled()) {
+            LOG.info("Customizing {}", entity.getAttribute(HazelcastNode.NODE_NAME));
+        }
+        
+        ImmutableList.Builder<String> commands = new ImmutableList.Builder<String>()
+                .add("mkdir -p lib conf log")
+                .add(String.format("cp %s/%s %s/lib/", getInstallDir(), resolver.getFilename(), getRunDir()));
+
+        newScript(CUSTOMIZING)
+                .body.append(commands.build())
+                .failOnNonZeroResultCode()
+                .execute();
+        
+        copyTemplate(entity.getConfig(HazelcastNode.CONFIG_TEMPLATE_URL), Os.mergePathsUnix(getRunDir(), "conf", getConfigFileName()));
+        
+    }
+
+    @Override
+    public void launch() {
+        
+        entity.setAttribute(HazelcastNode.PID_FILE, Os.mergePathsUnix(getRunDir(), PID_FILENAME));
+        
+        String maxHeapMemorySize = getHeapMemorySize();
+        
+        if (LOG.isInfoEnabled()) {
+            LOG.info("Launching {} with heap memory of {}", entity, maxHeapMemorySize);
+        }
+        
+        // Setting initial heap size (Xms) size to match max heap size (Xms) at first
+        String initialHeapMemorySize = maxHeapMemorySize;
+        
+        StringBuilder commandBuilder = new StringBuilder()
+            .append(format("nohup java -cp ./lib/%s", resolver.getFilename()))
+            .append(format(" -Xmx%s -Xms%s", maxHeapMemorySize, initialHeapMemorySize))
+            .append(format(" -Dhazelcast.config=./conf/%s", getConfigFileName()))
+            .append(format(" com.hazelcast.core.server.StartServer >> %s 2>&1 </dev/null &", getLogFileLocation()));
+        
+        newScript(MutableMap.of(USE_PID_FILE, true), LAUNCHING)
+            .updateTaskAndFailOnNonZeroResultCode()
+            .body.append(commandBuilder.toString())
+            .execute();
+    }
+       
+    public String getConfigFileName() {
+        return entity.getConfig(HazelcastNode.CONFIG_FILE_NAME);
+    }
+    
+    public String getHeapMemorySize() {
+        return entity.getConfig(HazelcastNode.NODE_HEAP_MEMORY_SIZE);
+    }
+    
+    @Override
+    public boolean isRunning() {       
+        return newScript(MutableMap.of(USE_PID_FILE, true), CHECK_RUNNING).execute() == 0;
+    }
+    
+    @Override
+    public void stop() {
+        newScript(MutableMap.of(USE_PID_FILE, true), STOPPING).execute();
+    }
+    
+    @Override
+    public void kill() {
+        newScript(MutableMap.of(USE_PID_FILE, true), KILLING).execute();
+    }
+
+    public List<String> getHazelcastNodesList() throws ExecutionException, InterruptedException {
+        HazelcastCluster cluster = (HazelcastCluster) entity.getParent();
+        List<String> result = Lists.newArrayList();
+
+        for (Entity member : cluster.getMembers()) {
+            String address = Entities.attributeSupplierWhenReady(member, HazelcastNode.SUBNET_ADDRESS).get();
+            Integer port = Entities.attributeSupplierWhenReady(member, HazelcastNode.NODE_PORT).get();
+            
+            String addressAndPort = String.format("%s:%d", address, port);
+            
+            if (LOG.isInfoEnabled()) {
+                LOG.info("Adding {} to the members' list of {}", addressAndPort, entity.getAttribute(HazelcastNode.NODE_NAME));
+            }
+            result.add(addressAndPort);
+        }
+        
+        return result;
+    }
+
+    @Override
+    protected String getLogFileLocation() {
+        return Os.mergePathsUnix(getRunDir(),"/log/out.log");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5Driver.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5Driver.java b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5Driver.java
new file mode 100644
index 0000000..810450e
--- /dev/null
+++ b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5Driver.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.infinispan;
+
+public interface Infinispan5Driver {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5Server.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5Server.java b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5Server.java
new file mode 100644
index 0000000..cb1043b
--- /dev/null
+++ b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5Server.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.infinispan;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.entity.java.UsesJmx;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.flags.SetFromFlag;
+
+/**
+ * An {@link brooklyn.entity.Entity} that represents an Infinispan service
+ */
+public class Infinispan5Server extends SoftwareProcessImpl implements UsesJmx {
+    private static final Logger log = LoggerFactory.getLogger(Infinispan5Server.class);
+    
+    public static final BasicAttributeSensorAndConfigKey<String> PROTOCOL = new BasicAttributeSensorAndConfigKey<String>(
+            String.class, "infinispan.server.protocol", 
+            "Infinispan protocol (e.g. memcached, hotrod, or websocket)", "memcached");
+    
+    public static final PortAttributeSensorAndConfigKey PORT = new PortAttributeSensorAndConfigKey(
+            "infinispan.server.port", "TCP port number to listen on");
+
+    @SetFromFlag("version")
+    public static final ConfigKey<String> SUGGESTED_VERSION =
+            ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "5.0.0.CR8");
+
+    // Default filename is "infinispan-${version}-all.zip"
+    @SetFromFlag("downloadUrl")
+    public static final BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>(
+            SoftwareProcess.DOWNLOAD_URL, "http://sourceforge.net/projects/infinispan/files/infinispan/${version}/infinispan-${version}-all.zip/download");
+
+    public Infinispan5Server() {
+        this(MutableMap.of(), null);
+    }
+    public Infinispan5Server(Map properties) {
+        this(properties, null);
+    }
+    public Infinispan5Server(Entity parent) {
+        this(MutableMap.of(), parent);
+    }
+    public Infinispan5Server(Map properties, Entity parent) {
+        super(properties, parent);
+    }
+
+    @Override
+    public Class getDriverInterface() {
+        return Infinispan5Driver.class;
+    }
+
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+        super.connectServiceUpIsRunning();
+    }
+    
+    @Override
+    protected void disconnectSensors() {
+        super.disconnectServiceUpIsRunning();
+        super.disconnectSensors();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java
new file mode 100644
index 0000000..1c2d33b
--- /dev/null
+++ b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.infinispan;
+
+import static java.lang.String.format;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
+import brooklyn.location.Location;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Networking;
+import brooklyn.util.ssh.BashCommands;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Start a {@link TomcatServer} in a {@link Location} accessible over ssh.
+ */
+public class Infinispan5SshDriver extends JavaSoftwareProcessSshDriver implements Infinispan5Driver {
+
+    public Infinispan5SshDriver(Infinispan5Server entity, SshMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    @Override
+    protected String getLogFileLocation() {
+        throw new UnsupportedOperationException("Work in progress");
+    }
+
+    protected String getProtocol() {
+        return entity.getAttribute(Infinispan5Server.PROTOCOL);
+    }
+
+    protected Integer getPort() {
+        return entity.getAttribute(Infinispan5Server.PORT);
+    }
+
+    @Override
+    public void install() {
+        List<String> urls = resolver.getTargets();
+        String saveAs = resolver.getFilename();
+
+        List<String> commands = ImmutableList.<String>builder()
+                .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs))
+                .add(BashCommands.INSTALL_ZIP)
+                .add("unzip " + saveAs)
+                .build();
+
+        newScript(INSTALLING).
+                failOnNonZeroResultCode().
+                body.append(commands).execute();
+    }
+
+    @Override
+    public void customize() {
+        // TODO create and reference a conf.xml? And start with --cache_config <path>
+        Map ports = MutableMap.of("port", getPort(), "jmxPort", getJmxPort());
+        Networking.checkPortsValid(ports);
+
+        newScript(CUSTOMIZING)
+                .body.append()
+                .execute();
+    }
+
+    @Override
+    public void launch() {
+        // FIXME Do we want to redirect stdout/stderr: >> %s/console 2>&1 </dev/null &", getRunDir())
+        newScript(MutableMap.of("usePidFile", true), LAUNCHING).
+                body.append(
+                        format("%s/bin/startServer.sh --protocol %s "
+                                +(getPort() != null ? " --port %s" : "")+" &", 
+                                getExpandedInstallDir(), getProtocol(), getPort()))
+                .execute();
+    }
+
+
+    @Override
+    public boolean isRunning() {
+        Map flags = MutableMap.of("usePidFile", true);
+        return newScript(flags, CHECK_RUNNING).execute() == 0;
+    }
+    
+    @Override
+    public void stop() {
+        Map flags = MutableMap.of("usePidFile", true);
+        newScript(flags, STOPPING).execute();
+    }
+
+    @Override
+    public void kill() {
+        Map flags = MutableMap.of("usePidFile", true);
+        newScript(flags, KILLING).execute();
+    }
+
+    @Override
+    protected List<String> getCustomJavaConfigOptions() {
+        List<String> options = new LinkedList<String>();
+        options.addAll(super.getCustomJavaConfigOptions());
+        options.add("-Xms200m");
+        options.add("-Xmx800m");
+        options.add("-XX:MaxPermSize=400m");
+        return options;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/resources/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/resources/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml b/sandbox/nosql/src/main/resources/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml
deleted file mode 100644
index 459be4e..0000000
--- a/sandbox/nosql/src/main/resources/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-[#ftl]
-<?xml version="1.0" encoding="UTF-8"?>
-
-<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.4.xsd"
-           xmlns="http://www.hazelcast.com/schema/config"
-           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-    <group>
-        <name>${entity.groupName}</name>
-        <password>${entity.groupPassword}</password>
-    </group>
-    <management-center enabled="false">http://localhost:8080/mancenter</management-center>
-    <network>
-        <port auto-increment="true" port-count="100">${entity.nodePort?c}</port>
-        <outbound-ports>
-            <!--
-            Allowed port range when connecting to other nodes.
-            0 or * means use system provided port.
-            -->
-            <ports>0</ports>
-        </outbound-ports>
-        
-        <interfaces enabled="true">
-            <interface>${entity.listenAddress}</interface> 
-        </interfaces> 
-        
-        <join>
-            <multicast enabled="false" />
-
-            <tcp-ip enabled="true">
-            [#list driver.hazelcastNodesList as member]
-                <member>${member}</member>
-            [/#list]
-            </tcp-ip>
-            
-            <aws enabled="false" />
-
-        </join>
-
-        <ssl enabled="false"/>
-        <socket-interceptor enabled="false"/>
-        <symmetric-encryption enabled="false"/>
-
-    </network>
-    <partition-group enabled="false"/>
-
-    <map name="default">
-        <in-memory-format>BINARY</in-memory-format>
-        <backup-count>1</backup-count>
-        <async-backup-count>0</async-backup-count>
-        <time-to-live-seconds>0</time-to-live-seconds>
-        <max-idle-seconds>0</max-idle-seconds>
-        <eviction-policy>NONE</eviction-policy>
-        <max-size policy="PER_NODE">0</max-size>
-        <eviction-percentage>25</eviction-percentage>
-        <min-eviction-check-millis>100</min-eviction-check-millis>
-        <merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy</merge-policy>
-    </map>
-
-    <serialization>
-        <portable-version>0</portable-version>
-    </serialization>
-
-    <services enable-defaults="true"/>
-
-</hazelcast>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/resources/org/apache/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/resources/org/apache/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml b/sandbox/nosql/src/main/resources/org/apache/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml
new file mode 100644
index 0000000..459be4e
--- /dev/null
+++ b/sandbox/nosql/src/main/resources/org/apache/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml
@@ -0,0 +1,65 @@
+[#ftl]
+<?xml version="1.0" encoding="UTF-8"?>
+
+<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.4.xsd"
+           xmlns="http://www.hazelcast.com/schema/config"
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <group>
+        <name>${entity.groupName}</name>
+        <password>${entity.groupPassword}</password>
+    </group>
+    <management-center enabled="false">http://localhost:8080/mancenter</management-center>
+    <network>
+        <port auto-increment="true" port-count="100">${entity.nodePort?c}</port>
+        <outbound-ports>
+            <!--
+            Allowed port range when connecting to other nodes.
+            0 or * means use system provided port.
+            -->
+            <ports>0</ports>
+        </outbound-ports>
+        
+        <interfaces enabled="true">
+            <interface>${entity.listenAddress}</interface> 
+        </interfaces> 
+        
+        <join>
+            <multicast enabled="false" />
+
+            <tcp-ip enabled="true">
+            [#list driver.hazelcastNodesList as member]
+                <member>${member}</member>
+            [/#list]
+            </tcp-ip>
+            
+            <aws enabled="false" />
+
+        </join>
+
+        <ssl enabled="false"/>
+        <socket-interceptor enabled="false"/>
+        <symmetric-encryption enabled="false"/>
+
+    </network>
+    <partition-group enabled="false"/>
+
+    <map name="default">
+        <in-memory-format>BINARY</in-memory-format>
+        <backup-count>1</backup-count>
+        <async-backup-count>0</async-backup-count>
+        <time-to-live-seconds>0</time-to-live-seconds>
+        <max-idle-seconds>0</max-idle-seconds>
+        <eviction-policy>NONE</eviction-policy>
+        <max-size policy="PER_NODE">0</max-size>
+        <eviction-percentage>25</eviction-percentage>
+        <min-eviction-check-millis>100</min-eviction-check-millis>
+        <merge-policy>com.hazelcast.map.merge.PutIfAbsentMapMergePolicy</merge-policy>
+    </map>
+
+    <serialization>
+        <portable-version>0</portable-version>
+    </serialization>
+
+    <services enable-defaults="true"/>
+
+</hazelcast>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/test/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/test/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterEc2LiveTest.java b/sandbox/nosql/src/test/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterEc2LiveTest.java
deleted file mode 100644
index bce319c..0000000
--- a/sandbox/nosql/src/test/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterEc2LiveTest.java
+++ /dev/null
@@ -1,71 +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.nosql.hazelcast;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.AbstractEc2LiveTest;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.location.Location;
-import brooklyn.test.EntityTestUtils;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-
-public class HazelcastClusterEc2LiveTest extends AbstractEc2LiveTest {
-    @SuppressWarnings("unused")
-    private static final Logger LOG = LoggerFactory.getLogger(HazelcastClusterEc2LiveTest.class);
-
-    @Override
-    protected void doTest(Location loc) throws Exception {
-        HazelcastCluster cluster = app.createAndManageChild(EntitySpec.create(HazelcastCluster.class)
-                .configure(HazelcastCluster.INITIAL_SIZE, 3)
-                .configure(HazelcastCluster.MEMBER_SPEC, EntitySpec.create(HazelcastNode.class)));
-        app.start(ImmutableList.of(loc));
-
-        EntityTestUtils.assertAttributeEqualsEventually(cluster, HazelcastNode.SERVICE_UP, true);
-
-        HazelcastNode first = (HazelcastNode) Iterables.get(cluster.getMembers(), 0);
-        HazelcastNode second = (HazelcastNode) Iterables.get(cluster.getMembers(), 1);
-
-        assertNodesUpAndInCluster(first, second);
-        
-        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_UP, true);
-    }
-    
-    private void assertNodesUpAndInCluster(final HazelcastNode... nodes) {
-        for (final HazelcastNode node : nodes) {
-            EntityTestUtils.assertAttributeEqualsEventually(node, HazelcastNode.SERVICE_UP, true);
-        }
-    }
-
-    @Test(enabled = false)
-    public void testDummy() {
-    } // Convince TestNG IDE integration that this really does have test methods
-
-    
-    @Test(groups = {"Live", "Live-sanity"})
-    @Override
-    public void test_CentOS_6_3() throws Exception {
-        super.test_CentOS_6_3();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/test/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterSoftlayerLiveTest.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/test/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterSoftlayerLiveTest.java b/sandbox/nosql/src/test/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterSoftlayerLiveTest.java
deleted file mode 100644
index 4ff4ab9..0000000
--- a/sandbox/nosql/src/test/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterSoftlayerLiveTest.java
+++ /dev/null
@@ -1,71 +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.nosql.hazelcast;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.AbstractSoftlayerLiveTest;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.location.Location;
-import brooklyn.test.EntityTestUtils;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-
-public class HazelcastClusterSoftlayerLiveTest extends AbstractSoftlayerLiveTest {
-    @SuppressWarnings("unused")
-    private static final Logger LOG = LoggerFactory.getLogger(HazelcastClusterSoftlayerLiveTest.class);
-
-    @Override
-    protected void doTest(Location loc) throws Exception {
-        HazelcastCluster cluster = app.createAndManageChild(EntitySpec.create(HazelcastCluster.class)
-                .configure(HazelcastCluster.INITIAL_SIZE, 3)
-                .configure(HazelcastCluster.MEMBER_SPEC, EntitySpec.create(HazelcastNode.class)));
-        app.start(ImmutableList.of(loc));
-
-        EntityTestUtils.assertAttributeEqualsEventually(cluster, HazelcastNode.SERVICE_UP, true);
-
-        HazelcastNode first = (HazelcastNode) Iterables.get(cluster.getMembers(), 0);
-        HazelcastNode second = (HazelcastNode) Iterables.get(cluster.getMembers(), 1);
-
-        assertNodesUpAndInCluster(first, second);
-        
-        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_UP, true);
-    }
-    
-    private void assertNodesUpAndInCluster(final HazelcastNode... nodes) {
-        for (final HazelcastNode node : nodes) {
-            EntityTestUtils.assertAttributeEqualsEventually(node, HazelcastNode.SERVICE_UP, true);
-        }
-    }
-
-    @Test(enabled = false)
-    public void testDummy() {
-    } // Convince TestNG IDE integration that this really does have test methods
-
-    
-    @Test(groups = {"Live", "Live-sanity"})
-    @Override
-    public void test_Ubuntu_12_0_4() throws Exception {
-        super.test_Ubuntu_12_0_4();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/test/java/brooklyn/entity/nosql/infinispan/Infinispan5ServerIntegrationTest.groovy
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/test/java/brooklyn/entity/nosql/infinispan/Infinispan5ServerIntegrationTest.groovy b/sandbox/nosql/src/test/java/brooklyn/entity/nosql/infinispan/Infinispan5ServerIntegrationTest.groovy
deleted file mode 100644
index 40eb546..0000000
--- a/sandbox/nosql/src/test/java/brooklyn/entity/nosql/infinispan/Infinispan5ServerIntegrationTest.groovy
+++ /dev/null
@@ -1,102 +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.nosql.infinispan
-
-import static brooklyn.test.TestUtils.*
-import static java.util.concurrent.TimeUnit.*
-import static org.testng.Assert.*
-
-import java.util.concurrent.TimeUnit
-
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import org.testng.annotations.AfterMethod
-import org.testng.annotations.BeforeMethod
-import org.testng.annotations.Test
-
-import brooklyn.entity.Application
-import brooklyn.entity.basic.Entities
-import brooklyn.location.basic.LocalhostMachineProvisioningLocation
-import brooklyn.test.entity.TestApplicationImpl
-import brooklyn.util.internal.TimeExtras
-import brooklyn.util.net.Networking
-import brooklyn.util.repeat.Repeater
-
-class Infinispan5ServerIntegrationTest {
-    private static final Logger logger = LoggerFactory.getLogger(Infinispan5ServerIntegrationTest.class)
-    
-    static String DEFAULT_PROTOCOL = "memcached"
-    static int DEFAULT_PORT = 11219
-
-    static boolean portLeftOpen = false;
-    
-    static { TimeExtras.init() }
-
-    @BeforeMethod(groups = [ "Integration" ])
-    public void failIfPortInUse() {
-        if (isPortInUse(DEFAULT_PORT, 5000L)) {
-            portLeftOpen = true;
-            fail "someone is already listening on port $DEFAULT_PORT; tests assume that port $DEFAULT_PORT is free on localhost"
-        }
-    }
- 
-    @AfterMethod(groups = [ "Integration" ])
-    public void ensureIsShutDown() {
-        Socket shutdownSocket = null;
-        SocketException gotException = null;
-
-        boolean socketClosed = new Repeater("Checking Infinispan has shut down")
-            .repeat {
-                    if (shutdownSocket) shutdownSocket.close();
-                    try { shutdownSocket = new Socket(Networking.localHost, DEFAULT_PORT); }
-                    catch (SocketException e) { gotException = e; return; }
-                    gotException = null
-                }
-            .every(100 * MILLISECONDS)
-            .until { gotException }
-            .limitIterationsTo(25)
-            .run();
-
-        if (socketClosed == false) {
-            logger.error "Infinispan did not shut down";
-            throw new Exception("Infinispan did not shut down")
-        }
-    }
-
-    public void ensureIsUp() {
-        Socket socket = new Socket(Networking.localHost, DEFAULT_PORT);
-        socket.close();
-    }
-
-    @Test(groups = [ "Integration", "WIP" ])
-    public void testInfinispanStartsAndStops() {
-        Application app = new TestApplicationImpl();
-        try {
-            final Infinispan5Server infini = new Infinispan5Server(parent:app)
-            infini.setConfig(Infinispan5Server.PORT.getConfigKey(), DEFAULT_PORT)
-            infini.start([ new LocalhostMachineProvisioningLocation(name:'london') ])
-            
-            executeUntilSucceeds {
-                assertTrue infini.getAttribute(Infinispan5Server.SERVICE_UP)
-            }
-        } finally {
-            Entities.destroy(app);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterEc2LiveTest.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterEc2LiveTest.java b/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterEc2LiveTest.java
new file mode 100644
index 0000000..552ad02
--- /dev/null
+++ b/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterEc2LiveTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.hazelcast;
+
+import org.apache.brooklyn.entity.nosql.hazelcast.HazelcastCluster;
+import org.apache.brooklyn.entity.nosql.hazelcast.HazelcastNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.AbstractEc2LiveTest;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.EntityTestUtils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class HazelcastClusterEc2LiveTest extends AbstractEc2LiveTest {
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(HazelcastClusterEc2LiveTest.class);
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        HazelcastCluster cluster = app.createAndManageChild(EntitySpec.create(HazelcastCluster.class)
+                .configure(HazelcastCluster.INITIAL_SIZE, 3)
+                .configure(HazelcastCluster.MEMBER_SPEC, EntitySpec.create(HazelcastNode.class)));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, HazelcastNode.SERVICE_UP, true);
+
+        HazelcastNode first = (HazelcastNode) Iterables.get(cluster.getMembers(), 0);
+        HazelcastNode second = (HazelcastNode) Iterables.get(cluster.getMembers(), 1);
+
+        assertNodesUpAndInCluster(first, second);
+        
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_UP, true);
+    }
+    
+    private void assertNodesUpAndInCluster(final HazelcastNode... nodes) {
+        for (final HazelcastNode node : nodes) {
+            EntityTestUtils.assertAttributeEqualsEventually(node, HazelcastNode.SERVICE_UP, true);
+        }
+    }
+
+    @Test(enabled = false)
+    public void testDummy() {
+    } // Convince TestNG IDE integration that this really does have test methods
+
+    
+    @Test(groups = {"Live", "Live-sanity"})
+    @Override
+    public void test_CentOS_6_3() throws Exception {
+        super.test_CentOS_6_3();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterSoftlayerLiveTest.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterSoftlayerLiveTest.java b/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterSoftlayerLiveTest.java
new file mode 100644
index 0000000..03d95af
--- /dev/null
+++ b/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterSoftlayerLiveTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.hazelcast;
+
+import org.apache.brooklyn.entity.nosql.hazelcast.HazelcastCluster;
+import org.apache.brooklyn.entity.nosql.hazelcast.HazelcastNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.AbstractSoftlayerLiveTest;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.test.EntityTestUtils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+public class HazelcastClusterSoftlayerLiveTest extends AbstractSoftlayerLiveTest {
+    @SuppressWarnings("unused")
+    private static final Logger LOG = LoggerFactory.getLogger(HazelcastClusterSoftlayerLiveTest.class);
+
+    @Override
+    protected void doTest(Location loc) throws Exception {
+        HazelcastCluster cluster = app.createAndManageChild(EntitySpec.create(HazelcastCluster.class)
+                .configure(HazelcastCluster.INITIAL_SIZE, 3)
+                .configure(HazelcastCluster.MEMBER_SPEC, EntitySpec.create(HazelcastNode.class)));
+        app.start(ImmutableList.of(loc));
+
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, HazelcastNode.SERVICE_UP, true);
+
+        HazelcastNode first = (HazelcastNode) Iterables.get(cluster.getMembers(), 0);
+        HazelcastNode second = (HazelcastNode) Iterables.get(cluster.getMembers(), 1);
+
+        assertNodesUpAndInCluster(first, second);
+        
+        EntityTestUtils.assertAttributeEqualsEventually(cluster, Attributes.SERVICE_UP, true);
+    }
+    
+    private void assertNodesUpAndInCluster(final HazelcastNode... nodes) {
+        for (final HazelcastNode node : nodes) {
+            EntityTestUtils.assertAttributeEqualsEventually(node, HazelcastNode.SERVICE_UP, true);
+        }
+    }
+
+    @Test(enabled = false)
+    public void testDummy() {
+    } // Convince TestNG IDE integration that this really does have test methods
+
+    
+    @Test(groups = {"Live", "Live-sanity"})
+    @Override
+    public void test_Ubuntu_12_0_4() throws Exception {
+        super.test_Ubuntu_12_0_4();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5ServerIntegrationTest.groovy
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5ServerIntegrationTest.groovy b/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5ServerIntegrationTest.groovy
new file mode 100644
index 0000000..b7d8d1a
--- /dev/null
+++ b/sandbox/nosql/src/test/java/org/apache/brooklyn/entity/nosql/infinispan/Infinispan5ServerIntegrationTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.infinispan
+
+import static brooklyn.test.TestUtils.*
+import static java.util.concurrent.TimeUnit.*
+import static org.testng.Assert.*
+
+import java.util.concurrent.TimeUnit
+
+import org.apache.brooklyn.entity.nosql.infinispan.Infinispan5Server;
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.testng.annotations.AfterMethod
+import org.testng.annotations.BeforeMethod
+import org.testng.annotations.Test
+
+import brooklyn.entity.Application
+import brooklyn.entity.basic.Entities
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation
+import brooklyn.test.entity.TestApplicationImpl
+import brooklyn.util.internal.TimeExtras
+import brooklyn.util.net.Networking
+import brooklyn.util.repeat.Repeater
+
+class Infinispan5ServerIntegrationTest {
+    private static final Logger logger = LoggerFactory.getLogger(Infinispan5ServerIntegrationTest.class)
+    
+    static String DEFAULT_PROTOCOL = "memcached"
+    static int DEFAULT_PORT = 11219
+
+    static boolean portLeftOpen = false;
+    
+    static { TimeExtras.init() }
+
+    @BeforeMethod(groups = [ "Integration" ])
+    public void failIfPortInUse() {
+        if (isPortInUse(DEFAULT_PORT, 5000L)) {
+            portLeftOpen = true;
+            fail "someone is already listening on port $DEFAULT_PORT; tests assume that port $DEFAULT_PORT is free on localhost"
+        }
+    }
+ 
+    @AfterMethod(groups = [ "Integration" ])
+    public void ensureIsShutDown() {
+        Socket shutdownSocket = null;
+        SocketException gotException = null;
+
+        boolean socketClosed = new Repeater("Checking Infinispan has shut down")
+            .repeat {
+                    if (shutdownSocket) shutdownSocket.close();
+                    try { shutdownSocket = new Socket(Networking.localHost, DEFAULT_PORT); }
+                    catch (SocketException e) { gotException = e; return; }
+                    gotException = null
+                }
+            .every(100 * MILLISECONDS)
+            .until { gotException }
+            .limitIterationsTo(25)
+            .run();
+
+        if (socketClosed == false) {
+            logger.error "Infinispan did not shut down";
+            throw new Exception("Infinispan did not shut down")
+        }
+    }
+
+    public void ensureIsUp() {
+        Socket socket = new Socket(Networking.localHost, DEFAULT_PORT);
+        socket.close();
+    }
+
+    @Test(groups = [ "Integration", "WIP" ])
+    public void testInfinispanStartsAndStops() {
+        Application app = new TestApplicationImpl();
+        try {
+            final Infinispan5Server infini = new Infinispan5Server(parent:app)
+            infini.setConfig(Infinispan5Server.PORT.getConfigKey(), DEFAULT_PORT)
+            infini.start([ new LocalhostMachineProvisioningLocation(name:'london') ])
+            
+            executeUntilSucceeds {
+                assertTrue infini.getAttribute(Infinispan5Server.SERVICE_UP)
+            }
+        } finally {
+            Entities.destroy(app);
+        }
+    }
+}


[3/6] incubator-brooklyn git commit: package rename to org.apache.brooklyn: sandbox

Posted by ha...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/resources/brooklyn/entity/salt/minion
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/resources/brooklyn/entity/salt/minion b/sandbox/extra/src/main/resources/brooklyn/entity/salt/minion
deleted file mode 100644
index cff42c7..0000000
--- a/sandbox/extra/src/main/resources/brooklyn/entity/salt/minion
+++ /dev/null
@@ -1,52 +0,0 @@
-[#ftl]
-##
-# 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.
-#
-# SaltStack Minion Configuration
-#
-# /etc/salt/minion
-##
-
-# The salt master server
-master: ${entity.master.hostname}
-ipv6: False
-retry_dns: 30
-master_port: ${entity.master.saltPort,c} # 4506
-acceptance_wait_time: 30
-acceptance_wait_time_max: 300
-
-# Minion configuration
-id: ${entity.id}
-user: root
-backup_mode: minion
-
-# Directory settings
-root_dir: /
-pidfile: ${runDir}/salt-minion.pid
-pki_dir: ${runDir}/pki
-cachedir: ${runDir}/cache
-log_file: ${runDir}/minion.log
-key_logfile: ${runDir}/key.log
-
-#verify_env: True
-#cache_jobs: True # Debugging
-
-output: nested
-color: False
-log_level: info
-log_level_logfile: debug # Debugging

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/master
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/master b/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/master
new file mode 100644
index 0000000..72d7eb9
--- /dev/null
+++ b/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/master
@@ -0,0 +1,65 @@
+[#ftl]
+#
+# 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.
+#
+# /etc/salt/master
+##
+
+#interface: 0.0.0.0
+#ipv6: False
+#publish_port: ${entity.publishPort,c} # 4505
+
+#user: root
+#max_open_files: 100000
+#worker_threads: 5
+ret_port: ${entity.saltPort,c} # 4506
+
+root_dir: /
+pidfile: ${driver.pidFile}
+pki_dir: ${runDir}/pki
+cachedir: ${runDir}/cache
+log_file: ${driver.logFileLocation}
+key_logfile: ${runDir}/key.log
+
+#verify_env: True
+#keep_jobs: 24
+
+#timeout: 5
+#loop_interval: 60
+
+output: nested
+color: False
+log_level: info
+log_level_logfile: debug # Debugging
+
+#job_cache: True
+#minion_data_cache: True
+
+#open_mode: False
+#auto_accept: False
+#autosign_file: autosign.conf
+#permissive_pki_access: False
+
+fileserver_backend:
+  - git
+
+gitfs_remotes:
+  - git://github.com/saltstack/salt-states.git
+  - git://github.com/saltstack-formulas/postgres-formula.git
+  - ${entity.remoteUrl}
+  # TODO iterate through formula URLs

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/masterless
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/masterless b/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/masterless
new file mode 100644
index 0000000..ba41c6e
--- /dev/null
+++ b/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/masterless
@@ -0,0 +1,53 @@
+[#ftl]
+##
+#
+# 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.
+#
+# SaltStack Masterless Minion Configuration
+#
+# /etc/salt/minion
+##
+
+# Minion configuration
+id: ${entity.id}
+user: root
+backup_mode: minion
+
+# Directory settings
+root_dir: /
+pidfile: ${runDir}/salt-minion.pid
+pki_dir: ${runDir}/pki
+cachedir: ${runDir}/cache
+log_file: ${runDir}/minion.log
+key_logfile: ${runDir}/key.log
+
+file_client: local
+file_roots:
+  base:
+    - ${runDir}/base
+[#list formulas?keys as formulaName]
+    - ${installDir}/${formulaName}
+[/#list]
+
+#verify_env: True
+#cache_jobs: True # Debugging
+
+output: nested
+color: False
+log_level: info
+log_level_logfile: debug # Debugging

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/minion
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/minion b/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/minion
new file mode 100644
index 0000000..cff42c7
--- /dev/null
+++ b/sandbox/extra/src/main/resources/org/apache/brooklyn/entity/salt/minion
@@ -0,0 +1,52 @@
+[#ftl]
+##
+# 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.
+#
+# SaltStack Minion Configuration
+#
+# /etc/salt/minion
+##
+
+# The salt master server
+master: ${entity.master.hostname}
+ipv6: False
+retry_dns: 30
+master_port: ${entity.master.saltPort,c} # 4506
+acceptance_wait_time: 30
+acceptance_wait_time_max: 300
+
+# Minion configuration
+id: ${entity.id}
+user: root
+backup_mode: minion
+
+# Directory settings
+root_dir: /
+pidfile: ${runDir}/salt-minion.pid
+pki_dir: ${runDir}/pki
+cachedir: ${runDir}/cache
+log_file: ${runDir}/minion.log
+key_logfile: ${runDir}/key.log
+
+#verify_env: True
+#cache_jobs: True # Debugging
+
+output: nested
+color: False
+log_level: info
+log_level_logfile: debug # Debugging

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/test/java/brooklyn/entity/database/postgresql/PostgreSqlSaltLiveTest.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/test/java/brooklyn/entity/database/postgresql/PostgreSqlSaltLiveTest.java b/sandbox/extra/src/test/java/brooklyn/entity/database/postgresql/PostgreSqlSaltLiveTest.java
deleted file mode 100644
index a44efa9..0000000
--- a/sandbox/extra/src/test/java/brooklyn/entity/database/postgresql/PostgreSqlSaltLiveTest.java
+++ /dev/null
@@ -1,109 +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.database.postgresql;
-
-import java.util.Random;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.database.VogellaExampleAccess;
-import brooklyn.entity.effector.EffectorTasks;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.salt.SaltConfig;
-import brooklyn.entity.salt.SaltLiveTestSupport;
-import brooklyn.entity.software.SshEffectorTasks;
-import brooklyn.location.PortRange;
-import brooklyn.location.basic.PortRanges;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.task.system.ProcessTaskWrapper;
-import brooklyn.util.time.Duration;
-
-import com.google.common.collect.ImmutableList;
-
-/**
- * Tests Salt installation of {@link PostgreSqlNode} entity.
- */
-public class PostgreSqlSaltLiveTest extends SaltLiveTestSupport {
-
-    private static final Logger log = LoggerFactory.getLogger(PostgreSqlSaltLiveTest.class);
-
-    private PostgreSqlNode psql;
-
-    @Override
-    @AfterMethod(alwaysRun=true)
-    public void tearDown() throws Exception {
-        try {
-            if (psql != null) psql.stop();
-        } finally {
-            super.tearDown();
-        }
-    }
-
-    @Test(groups="Live")
-    public void testPostgresStartsAndStops() throws Exception {
-        psql = app.createAndManageChild(EntitySpec.create(PostgreSqlNode.class, PostgreSqlNodeSaltImpl.class)
-                .configure(SaltConfig.MASTERLESS_MODE, true));
-
-        app.start(ImmutableList.of(targetLocation));
-
-        Entities.submit(psql, SshEffectorTasks.ssh("ps aux | grep [p]ostgres").requiringExitCodeZero());
-        SshMachineLocation targetMachine = EffectorTasks.getSshMachine(psql);
-
-        psql.stop();
-
-        try {
-            // if host is still contactable ensure postgres is not running
-            ProcessTaskWrapper<Integer> t = Entities.submit(app, SshEffectorTasks.ssh("ps aux | grep [p]ostgres").machine(targetMachine).allowingNonZeroExitCode());
-            t.getTask().blockUntilEnded(Duration.TEN_SECONDS);
-            if (!t.isDone()) {
-                Assert.fail("Task not finished yet: "+t.getTask());
-            }
-            Assert.assertNotEquals(t.get(), (Integer)0, "Task ended with code "+t.get()+"; output: "+t.getStdout() );
-        } catch (Exception e) {
-            // host has been killed, that is fine
-            log.info("Machine "+targetMachine+" destroyed on stop (expected - "+e+")");
-        }
-    }
-
-    @Test(groups="Live")
-    public void testPostgresScriptAndAccess() throws Exception {
-        SaltLiveTestSupport.createLocation(mgmt);
-        PortRange randomPort = PortRanges.fromString(""+(5420+new Random().nextInt(10))+"+");
-        psql = app.createAndManageChild(EntitySpec.create(PostgreSqlNode.class, PostgreSqlNodeSaltImpl.class)
-                .configure(SaltConfig.MASTERLESS_MODE, true)
-                .configure(PostgreSqlNode.CREATION_SCRIPT_CONTENTS, PostgreSqlIntegrationTest.CREATION_SCRIPT)
-                .configure(PostgreSqlNode.POSTGRESQL_PORT, randomPort));
-
-        app.start(ImmutableList.of(targetLocation));
-
-        String url = psql.getAttribute(PostgreSqlNode.DATASTORE_URL);
-        log.info("Trying to connect to "+psql+" at "+url);
-        Assert.assertNotNull(url);
-        Assert.assertTrue(url.contains("542"));
-
-        new VogellaExampleAccess("org.postgresql.Driver", url).readModifyAndRevertDataBase();
-    }
-
-}
-

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/test/java/brooklyn/entity/salt/SaltConfigsTest.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/test/java/brooklyn/entity/salt/SaltConfigsTest.java b/sandbox/extra/src/test/java/brooklyn/entity/salt/SaltConfigsTest.java
deleted file mode 100644
index 6706748..0000000
--- a/sandbox/extra/src/test/java/brooklyn/entity/salt/SaltConfigsTest.java
+++ /dev/null
@@ -1,69 +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.salt;
-
-import java.util.Map;
-import java.util.Set;
-
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.Test;
-
-import brooklyn.entity.basic.ApplicationBuilder;
-import brooklyn.entity.basic.Entities;
-import brooklyn.test.entity.TestApplication;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-public class SaltConfigsTest {
-
-    private TestApplication app = null;
-
-    @AfterMethod(alwaysRun=true)
-    public void tearDown() {
-        if (app!=null) Entities.destroyAll(app.getManagementContext());
-        app = null;
-    }
-
-    @Test
-    public void testAddToRunList() {
-        app = ApplicationBuilder.newManagedApp(TestApplication.class);
-        SaltConfigs.addToRunList(app, "a", "b");
-        Set<? extends String> runs = app.getConfig(SaltConfig.SALT_RUN_LIST);
-        Assert.assertEquals(runs, ImmutableSet.of("a", "b"));
-    }
-
-    @Test
-    public void testAddToFormulas() {
-        app = ApplicationBuilder.newManagedApp(TestApplication.class);
-        SaltConfigs.addToFormulas(app, "k1", "v1");
-        SaltConfigs.addToFormulas(app, "k2", "v2");
-        Map<String, String> formulas = app.getConfig(SaltConfig.SALT_FORMULAS);
-        Assert.assertEquals(formulas, ImmutableMap.of("k1", "v1", "k2", "v2"));
-    }
-
-    @Test
-    public void testAddLaunchAttributes() {
-        app = ApplicationBuilder.newManagedApp(TestApplication.class);
-        SaltConfigs.addLaunchAttributes(app, ImmutableMap.of("k1", "v1"));
-        Map<String, Object> attribs = app.getConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES);
-        Assert.assertEquals(attribs, ImmutableMap.of("k1", "v1"));
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/test/java/brooklyn/entity/salt/SaltLiveTestSupport.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/test/java/brooklyn/entity/salt/SaltLiveTestSupport.java b/sandbox/extra/src/test/java/brooklyn/entity/salt/SaltLiveTestSupport.java
deleted file mode 100644
index 6cb7409..0000000
--- a/sandbox/extra/src/test/java/brooklyn/entity/salt/SaltLiveTestSupport.java
+++ /dev/null
@@ -1,68 +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.salt;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.BeforeMethod;
-
-import brooklyn.entity.BrooklynAppLiveTestSupport;
-import brooklyn.location.Location;
-import brooklyn.location.MachineProvisioningLocation;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.management.ManagementContext;
-
-public class SaltLiveTestSupport extends BrooklynAppLiveTestSupport {
-
-    private static final Logger log = LoggerFactory.getLogger(SaltLiveTestSupport.class);
-
-    protected MachineProvisioningLocation<? extends SshMachineLocation> targetLocation;
-
-    @Override
-    @BeforeMethod(alwaysRun=true)
-    public void setUp() throws Exception {
-        super.setUp();
-
-        targetLocation = createLocation();
-    }
-
-    protected MachineProvisioningLocation<? extends SshMachineLocation> createLocation() {
-        return createLocation(mgmt);
-    }
-
-    /**
-     * Convenience for setting up a pre-built or fixed IP machine.
-     * <p>
-     * Useful if you are unable to set up Salt on localhost,
-     * and for ensuring tests against Salt always use the same
-     * configured location.
-     */
-    @SuppressWarnings("unchecked")
-    public static MachineProvisioningLocation<? extends SshMachineLocation> createLocation(ManagementContext mgmt) {
-        Location bestLocation = mgmt.getLocationRegistry().resolveIfPossible("named:SaltTests");
-        if (bestLocation==null) {
-            log.info("using AWS for salt tests because named:SaltTests does not exist");
-            bestLocation = mgmt.getLocationRegistry().resolveIfPossible("jclouds:aws-ec2:us-east-1");
-        }
-        if (bestLocation==null) {
-            throw new IllegalStateException("Need a location called named:SaltTests or AWS configured for these tests");
-        }
-        return (MachineProvisioningLocation<? extends SshMachineLocation>)bestLocation;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/test/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSaltLiveTest.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/test/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSaltLiveTest.java b/sandbox/extra/src/test/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSaltLiveTest.java
new file mode 100644
index 0000000..250cc03
--- /dev/null
+++ b/sandbox/extra/src/test/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSaltLiveTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.postgresql;
+
+import java.util.Random;
+
+import org.apache.brooklyn.entity.database.postgresql.PostgreSqlNodeSaltImpl;
+import org.apache.brooklyn.entity.salt.SaltConfig;
+import org.apache.brooklyn.entity.salt.SaltLiveTestSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.database.VogellaExampleAccess;
+import brooklyn.entity.database.postgresql.PostgreSqlIntegrationTest;
+import brooklyn.entity.database.postgresql.PostgreSqlNode;
+import brooklyn.entity.effector.EffectorTasks;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.software.SshEffectorTasks;
+import brooklyn.location.PortRange;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.task.system.ProcessTaskWrapper;
+import brooklyn.util.time.Duration;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Tests Salt installation of {@link PostgreSqlNode} entity.
+ */
+public class PostgreSqlSaltLiveTest extends SaltLiveTestSupport {
+
+    private static final Logger log = LoggerFactory.getLogger(PostgreSqlSaltLiveTest.class);
+
+    private PostgreSqlNode psql;
+
+    @Override
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() throws Exception {
+        try {
+            if (psql != null) psql.stop();
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    @Test(groups="Live")
+    public void testPostgresStartsAndStops() throws Exception {
+        psql = app.createAndManageChild(EntitySpec.create(PostgreSqlNode.class, PostgreSqlNodeSaltImpl.class)
+                .configure(SaltConfig.MASTERLESS_MODE, true));
+
+        app.start(ImmutableList.of(targetLocation));
+
+        Entities.submit(psql, SshEffectorTasks.ssh("ps aux | grep [p]ostgres").requiringExitCodeZero());
+        SshMachineLocation targetMachine = EffectorTasks.getSshMachine(psql);
+
+        psql.stop();
+
+        try {
+            // if host is still contactable ensure postgres is not running
+            ProcessTaskWrapper<Integer> t = Entities.submit(app, SshEffectorTasks.ssh("ps aux | grep [p]ostgres").machine(targetMachine).allowingNonZeroExitCode());
+            t.getTask().blockUntilEnded(Duration.TEN_SECONDS);
+            if (!t.isDone()) {
+                Assert.fail("Task not finished yet: "+t.getTask());
+            }
+            Assert.assertNotEquals(t.get(), (Integer)0, "Task ended with code "+t.get()+"; output: "+t.getStdout() );
+        } catch (Exception e) {
+            // host has been killed, that is fine
+            log.info("Machine "+targetMachine+" destroyed on stop (expected - "+e+")");
+        }
+    }
+
+    @Test(groups="Live")
+    public void testPostgresScriptAndAccess() throws Exception {
+        SaltLiveTestSupport.createLocation(mgmt);
+        PortRange randomPort = PortRanges.fromString(""+(5420+new Random().nextInt(10))+"+");
+        psql = app.createAndManageChild(EntitySpec.create(PostgreSqlNode.class, PostgreSqlNodeSaltImpl.class)
+                .configure(SaltConfig.MASTERLESS_MODE, true)
+                .configure(PostgreSqlNode.CREATION_SCRIPT_CONTENTS, PostgreSqlIntegrationTest.CREATION_SCRIPT)
+                .configure(PostgreSqlNode.POSTGRESQL_PORT, randomPort));
+
+        app.start(ImmutableList.of(targetLocation));
+
+        String url = psql.getAttribute(PostgreSqlNode.DATASTORE_URL);
+        log.info("Trying to connect to "+psql+" at "+url);
+        Assert.assertNotNull(url);
+        Assert.assertTrue(url.contains("542"));
+
+        new VogellaExampleAccess("org.postgresql.Driver", url).readModifyAndRevertDataBase();
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/test/java/org/apache/brooklyn/entity/salt/SaltConfigsTest.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/test/java/org/apache/brooklyn/entity/salt/SaltConfigsTest.java b/sandbox/extra/src/test/java/org/apache/brooklyn/entity/salt/SaltConfigsTest.java
new file mode 100644
index 0000000..cf1a800
--- /dev/null
+++ b/sandbox/extra/src/test/java/org/apache/brooklyn/entity/salt/SaltConfigsTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.entity.salt.SaltConfig;
+import org.apache.brooklyn.entity.salt.SaltConfigs;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.Entities;
+import brooklyn.test.entity.TestApplication;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+public class SaltConfigsTest {
+
+    private TestApplication app = null;
+
+    @AfterMethod(alwaysRun=true)
+    public void tearDown() {
+        if (app!=null) Entities.destroyAll(app.getManagementContext());
+        app = null;
+    }
+
+    @Test
+    public void testAddToRunList() {
+        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+        SaltConfigs.addToRunList(app, "a", "b");
+        Set<? extends String> runs = app.getConfig(SaltConfig.SALT_RUN_LIST);
+        Assert.assertEquals(runs, ImmutableSet.of("a", "b"));
+    }
+
+    @Test
+    public void testAddToFormulas() {
+        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+        SaltConfigs.addToFormulas(app, "k1", "v1");
+        SaltConfigs.addToFormulas(app, "k2", "v2");
+        Map<String, String> formulas = app.getConfig(SaltConfig.SALT_FORMULAS);
+        Assert.assertEquals(formulas, ImmutableMap.of("k1", "v1", "k2", "v2"));
+    }
+
+    @Test
+    public void testAddLaunchAttributes() {
+        app = ApplicationBuilder.newManagedApp(TestApplication.class);
+        SaltConfigs.addLaunchAttributes(app, ImmutableMap.of("k1", "v1"));
+        Map<String, Object> attribs = app.getConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES);
+        Assert.assertEquals(attribs, ImmutableMap.of("k1", "v1"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/test/java/org/apache/brooklyn/entity/salt/SaltLiveTestSupport.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/test/java/org/apache/brooklyn/entity/salt/SaltLiveTestSupport.java b/sandbox/extra/src/test/java/org/apache/brooklyn/entity/salt/SaltLiveTestSupport.java
new file mode 100644
index 0000000..8fbc14f
--- /dev/null
+++ b/sandbox/extra/src/test/java/org/apache/brooklyn/entity/salt/SaltLiveTestSupport.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.BeforeMethod;
+
+import brooklyn.entity.BrooklynAppLiveTestSupport;
+import brooklyn.location.Location;
+import brooklyn.location.MachineProvisioningLocation;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.management.ManagementContext;
+
+public class SaltLiveTestSupport extends BrooklynAppLiveTestSupport {
+
+    private static final Logger log = LoggerFactory.getLogger(SaltLiveTestSupport.class);
+
+    protected MachineProvisioningLocation<? extends SshMachineLocation> targetLocation;
+
+    @Override
+    @BeforeMethod(alwaysRun=true)
+    public void setUp() throws Exception {
+        super.setUp();
+
+        targetLocation = createLocation();
+    }
+
+    protected MachineProvisioningLocation<? extends SshMachineLocation> createLocation() {
+        return createLocation(mgmt);
+    }
+
+    /**
+     * Convenience for setting up a pre-built or fixed IP machine.
+     * <p>
+     * Useful if you are unable to set up Salt on localhost,
+     * and for ensuring tests against Salt always use the same
+     * configured location.
+     */
+    @SuppressWarnings("unchecked")
+    public static MachineProvisioningLocation<? extends SshMachineLocation> createLocation(ManagementContext mgmt) {
+        Location bestLocation = mgmt.getLocationRegistry().resolveIfPossible("named:SaltTests");
+        if (bestLocation==null) {
+            log.info("using AWS for salt tests because named:SaltTests does not exist");
+            bestLocation = mgmt.getLocationRegistry().resolveIfPossible("jclouds:aws-ec2:us-east-1");
+        }
+        if (bestLocation==null) {
+            throw new IllegalStateException("Need a location called named:SaltTests or AWS configured for these tests");
+        }
+        return (MachineProvisioningLocation<? extends SshMachineLocation>)bestLocation;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java b/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java
deleted file mode 100644
index 5fe96cd..0000000
--- a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java
+++ /dev/null
@@ -1,463 +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.monitoring.zabbix;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.net.URI;
-import java.net.URL;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.http.client.HttpClient;
-import org.apache.http.impl.NoConnectionReuseStrategy;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.EntityFunctions;
-import brooklyn.entity.basic.EntityLocal;
-import brooklyn.event.feed.AbstractFeed;
-import brooklyn.event.feed.AttributePollHandler;
-import brooklyn.event.feed.PollHandler;
-import brooklyn.event.feed.Poller;
-import brooklyn.event.feed.http.HttpValueFunctions;
-import brooklyn.location.Location;
-import brooklyn.location.MachineLocation;
-import brooklyn.location.access.BrooklynAccessUtils;
-import brooklyn.location.basic.SupportsPortForwarding;
-import brooklyn.util.http.HttpTool;
-import brooklyn.util.http.HttpToolResponse;
-import brooklyn.util.net.Cidr;
-
-import com.google.common.base.Function;
-import com.google.common.base.Functions;
-import com.google.common.base.Objects;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicates;
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.net.HostAndPort;
-import com.google.common.reflect.TypeToken;
-import com.google.gson.JsonObject;
-
-public class ZabbixFeed extends AbstractFeed {
-
-    public static final Logger log = LoggerFactory.getLogger(ZabbixFeed.class);
-
-    public static final String JSON_ITEM_GET =
-            "{ \"jsonrpc\":\"2.0\",\"method\":\"item.get\"," +
-                "\"params\":{\"output\":\"extend\"," +
-                    "\"filter\":{\"hostid\":[\"{{hostId}}\"],\"key_\":\"{{itemKey}}\"}}," +
-                "\"auth\":\"{{token}}\",\"id\":{{id}}}";
-    public static final String JSON_USER_LOGIN =
-            "{ \"jsonrpc\":\"2.0\",\"method\":\"user.login\"," +
-                "\"params\":{\"user\":\"{{username}}\",\"password\":\"{{password}}\"}," +
-                "\"id\":0 }";
-    public static final String JSON_HOST_CREATE =
-            "{ \"jsonrpc\":\"2.0\",\"method\":\"host.create\"," +
-                "\"params\":{\"host\":\"{{host}}\"," +
-                    "\"interfaces\":[{\"type\":1,\"main\":1,\"useip\":1,\"ip\":\"{{ip}}\",\"dns\":\"\",\"port\":\"{{port}}\"}]," +
-                    "\"groups\":[{\"groupid\":\"{{groupId}}\"}]," +
-                    "\"templates\":[{\"templateid\":\"{{templateId}}\"}]}," +
-                "\"auth\":\"{{token}}\",\"id\":{{id}}}";
-
-    private static final AtomicInteger id = new AtomicInteger(0);
-
-    @SuppressWarnings("serial")
-    public static final ConfigKey<Set<ZabbixPollConfig<?>>> POLLS = ConfigKeys.newConfigKey(
-            new TypeToken<Set<ZabbixPollConfig<?>>>() {},
-            "polls");
-
-    @SuppressWarnings("serial")
-    public static final ConfigKey<Supplier<URI>> BASE_URI_PROVIDER = ConfigKeys.newConfigKey(
-            new TypeToken<Supplier<URI>>() {},
-            "baseUriProvider");
-    
-    public static final ConfigKey<Integer> GROUP_ID = ConfigKeys.newIntegerConfigKey("groupId");
-    
-    public static final ConfigKey<Integer> TEMPLATE_ID = ConfigKeys.newIntegerConfigKey("templateId");
-
-    @SuppressWarnings("serial")
-    public static final ConfigKey<Function<? super EntityLocal, String>> UNIQUE_HOSTNAME_GENERATOR = ConfigKeys.newConfigKey(
-            new TypeToken<Function<? super EntityLocal, String>>() {},
-            "uniqueHostnameGenerator");
-
-    public static Builder<ZabbixFeed, ?> builder() {
-        return new ConcreteBuilder();
-    }
-
-    private static class ConcreteBuilder extends Builder<ZabbixFeed, ConcreteBuilder> {
-    }
-
-    public static class Builder<T extends ZabbixFeed, B extends Builder<T,B>> {
-        private EntityLocal entity;
-        private Supplier<URI> baseUriProvider;
-        private long period = 500;
-        private TimeUnit periodUnits = TimeUnit.MILLISECONDS;
-        private List<ZabbixPollConfig<?>> polls = Lists.newArrayList();
-        private URI baseUri;
-        private boolean suspended = false;
-        private volatile boolean built;
-        private ZabbixServer server;
-        private String username;
-        private String password;
-        private Integer sessionTimeout;
-        private Integer groupId;
-        private Integer templateId;
-        private Function<? super EntityLocal, String> uniqueHostnameGenerator = Functions.compose(
-                EntityFunctions.id(), 
-                EntityFunctions.locationMatching(Predicates.instanceOf(MachineLocation.class)));
-        private String uniqueTag;
-
-        @SuppressWarnings("unchecked")
-        protected B self() {
-           return (B) this;
-        }
-
-        public B entity(EntityLocal val) {
-            this.entity = val;
-            return self();
-        }
-        public B baseUri(Supplier<URI> val) {
-            if (baseUri!=null && val!=null)
-                throw new IllegalStateException("Builder cannot take both a URI and a URI Provider");
-            this.baseUriProvider = val;
-            return self();
-        }
-        public B baseUri(URI val) {
-            if (baseUriProvider!=null && val!=null)
-                throw new IllegalStateException("Builder cannot take both a URI and a URI Provider");
-            this.baseUri = val;
-            return self();
-        }
-        public B baseUrl(URL val) {
-            return baseUri(URI.create(val.toString()));
-        }
-        public B baseUri(String val) {
-            return baseUri(URI.create(val));
-        }
-        public B period(long millis) {
-            return period(millis, TimeUnit.MILLISECONDS);
-        }
-        public B period(long val, TimeUnit units) {
-            this.period = val;
-            this.periodUnits = units;
-            return self();
-        }
-        public B poll(ZabbixPollConfig<?> config) {
-            polls.add(config);
-            return self();
-        }
-        public B suspended() {
-            return suspended(true);
-        }
-        public B suspended(boolean startsSuspended) {
-            this.suspended = startsSuspended;
-            return self();
-        }
-
-        public B server(final ZabbixServer server) {
-            this.server = server;
-            baseUri(URI.create(server.getConfig(ZabbixServer.ZABBIX_SERVER_API_URL)));
-            username(server.getConfig(ZabbixServer.ZABBIX_SERVER_USERNAME));
-            password(server.getConfig(ZabbixServer.ZABBIX_SERVER_PASSWORD));
-            sessionTimeout(server.getConfig(ZabbixServer.ZABBIX_SESSION_TIMEOUT));
-            return self();
-        }
-        public B username(String username) {
-            this.username = username;
-            return self();
-        }
-        public B password(String password) {
-            this.password = password;
-            return self();
-        }
-        public B sessionTimeout(Integer sessionTimeout) {
-            this.sessionTimeout = sessionTimeout;
-            return self();
-        }
-        public B groupId(Integer groupId) {
-            this.groupId = groupId;
-            return self();
-        }
-        public B templateId(Integer templateId) {
-            this.templateId = templateId;
-            return self();
-        }
-        public B register(Integer groupId, Integer templateId) {
-            this.groupId = groupId;
-            this.templateId = templateId;
-            return self();
-        }
-        /**
-         * For generating the name to be used when registering the zabbix agent with the zabbix server.
-         * When called, guarantees that the entity will have a {@link MachineLocation} (see {@link Entity#getLocations()}).
-         * Must return a non-empty string that will be unique across all machines where zabbix agents are installed.
-         */
-        public B uniqueHostnameGenerator(Function<? super EntityLocal, String> val) {
-            this.uniqueHostnameGenerator = checkNotNull(val, "uniqueHostnameGenerator");
-            return self();
-        }
-        
-        public Builder uniqueTag(String uniqueTag) {
-            this.uniqueTag = uniqueTag;
-            return this;
-        }
-        
-        @SuppressWarnings("unchecked")
-        public T build() {
-            // If server not set and other config not available, try to obtain from entity config
-            if (server == null
-                    && (baseUri == null || baseUriProvider == null)
-                    && username == null && password == null && sessionTimeout == null) {
-                ZabbixServer server = Preconditions.checkNotNull(entity.getConfig(ZabbixMonitored.ZABBIX_SERVER), "The ZABBIX_SERVER config key must be set on the entity");
-                server(server);
-            }
-            // Now create feed
-            T result = (T) new ZabbixFeed(this);
-            result.setEntity(checkNotNull(entity, "entity"));
-            built = true;
-            if (suspended) result.suspend();
-            result.start();
-            return result;
-        }
-        @Override
-        protected void finalize() {
-            if (!built) log.warn("ZabbixFeed.Builder created, but build() never called");
-        }
-    }
-
-    protected static class ZabbixPollIdentifier {
-        final String itemName;
-
-        protected ZabbixPollIdentifier(String itemName) {
-            this.itemName = checkNotNull(itemName, "itemName");
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(itemName);
-        }
-
-        @Override
-        public boolean equals(Object other) {
-            if (!(other instanceof ZabbixPollIdentifier)) {
-                return false;
-            }
-            ZabbixPollIdentifier o = (ZabbixPollIdentifier) other;
-            return Objects.equal(itemName, o.itemName);
-        }
-    }
-
-    // Flag set when the Zabbix agent is registered for a host
-    protected final AtomicBoolean registered = new AtomicBoolean(false);
-
-    /**
-     * For rebind; do not call directly; use builder
-     */
-    public ZabbixFeed() {
-    }
-    
-    protected ZabbixFeed(final Builder<? extends ZabbixFeed, ?> builder) {
-        setConfig(BASE_URI_PROVIDER, builder.baseUriProvider);
-        if (builder.baseUri != null) {
-            if (builder.baseUriProvider != null) {
-                throw new IllegalStateException("Not permitted to supply baseUri and baseUriProvider");
-            }
-            setConfig(BASE_URI_PROVIDER, Suppliers.ofInstance(builder.baseUri));
-        } else {
-            setConfig(BASE_URI_PROVIDER, checkNotNull(builder.baseUriProvider, "baseUriProvider and baseUri"));
-        }
-
-        setConfig(GROUP_ID, checkNotNull(builder.groupId, "Zabbix groupId must be set"));
-        setConfig(TEMPLATE_ID, checkNotNull(builder.templateId, "Zabbix templateId must be set"));
-        setConfig(UNIQUE_HOSTNAME_GENERATOR, checkNotNull(builder.uniqueHostnameGenerator, "uniqueHostnameGenerator"));
-
-        Set<ZabbixPollConfig<?>> polls = Sets.newLinkedHashSet();
-        for (ZabbixPollConfig<?> config : builder.polls) {
-            @SuppressWarnings({ "unchecked", "rawtypes" })
-            ZabbixPollConfig<?> configCopy = new ZabbixPollConfig(config);
-            if (configCopy.getPeriod() < 0) configCopy.period(builder.period, builder.periodUnits);
-            polls.add(configCopy);
-        }
-        setConfig(POLLS, polls);
-        initUniqueTag(builder.uniqueTag, polls);
-    }
-
-    @Override
-    protected void preStart() {
-        final Supplier<URI> baseUriProvider = getConfig(BASE_URI_PROVIDER);
-        final Function<? super EntityLocal, String> uniqueHostnameGenerator = getConfig(UNIQUE_HOSTNAME_GENERATOR);
-        final Integer groupId = getConfig(GROUP_ID);
-        final Integer templateId = getConfig(TEMPLATE_ID);
-        final Set<ZabbixPollConfig<?>> polls = getConfig(POLLS);
-        
-        log.info("starting zabbix feed for {}", entity);
-
-        // TODO if supplier returns null, we may wish to defer initialization until url available?
-        // TODO for https should we really trust all?
-        final HttpClient httpClient = HttpTool.httpClientBuilder()
-                .trustAll()
-                .clientConnectionManager(new ThreadSafeClientConnManager())
-                .reuseStrategy(new NoConnectionReuseStrategy())
-                .uri(baseUriProvider.get())
-                .build();
-
-        // Registration job, calls Zabbix host.create API
-        final Callable<HttpToolResponse> registerJob = new Callable<HttpToolResponse>() {
-            @Override
-            public HttpToolResponse call() throws Exception {
-                if (!registered.get()) {
-                    // Find the first machine, if available
-                    Optional<Location> location = Iterables.tryFind(entity.getLocations(), Predicates.instanceOf(MachineLocation.class));
-                    if (!location.isPresent()) {
-                        return null; // Do nothing until location is present
-                    }
-                    MachineLocation machine = (MachineLocation) location.get();
-
-                    String host = uniqueHostnameGenerator.apply(entity);
-                    
-                    // Select address and port using port-forwarding if available
-                    String address = entity.getAttribute(Attributes.ADDRESS);
-                    Integer port = entity.getAttribute(ZabbixMonitored.ZABBIX_AGENT_PORT);
-                    if (machine instanceof SupportsPortForwarding) {
-                        Cidr management = entity.getConfig(BrooklynAccessUtils.MANAGEMENT_ACCESS_CIDR);
-                        HostAndPort forwarded = ((SupportsPortForwarding) machine).getSocketEndpointFor(management, port);
-                        address = forwarded.getHostText();
-                        port = forwarded.getPort();
-                    }
-
-                    // Fill in the JSON template and POST it
-                    byte[] body = JSON_HOST_CREATE
-                            .replace("{{token}}", entity.getConfig(ZabbixMonitored.ZABBIX_SERVER).getAttribute(ZabbixServer.ZABBIX_TOKEN))
-                            .replace("{{host}}", host)
-                            .replace("{{ip}}", address)
-                            .replace("{{port}}", Integer.toString(port))
-                            .replace("{{groupId}}", Integer.toString(groupId))
-                            .replace("{{templateId}}", Integer.toString(templateId))
-                            .replace("{{id}}", Integer.toString(id.incrementAndGet()))
-                            .getBytes();
-                    
-                    return HttpTool.httpPost(httpClient, baseUriProvider.get(), ImmutableMap.of("Content-Type", "application/json"), body);
-                }
-                return null;
-            }
-        };
-
-        // The handler for the registration job
-        PollHandler<? super HttpToolResponse> registrationHandler = new PollHandler<HttpToolResponse>() {
-            @Override
-            public void onSuccess(HttpToolResponse val) {
-                if (registered.get() || val == null) {
-                    return; // Skip if we are registered already or no data from job
-                }
-                JsonObject response = HttpValueFunctions.jsonContents().apply(val).getAsJsonObject();
-                if (response.has("error")) {
-                    // Parse the JSON error object and log the message
-                    JsonObject error = response.get("error").getAsJsonObject();
-                    String message = error.get("message").getAsString();
-                    String data = error.get("data").getAsString();
-                    log.warn("zabbix failed registering host - {}: {}", message, data);
-                } else if (response.has("result")) {
-                    // Parse the JSON result object and save the hostId
-                    JsonObject result = response.get("result").getAsJsonObject();
-                    String hostId = result.get("hostids").getAsJsonArray().get(0).getAsString();
-                    // Update the registered status if not set
-                    if (registered.compareAndSet(false, true)) {
-                        entity.setAttribute(ZabbixMonitored.ZABBIX_AGENT_HOSTID, hostId);
-                        log.info("zabbix registered host as id {}", hostId);
-                    }
-                } else {
-                    throw new IllegalStateException(String.format("zabbix host registration returned invalid result: %s", response.toString()));
-                }
-            }
-            @Override
-            public boolean checkSuccess(HttpToolResponse val) {
-                return (val.getResponseCode() == 200);
-            }
-            @Override
-            public void onFailure(HttpToolResponse val) {
-                log.warn("zabbix sever returned failure code: {}", val.getResponseCode());
-            }
-            @Override
-            public void onException(Exception exception) {
-                log.warn("zabbix exception registering host", exception);
-            }
-            @Override
-            public String toString() {
-                return super.toString()+"["+getDescription()+"]";
-            }
-            @Override
-            public String getDescription() {
-                return "Zabbix rest poll";
-            }
-        };
-
-        // Schedule registration attempt once per second
-        getPoller().scheduleAtFixedRate(registerJob, registrationHandler, 1000l); // TODO make configurable
-
-        // Create a polling job for each Zabbix metric
-        for (final ZabbixPollConfig<?> config : polls) {
-            Callable<HttpToolResponse> pollJob = new Callable<HttpToolResponse>() {
-                @Override
-                public HttpToolResponse call() throws Exception {
-                    if (registered.get()) {
-                        if (log.isTraceEnabled()) log.trace("zabbix polling {} for {}", entity, config);
-                        byte[] body = JSON_ITEM_GET
-                                .replace("{{token}}", entity.getConfig(ZabbixMonitored.ZABBIX_SERVER).getAttribute(ZabbixServer.ZABBIX_TOKEN))
-                                .replace("{{hostId}}", entity.getAttribute(ZabbixMonitored.ZABBIX_AGENT_HOSTID))
-                                .replace("{{itemKey}}", config.getItemKey())
-                                .replace("{{id}}", Integer.toString(id.incrementAndGet()))
-                                .getBytes();
-                        
-                        return HttpTool.httpPost(httpClient, baseUriProvider.get(), ImmutableMap.of("Content-Type", "application/json"), body);
-                    } else {
-                        throw new IllegalStateException("zabbix agent not yet registered");
-                    }
-                }
-            };
-
-            // Schedule the Zabbix polling job
-            AttributePollHandler<? super HttpToolResponse> pollHandler = new AttributePollHandler<HttpToolResponse>(config, entity, this);
-            long minPeriod = Integer.MAX_VALUE; // TODO make configurable
-            if (config.getPeriod() > 0) minPeriod = Math.min(minPeriod, config.getPeriod());
-            getPoller().scheduleAtFixedRate(pollJob, pollHandler, minPeriod);
-        }
-
-    }
-
-    @SuppressWarnings("unchecked")
-    protected Poller<HttpToolResponse> getPoller() {
-        return (Poller<HttpToolResponse>) super.getPoller();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixMonitored.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixMonitored.java b/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixMonitored.java
deleted file mode 100644
index fbaab5d..0000000
--- a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixMonitored.java
+++ /dev/null
@@ -1,38 +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.monitoring.zabbix;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensor;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
-import brooklyn.util.flags.SetFromFlag;
-
-public interface ZabbixMonitored {
-
-    /** The entity representing the Zabbix server monitoring an entity. */
-    @SetFromFlag("zabbixServer")
-    ConfigKey<ZabbixServer> ZABBIX_SERVER = new BasicConfigKey<ZabbixServer>(ZabbixServer.class, "zabbix.server.entity", "Zabbix server for this entity");
-
-    PortAttributeSensorAndConfigKey ZABBIX_AGENT_PORT = new PortAttributeSensorAndConfigKey("zabbix.agent.port", "The port the Zabbix agent is listening on", "10050+");
-
-    AttributeSensor<String> ZABBIX_AGENT_HOSTID = new BasicAttributeSensor<String>(String.class, "zabbix.agent.hostid", "The hostId for a Zabbix monitored agent");
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixPollConfig.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixPollConfig.java b/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixPollConfig.java
deleted file mode 100644
index 743df8b..0000000
--- a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixPollConfig.java
+++ /dev/null
@@ -1,75 +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.monitoring.zabbix;
-
-import javax.annotation.Nullable;
-
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.feed.PollConfig;
-import brooklyn.event.feed.http.HttpValueFunctions;
-import brooklyn.event.feed.http.JsonFunctions;
-import brooklyn.util.collections.MutableList;
-import brooklyn.util.http.HttpToolResponse;
-
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
-import com.google.gson.JsonElement;
-
-public class ZabbixPollConfig<T> extends PollConfig<HttpToolResponse, T, ZabbixPollConfig<T>> {
-
-    private String itemKey;
-
-    public ZabbixPollConfig(AttributeSensor<T> sensor) {
-        super(sensor);
-        // Add onSuccess method to extract the last value of the item
-        // FIXME Fix generics
-        onSuccess((Function)HttpValueFunctions.chain(
-                HttpValueFunctions.jsonContents(),
-                new Function<JsonElement, JsonElement>() {
-                    @Override
-                    public JsonElement apply(@Nullable JsonElement input) {
-                        Preconditions.checkNotNull(input, "JSON input");
-                        return input.getAsJsonObject().get("result")
-                                .getAsJsonArray().get(0)
-                                .getAsJsonObject().get("lastvalue");
-                    }
-                },
-                JsonFunctions.cast(getSensor().getType())));
-    }
-
-    public ZabbixPollConfig(ZabbixPollConfig<T> other) {
-        super(other);
-        this.itemKey = other.getItemKey();
-    }
-
-    public String getItemKey() {
-        return itemKey;
-    }
-
-    public ZabbixPollConfig<T> itemKey(String val) {
-        this.itemKey = val;
-        return this;
-    }
-
-    @Override
-    protected MutableList<Object> equalsFields() {
-        return super.equalsFields().appendIfNotNull(itemKey);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixServer.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixServer.java b/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixServer.java
deleted file mode 100644
index b5987c5..0000000
--- a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixServer.java
+++ /dev/null
@@ -1,52 +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.monitoring.zabbix;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Entity;
-import brooklyn.entity.proxying.ImplementedBy;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensor;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.util.flags.SetFromFlag;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-
-@ImplementedBy(ZabbixServerImpl.class)
-public interface ZabbixServer extends Entity {
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @SetFromFlag("filter")
-    ConfigKey<Predicate<? super Entity>> ENTITY_FILTER = new BasicConfigKey(Predicate.class, "zabbix.server.filter", "Filter for entities which will automatically be monitored", Predicates.instanceOf(ZabbixMonitored.class));
-
-    @SetFromFlag("serverApiUrl")
-    ConfigKey<String> ZABBIX_SERVER_API_URL = new BasicConfigKey<String>(String.class, "zabbix.server.apiUrl", "Main Zabbix server API URL");
-
-    @SetFromFlag("username")
-    ConfigKey<String> ZABBIX_SERVER_USERNAME = new BasicConfigKey<String>(String.class, "zabbix.server.username", "Zabbix server API login user");
-
-    @SetFromFlag("password")
-    ConfigKey<String> ZABBIX_SERVER_PASSWORD = new BasicConfigKey<String>(String.class, "zabbix.server.password", "Zabbix server API login password");
-
-    ConfigKey<Integer> ZABBIX_SESSION_TIMEOUT = new BasicConfigKey<Integer>(Integer.class, "zabbix.server.sessionTimeout", "Zabbix server API session timeout period (seconds)", 3600);
-
-    AttributeSensor<String> ZABBIX_TOKEN = new BasicAttributeSensor<String>(String.class, "zabbix.server.token", "Zabbix server API authentication token");
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixServerImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixServerImpl.java b/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixServerImpl.java
deleted file mode 100644
index 43d5f64..0000000
--- a/sandbox/monitoring/src/main/java/brooklyn/entity/monitoring/zabbix/ZabbixServerImpl.java
+++ /dev/null
@@ -1,143 +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.monitoring.zabbix;
-
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.AbstractEntity;
-import brooklyn.entity.basic.DynamicGroup;
-import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.entity.trait.Startable;
-import brooklyn.event.feed.http.HttpFeed;
-import brooklyn.event.feed.http.HttpPollConfig;
-import brooklyn.event.feed.http.HttpValueFunctions;
-import brooklyn.location.Location;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.policy.PolicySpec;
-
-import com.google.common.base.Functions;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Multimap;
-
-public class ZabbixServerImpl extends AbstractEntity implements ZabbixServer {
-
-    private static final Logger log = LoggerFactory.getLogger(ZabbixServerImpl.class);
-
-    private Object[] mutex = new Object[0];
-    private DynamicGroup monitoredEntities;
-    private AgentTrackingPolicy policy;
-    private Multimap<Location, Entity> entityLocations = HashMultimap.create();
-
-    private transient HttpFeed login;
-
-    @Override
-    public void init() {
-        super.init();
-        Predicate<? super Entity> filter = getConfig(ENTITY_FILTER);
-        monitoredEntities = addChild(EntitySpec.create(DynamicGroup.class)
-                .configure(DynamicGroup.ENTITY_FILTER, filter)
-                .displayName("agents"));
-    }
-
-    @Override
-    public void onManagementStarted() {
-        final byte[] jsonData = ZabbixFeed.JSON_USER_LOGIN
-                .replace("{{username}}", getConfig(ZABBIX_SERVER_USERNAME))
-                .replace("{{password}}", getConfig(ZABBIX_SERVER_PASSWORD))
-                .getBytes();
-        login = HttpFeed.builder()
-                .entity(this)
-                .baseUri(getConfig(ZABBIX_SERVER_API_URL))
-                .headers(ImmutableMap.of("Content-Type", "application/json"))
-                .poll(new HttpPollConfig<String>(ZABBIX_TOKEN)
-                        .method("POST")
-                        .body(jsonData)
-                        .onFailure(Functions.constant(""))
-                        .onSuccess(HttpValueFunctions.jsonContents("result", String.class)))
-                .build();
-
-        policy = addPolicy(PolicySpec.create(AgentTrackingPolicy.class)
-                .displayName("Zabbix Agent Tracker")
-                .configure("group", monitoredEntities));
-
-        for (Entity each : monitoredEntities.getMembers()) {
-            added(each);
-        }
-
-        setAttribute(Startable.SERVICE_UP, true);
-    }
-
-    public static class AgentTrackingPolicy extends AbstractMembershipTrackingPolicy {
-        @Override
-        protected void onEntityChange(Entity member) {
-            ((ZabbixServerImpl)entity).added(member); }
-        @Override
-        protected void onEntityAdded(Entity member) {
-        } // Ignore
-        @Override
-        protected void onEntityRemoved(Entity member) {
-            ((ZabbixServerImpl)entity).removed(member);
-        }
-    }
-    
-    public void added(Entity member) {
-        synchronized (mutex) {
-            Optional<Location> location = Iterables.tryFind(member.getLocations(), Predicates.instanceOf(SshMachineLocation.class));
-            if (location.isPresent() && member.getAttribute(Startable.SERVICE_UP)) {
-                SshMachineLocation machine = (SshMachineLocation) location.get();
-                if (!entityLocations.containsKey(machine)) {
-                    entityLocations.put(machine, member);
-                    // Configure the Zabbix agent
-                    List<String> commands = ImmutableList.<String>builder()
-                            .add("sed -i.bk 's/\\$HOSTNAME/" + machine.getDisplayName() + "/' /etc/zabbix/zabbix_agentd.conf")
-                            .add("zabbix_agentd")
-                            .build();
-                    int result = machine.execCommands("configuring zabbix_agentd", commands);
-                    if (result == 0) {
-                        log.info("zabbix_agentd configured on {} at {}", member, machine);
-                    } else {
-                        log.warn("failed to configure zabbix_agentd on {}, status {}", machine, result);
-                    }
-                }
-            } else {
-                log.warn("zabbix added({}) called but no location or service not started", member);
-            }
-        }
-    }
-
-    public void removed(Entity member) {
-        synchronized (mutex) {
-            for (Location location : member.getLocations()) {
-                entityLocations.remove(location, member);
-            }
-        }
-    }
-
-}


[6/6] incubator-brooklyn git commit: This closes #802

Posted by ha...@apache.org.
This closes #802


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

Branch: refs/heads/master
Commit: 7154cc9ec5d729641aef56857a76c2473317330f
Parents: fc42b17 070b5ca
Author: Hadrian Zbarcea <ha...@apache.org>
Authored: Thu Aug 6 19:48:10 2015 -0400
Committer: Hadrian Zbarcea <ha...@apache.org>
Committed: Thu Aug 6 19:48:10 2015 -0400

----------------------------------------------------------------------
 .../customsnitch/MultiCloudSnitch.java          | 222 ---------
 .../customsnitch/MultiCloudSnitch.java          | 222 +++++++++
 .../brooklyn/entity/database/Database.groovy    |  53 ---
 .../entity/database/derby/DerbyDatabase.java    | 172 -------
 .../database/derby/DerbyDatabaseDriver.java     |  25 -
 .../database/derby/DerbyDatabaseSshDriver.java  | 116 -----
 .../entity/database/derby/DerbySchema.java      | 147 ------
 .../brooklyn/entity/database/Database.groovy    |  53 +++
 .../entity/database/derby/DerbyDatabase.java    | 172 +++++++
 .../database/derby/DerbyDatabaseDriver.java     |  25 +
 .../database/derby/DerbyDatabaseSshDriver.java  | 116 +++++
 .../entity/database/derby/DerbySchema.java      | 148 ++++++
 .../entity/database/PlaceholderTest.java        |  26 --
 .../entity/database/PlaceholderTest.java        |  26 ++
 .../postgresql/PostgreSqlNodeSaltImpl.java      | 177 -------
 .../brooklyn/entity/salt/SaltBashCommands.java  |  92 ----
 .../java/brooklyn/entity/salt/SaltConfig.java   | 100 ----
 .../java/brooklyn/entity/salt/SaltConfigs.java  |  89 ----
 .../entity/salt/SaltLifecycleEffectorTasks.java | 220 ---------
 .../brooklyn/entity/salt/SaltStackMaster.java   |  70 ---
 .../entity/salt/SaltStackMasterDriver.java      |  25 -
 .../entity/salt/SaltStackMasterImpl.java        |  56 ---
 .../entity/salt/SaltStackMasterSshDriver.java   |  95 ----
 .../java/brooklyn/entity/salt/SaltTasks.java    | 145 ------
 .../postgresql/PostgreSqlNodeSaltImpl.java      | 179 +++++++
 .../brooklyn/entity/salt/SaltBashCommands.java  |  92 ++++
 .../apache/brooklyn/entity/salt/SaltConfig.java | 100 ++++
 .../brooklyn/entity/salt/SaltConfigs.java       |  89 ++++
 .../entity/salt/SaltLifecycleEffectorTasks.java | 220 +++++++++
 .../brooklyn/entity/salt/SaltStackMaster.java   |  70 +++
 .../entity/salt/SaltStackMasterDriver.java      |  25 +
 .../entity/salt/SaltStackMasterImpl.java        |  56 +++
 .../entity/salt/SaltStackMasterSshDriver.java   |  95 ++++
 .../apache/brooklyn/entity/salt/SaltTasks.java  | 145 ++++++
 .../main/resources/brooklyn/entity/salt/master  |  65 ---
 .../resources/brooklyn/entity/salt/masterless   |  53 ---
 .../main/resources/brooklyn/entity/salt/minion  |  52 ---
 .../org/apache/brooklyn/entity/salt/master      |  65 +++
 .../org/apache/brooklyn/entity/salt/masterless  |  53 +++
 .../org/apache/brooklyn/entity/salt/minion      |  52 +++
 .../postgresql/PostgreSqlSaltLiveTest.java      | 109 -----
 .../brooklyn/entity/salt/SaltConfigsTest.java   |  69 ---
 .../entity/salt/SaltLiveTestSupport.java        |  68 ---
 .../postgresql/PostgreSqlSaltLiveTest.java      | 112 +++++
 .../brooklyn/entity/salt/SaltConfigsTest.java   |  71 +++
 .../entity/salt/SaltLiveTestSupport.java        |  68 +++
 .../entity/monitoring/zabbix/ZabbixFeed.java    | 463 -------------------
 .../monitoring/zabbix/ZabbixMonitored.java      |  38 --
 .../monitoring/zabbix/ZabbixPollConfig.java     |  75 ---
 .../entity/monitoring/zabbix/ZabbixServer.java  |  52 ---
 .../monitoring/zabbix/ZabbixServerImpl.java     | 143 ------
 .../entity/monitoring/zabbix/ZabbixFeed.java    | 463 +++++++++++++++++++
 .../monitoring/zabbix/ZabbixMonitored.java      |  38 ++
 .../monitoring/zabbix/ZabbixPollConfig.java     |  75 +++
 .../entity/monitoring/zabbix/ZabbixServer.java  |  52 +++
 .../monitoring/zabbix/ZabbixServerImpl.java     | 143 ++++++
 sandbox/nosql/pom.xml                           |   2 +-
 .../nosql/hazelcast/HazelcastCluster.java       |  59 ---
 .../nosql/hazelcast/HazelcastClusterImpl.java   | 124 -----
 .../entity/nosql/hazelcast/HazelcastNode.java   |  97 ----
 .../nosql/hazelcast/HazelcastNodeDriver.java    |  25 -
 .../nosql/hazelcast/HazelcastNodeImpl.java      | 148 ------
 .../nosql/hazelcast/HazelcastNodeSshDriver.java | 159 -------
 .../nosql/infinispan/Infinispan5Driver.java     |  23 -
 .../nosql/infinispan/Infinispan5Server.java     |  88 ----
 .../nosql/infinispan/Infinispan5SshDriver.java  | 124 -----
 .../nosql/hazelcast/HazelcastCluster.java       |  59 +++
 .../nosql/hazelcast/HazelcastClusterImpl.java   | 124 +++++
 .../entity/nosql/hazelcast/HazelcastNode.java   |  97 ++++
 .../nosql/hazelcast/HazelcastNodeDriver.java    |  25 +
 .../nosql/hazelcast/HazelcastNodeImpl.java      | 148 ++++++
 .../nosql/hazelcast/HazelcastNodeSshDriver.java | 159 +++++++
 .../nosql/infinispan/Infinispan5Driver.java     |  23 +
 .../nosql/infinispan/Infinispan5Server.java     |  88 ++++
 .../nosql/infinispan/Infinispan5SshDriver.java  | 124 +++++
 .../nosql/hazelcast/hazelcast-brooklyn.xml      |  65 ---
 .../nosql/hazelcast/hazelcast-brooklyn.xml      |  65 +++
 .../hazelcast/HazelcastClusterEc2LiveTest.java  |  71 ---
 .../HazelcastClusterSoftlayerLiveTest.java      |  71 ---
 .../Infinispan5ServerIntegrationTest.groovy     | 102 ----
 .../hazelcast/HazelcastClusterEc2LiveTest.java  |  73 +++
 .../HazelcastClusterSoftlayerLiveTest.java      |  73 +++
 .../Infinispan5ServerIntegrationTest.groovy     | 103 +++++
 83 files changed, 4187 insertions(+), 4174 deletions(-)
----------------------------------------------------------------------



[2/6] incubator-brooklyn git commit: package rename to org.apache.brooklyn: sandbox

Posted by ha...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java
new file mode 100644
index 0000000..08fba87
--- /dev/null
+++ b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixFeed.java
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.monitoring.zabbix;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+import java.net.URL;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.http.client.HttpClient;
+import org.apache.http.impl.NoConnectionReuseStrategy;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EntityFunctions;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.event.feed.AbstractFeed;
+import brooklyn.event.feed.AttributePollHandler;
+import brooklyn.event.feed.PollHandler;
+import brooklyn.event.feed.Poller;
+import brooklyn.event.feed.http.HttpValueFunctions;
+import brooklyn.location.Location;
+import brooklyn.location.MachineLocation;
+import brooklyn.location.access.BrooklynAccessUtils;
+import brooklyn.location.basic.SupportsPortForwarding;
+import brooklyn.util.http.HttpTool;
+import brooklyn.util.http.HttpToolResponse;
+import brooklyn.util.net.Cidr;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicates;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.net.HostAndPort;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.JsonObject;
+
+public class ZabbixFeed extends AbstractFeed {
+
+    public static final Logger log = LoggerFactory.getLogger(ZabbixFeed.class);
+
+    public static final String JSON_ITEM_GET =
+            "{ \"jsonrpc\":\"2.0\",\"method\":\"item.get\"," +
+                "\"params\":{\"output\":\"extend\"," +
+                    "\"filter\":{\"hostid\":[\"{{hostId}}\"],\"key_\":\"{{itemKey}}\"}}," +
+                "\"auth\":\"{{token}}\",\"id\":{{id}}}";
+    public static final String JSON_USER_LOGIN =
+            "{ \"jsonrpc\":\"2.0\",\"method\":\"user.login\"," +
+                "\"params\":{\"user\":\"{{username}}\",\"password\":\"{{password}}\"}," +
+                "\"id\":0 }";
+    public static final String JSON_HOST_CREATE =
+            "{ \"jsonrpc\":\"2.0\",\"method\":\"host.create\"," +
+                "\"params\":{\"host\":\"{{host}}\"," +
+                    "\"interfaces\":[{\"type\":1,\"main\":1,\"useip\":1,\"ip\":\"{{ip}}\",\"dns\":\"\",\"port\":\"{{port}}\"}]," +
+                    "\"groups\":[{\"groupid\":\"{{groupId}}\"}]," +
+                    "\"templates\":[{\"templateid\":\"{{templateId}}\"}]}," +
+                "\"auth\":\"{{token}}\",\"id\":{{id}}}";
+
+    private static final AtomicInteger id = new AtomicInteger(0);
+
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Set<ZabbixPollConfig<?>>> POLLS = ConfigKeys.newConfigKey(
+            new TypeToken<Set<ZabbixPollConfig<?>>>() {},
+            "polls");
+
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Supplier<URI>> BASE_URI_PROVIDER = ConfigKeys.newConfigKey(
+            new TypeToken<Supplier<URI>>() {},
+            "baseUriProvider");
+    
+    public static final ConfigKey<Integer> GROUP_ID = ConfigKeys.newIntegerConfigKey("groupId");
+    
+    public static final ConfigKey<Integer> TEMPLATE_ID = ConfigKeys.newIntegerConfigKey("templateId");
+
+    @SuppressWarnings("serial")
+    public static final ConfigKey<Function<? super EntityLocal, String>> UNIQUE_HOSTNAME_GENERATOR = ConfigKeys.newConfigKey(
+            new TypeToken<Function<? super EntityLocal, String>>() {},
+            "uniqueHostnameGenerator");
+
+    public static Builder<ZabbixFeed, ?> builder() {
+        return new ConcreteBuilder();
+    }
+
+    private static class ConcreteBuilder extends Builder<ZabbixFeed, ConcreteBuilder> {
+    }
+
+    public static class Builder<T extends ZabbixFeed, B extends Builder<T,B>> {
+        private EntityLocal entity;
+        private Supplier<URI> baseUriProvider;
+        private long period = 500;
+        private TimeUnit periodUnits = TimeUnit.MILLISECONDS;
+        private List<ZabbixPollConfig<?>> polls = Lists.newArrayList();
+        private URI baseUri;
+        private boolean suspended = false;
+        private volatile boolean built;
+        private ZabbixServer server;
+        private String username;
+        private String password;
+        private Integer sessionTimeout;
+        private Integer groupId;
+        private Integer templateId;
+        private Function<? super EntityLocal, String> uniqueHostnameGenerator = Functions.compose(
+                EntityFunctions.id(), 
+                EntityFunctions.locationMatching(Predicates.instanceOf(MachineLocation.class)));
+        private String uniqueTag;
+
+        @SuppressWarnings("unchecked")
+        protected B self() {
+           return (B) this;
+        }
+
+        public B entity(EntityLocal val) {
+            this.entity = val;
+            return self();
+        }
+        public B baseUri(Supplier<URI> val) {
+            if (baseUri!=null && val!=null)
+                throw new IllegalStateException("Builder cannot take both a URI and a URI Provider");
+            this.baseUriProvider = val;
+            return self();
+        }
+        public B baseUri(URI val) {
+            if (baseUriProvider!=null && val!=null)
+                throw new IllegalStateException("Builder cannot take both a URI and a URI Provider");
+            this.baseUri = val;
+            return self();
+        }
+        public B baseUrl(URL val) {
+            return baseUri(URI.create(val.toString()));
+        }
+        public B baseUri(String val) {
+            return baseUri(URI.create(val));
+        }
+        public B period(long millis) {
+            return period(millis, TimeUnit.MILLISECONDS);
+        }
+        public B period(long val, TimeUnit units) {
+            this.period = val;
+            this.periodUnits = units;
+            return self();
+        }
+        public B poll(ZabbixPollConfig<?> config) {
+            polls.add(config);
+            return self();
+        }
+        public B suspended() {
+            return suspended(true);
+        }
+        public B suspended(boolean startsSuspended) {
+            this.suspended = startsSuspended;
+            return self();
+        }
+
+        public B server(final ZabbixServer server) {
+            this.server = server;
+            baseUri(URI.create(server.getConfig(ZabbixServer.ZABBIX_SERVER_API_URL)));
+            username(server.getConfig(ZabbixServer.ZABBIX_SERVER_USERNAME));
+            password(server.getConfig(ZabbixServer.ZABBIX_SERVER_PASSWORD));
+            sessionTimeout(server.getConfig(ZabbixServer.ZABBIX_SESSION_TIMEOUT));
+            return self();
+        }
+        public B username(String username) {
+            this.username = username;
+            return self();
+        }
+        public B password(String password) {
+            this.password = password;
+            return self();
+        }
+        public B sessionTimeout(Integer sessionTimeout) {
+            this.sessionTimeout = sessionTimeout;
+            return self();
+        }
+        public B groupId(Integer groupId) {
+            this.groupId = groupId;
+            return self();
+        }
+        public B templateId(Integer templateId) {
+            this.templateId = templateId;
+            return self();
+        }
+        public B register(Integer groupId, Integer templateId) {
+            this.groupId = groupId;
+            this.templateId = templateId;
+            return self();
+        }
+        /**
+         * For generating the name to be used when registering the zabbix agent with the zabbix server.
+         * When called, guarantees that the entity will have a {@link MachineLocation} (see {@link Entity#getLocations()}).
+         * Must return a non-empty string that will be unique across all machines where zabbix agents are installed.
+         */
+        public B uniqueHostnameGenerator(Function<? super EntityLocal, String> val) {
+            this.uniqueHostnameGenerator = checkNotNull(val, "uniqueHostnameGenerator");
+            return self();
+        }
+        
+        public Builder uniqueTag(String uniqueTag) {
+            this.uniqueTag = uniqueTag;
+            return this;
+        }
+        
+        @SuppressWarnings("unchecked")
+        public T build() {
+            // If server not set and other config not available, try to obtain from entity config
+            if (server == null
+                    && (baseUri == null || baseUriProvider == null)
+                    && username == null && password == null && sessionTimeout == null) {
+                ZabbixServer server = Preconditions.checkNotNull(entity.getConfig(ZabbixMonitored.ZABBIX_SERVER), "The ZABBIX_SERVER config key must be set on the entity");
+                server(server);
+            }
+            // Now create feed
+            T result = (T) new ZabbixFeed(this);
+            result.setEntity(checkNotNull(entity, "entity"));
+            built = true;
+            if (suspended) result.suspend();
+            result.start();
+            return result;
+        }
+        @Override
+        protected void finalize() {
+            if (!built) log.warn("ZabbixFeed.Builder created, but build() never called");
+        }
+    }
+
+    protected static class ZabbixPollIdentifier {
+        final String itemName;
+
+        protected ZabbixPollIdentifier(String itemName) {
+            this.itemName = checkNotNull(itemName, "itemName");
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hashCode(itemName);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof ZabbixPollIdentifier)) {
+                return false;
+            }
+            ZabbixPollIdentifier o = (ZabbixPollIdentifier) other;
+            return Objects.equal(itemName, o.itemName);
+        }
+    }
+
+    // Flag set when the Zabbix agent is registered for a host
+    protected final AtomicBoolean registered = new AtomicBoolean(false);
+
+    /**
+     * For rebind; do not call directly; use builder
+     */
+    public ZabbixFeed() {
+    }
+    
+    protected ZabbixFeed(final Builder<? extends ZabbixFeed, ?> builder) {
+        setConfig(BASE_URI_PROVIDER, builder.baseUriProvider);
+        if (builder.baseUri != null) {
+            if (builder.baseUriProvider != null) {
+                throw new IllegalStateException("Not permitted to supply baseUri and baseUriProvider");
+            }
+            setConfig(BASE_URI_PROVIDER, Suppliers.ofInstance(builder.baseUri));
+        } else {
+            setConfig(BASE_URI_PROVIDER, checkNotNull(builder.baseUriProvider, "baseUriProvider and baseUri"));
+        }
+
+        setConfig(GROUP_ID, checkNotNull(builder.groupId, "Zabbix groupId must be set"));
+        setConfig(TEMPLATE_ID, checkNotNull(builder.templateId, "Zabbix templateId must be set"));
+        setConfig(UNIQUE_HOSTNAME_GENERATOR, checkNotNull(builder.uniqueHostnameGenerator, "uniqueHostnameGenerator"));
+
+        Set<ZabbixPollConfig<?>> polls = Sets.newLinkedHashSet();
+        for (ZabbixPollConfig<?> config : builder.polls) {
+            @SuppressWarnings({ "unchecked", "rawtypes" })
+            ZabbixPollConfig<?> configCopy = new ZabbixPollConfig(config);
+            if (configCopy.getPeriod() < 0) configCopy.period(builder.period, builder.periodUnits);
+            polls.add(configCopy);
+        }
+        setConfig(POLLS, polls);
+        initUniqueTag(builder.uniqueTag, polls);
+    }
+
+    @Override
+    protected void preStart() {
+        final Supplier<URI> baseUriProvider = getConfig(BASE_URI_PROVIDER);
+        final Function<? super EntityLocal, String> uniqueHostnameGenerator = getConfig(UNIQUE_HOSTNAME_GENERATOR);
+        final Integer groupId = getConfig(GROUP_ID);
+        final Integer templateId = getConfig(TEMPLATE_ID);
+        final Set<ZabbixPollConfig<?>> polls = getConfig(POLLS);
+        
+        log.info("starting zabbix feed for {}", entity);
+
+        // TODO if supplier returns null, we may wish to defer initialization until url available?
+        // TODO for https should we really trust all?
+        final HttpClient httpClient = HttpTool.httpClientBuilder()
+                .trustAll()
+                .clientConnectionManager(new ThreadSafeClientConnManager())
+                .reuseStrategy(new NoConnectionReuseStrategy())
+                .uri(baseUriProvider.get())
+                .build();
+
+        // Registration job, calls Zabbix host.create API
+        final Callable<HttpToolResponse> registerJob = new Callable<HttpToolResponse>() {
+            @Override
+            public HttpToolResponse call() throws Exception {
+                if (!registered.get()) {
+                    // Find the first machine, if available
+                    Optional<Location> location = Iterables.tryFind(entity.getLocations(), Predicates.instanceOf(MachineLocation.class));
+                    if (!location.isPresent()) {
+                        return null; // Do nothing until location is present
+                    }
+                    MachineLocation machine = (MachineLocation) location.get();
+
+                    String host = uniqueHostnameGenerator.apply(entity);
+                    
+                    // Select address and port using port-forwarding if available
+                    String address = entity.getAttribute(Attributes.ADDRESS);
+                    Integer port = entity.getAttribute(ZabbixMonitored.ZABBIX_AGENT_PORT);
+                    if (machine instanceof SupportsPortForwarding) {
+                        Cidr management = entity.getConfig(BrooklynAccessUtils.MANAGEMENT_ACCESS_CIDR);
+                        HostAndPort forwarded = ((SupportsPortForwarding) machine).getSocketEndpointFor(management, port);
+                        address = forwarded.getHostText();
+                        port = forwarded.getPort();
+                    }
+
+                    // Fill in the JSON template and POST it
+                    byte[] body = JSON_HOST_CREATE
+                            .replace("{{token}}", entity.getConfig(ZabbixMonitored.ZABBIX_SERVER).getAttribute(ZabbixServer.ZABBIX_TOKEN))
+                            .replace("{{host}}", host)
+                            .replace("{{ip}}", address)
+                            .replace("{{port}}", Integer.toString(port))
+                            .replace("{{groupId}}", Integer.toString(groupId))
+                            .replace("{{templateId}}", Integer.toString(templateId))
+                            .replace("{{id}}", Integer.toString(id.incrementAndGet()))
+                            .getBytes();
+                    
+                    return HttpTool.httpPost(httpClient, baseUriProvider.get(), ImmutableMap.of("Content-Type", "application/json"), body);
+                }
+                return null;
+            }
+        };
+
+        // The handler for the registration job
+        PollHandler<? super HttpToolResponse> registrationHandler = new PollHandler<HttpToolResponse>() {
+            @Override
+            public void onSuccess(HttpToolResponse val) {
+                if (registered.get() || val == null) {
+                    return; // Skip if we are registered already or no data from job
+                }
+                JsonObject response = HttpValueFunctions.jsonContents().apply(val).getAsJsonObject();
+                if (response.has("error")) {
+                    // Parse the JSON error object and log the message
+                    JsonObject error = response.get("error").getAsJsonObject();
+                    String message = error.get("message").getAsString();
+                    String data = error.get("data").getAsString();
+                    log.warn("zabbix failed registering host - {}: {}", message, data);
+                } else if (response.has("result")) {
+                    // Parse the JSON result object and save the hostId
+                    JsonObject result = response.get("result").getAsJsonObject();
+                    String hostId = result.get("hostids").getAsJsonArray().get(0).getAsString();
+                    // Update the registered status if not set
+                    if (registered.compareAndSet(false, true)) {
+                        entity.setAttribute(ZabbixMonitored.ZABBIX_AGENT_HOSTID, hostId);
+                        log.info("zabbix registered host as id {}", hostId);
+                    }
+                } else {
+                    throw new IllegalStateException(String.format("zabbix host registration returned invalid result: %s", response.toString()));
+                }
+            }
+            @Override
+            public boolean checkSuccess(HttpToolResponse val) {
+                return (val.getResponseCode() == 200);
+            }
+            @Override
+            public void onFailure(HttpToolResponse val) {
+                log.warn("zabbix sever returned failure code: {}", val.getResponseCode());
+            }
+            @Override
+            public void onException(Exception exception) {
+                log.warn("zabbix exception registering host", exception);
+            }
+            @Override
+            public String toString() {
+                return super.toString()+"["+getDescription()+"]";
+            }
+            @Override
+            public String getDescription() {
+                return "Zabbix rest poll";
+            }
+        };
+
+        // Schedule registration attempt once per second
+        getPoller().scheduleAtFixedRate(registerJob, registrationHandler, 1000l); // TODO make configurable
+
+        // Create a polling job for each Zabbix metric
+        for (final ZabbixPollConfig<?> config : polls) {
+            Callable<HttpToolResponse> pollJob = new Callable<HttpToolResponse>() {
+                @Override
+                public HttpToolResponse call() throws Exception {
+                    if (registered.get()) {
+                        if (log.isTraceEnabled()) log.trace("zabbix polling {} for {}", entity, config);
+                        byte[] body = JSON_ITEM_GET
+                                .replace("{{token}}", entity.getConfig(ZabbixMonitored.ZABBIX_SERVER).getAttribute(ZabbixServer.ZABBIX_TOKEN))
+                                .replace("{{hostId}}", entity.getAttribute(ZabbixMonitored.ZABBIX_AGENT_HOSTID))
+                                .replace("{{itemKey}}", config.getItemKey())
+                                .replace("{{id}}", Integer.toString(id.incrementAndGet()))
+                                .getBytes();
+                        
+                        return HttpTool.httpPost(httpClient, baseUriProvider.get(), ImmutableMap.of("Content-Type", "application/json"), body);
+                    } else {
+                        throw new IllegalStateException("zabbix agent not yet registered");
+                    }
+                }
+            };
+
+            // Schedule the Zabbix polling job
+            AttributePollHandler<? super HttpToolResponse> pollHandler = new AttributePollHandler<HttpToolResponse>(config, entity, this);
+            long minPeriod = Integer.MAX_VALUE; // TODO make configurable
+            if (config.getPeriod() > 0) minPeriod = Math.min(minPeriod, config.getPeriod());
+            getPoller().scheduleAtFixedRate(pollJob, pollHandler, minPeriod);
+        }
+
+    }
+
+    @SuppressWarnings("unchecked")
+    protected Poller<HttpToolResponse> getPoller() {
+        return (Poller<HttpToolResponse>) super.getPoller();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixMonitored.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixMonitored.java b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixMonitored.java
new file mode 100644
index 0000000..78e9dfd
--- /dev/null
+++ b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixMonitored.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.monitoring.zabbix;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.util.flags.SetFromFlag;
+
+public interface ZabbixMonitored {
+
+    /** The entity representing the Zabbix server monitoring an entity. */
+    @SetFromFlag("zabbixServer")
+    ConfigKey<ZabbixServer> ZABBIX_SERVER = new BasicConfigKey<ZabbixServer>(ZabbixServer.class, "zabbix.server.entity", "Zabbix server for this entity");
+
+    PortAttributeSensorAndConfigKey ZABBIX_AGENT_PORT = new PortAttributeSensorAndConfigKey("zabbix.agent.port", "The port the Zabbix agent is listening on", "10050+");
+
+    AttributeSensor<String> ZABBIX_AGENT_HOSTID = new BasicAttributeSensor<String>(String.class, "zabbix.agent.hostid", "The hostId for a Zabbix monitored agent");
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixPollConfig.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixPollConfig.java b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixPollConfig.java
new file mode 100644
index 0000000..a215133
--- /dev/null
+++ b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixPollConfig.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.monitoring.zabbix;
+
+import javax.annotation.Nullable;
+
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.feed.PollConfig;
+import brooklyn.event.feed.http.HttpValueFunctions;
+import brooklyn.event.feed.http.JsonFunctions;
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.http.HttpToolResponse;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.gson.JsonElement;
+
+public class ZabbixPollConfig<T> extends PollConfig<HttpToolResponse, T, ZabbixPollConfig<T>> {
+
+    private String itemKey;
+
+    public ZabbixPollConfig(AttributeSensor<T> sensor) {
+        super(sensor);
+        // Add onSuccess method to extract the last value of the item
+        // FIXME Fix generics
+        onSuccess((Function)HttpValueFunctions.chain(
+                HttpValueFunctions.jsonContents(),
+                new Function<JsonElement, JsonElement>() {
+                    @Override
+                    public JsonElement apply(@Nullable JsonElement input) {
+                        Preconditions.checkNotNull(input, "JSON input");
+                        return input.getAsJsonObject().get("result")
+                                .getAsJsonArray().get(0)
+                                .getAsJsonObject().get("lastvalue");
+                    }
+                },
+                JsonFunctions.cast(getSensor().getType())));
+    }
+
+    public ZabbixPollConfig(ZabbixPollConfig<T> other) {
+        super(other);
+        this.itemKey = other.getItemKey();
+    }
+
+    public String getItemKey() {
+        return itemKey;
+    }
+
+    public ZabbixPollConfig<T> itemKey(String val) {
+        this.itemKey = val;
+        return this;
+    }
+
+    @Override
+    protected MutableList<Object> equalsFields() {
+        return super.equalsFields().appendIfNotNull(itemKey);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixServer.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixServer.java b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixServer.java
new file mode 100644
index 0000000..db287e8
--- /dev/null
+++ b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixServer.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.monitoring.zabbix;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+@ImplementedBy(ZabbixServerImpl.class)
+public interface ZabbixServer extends Entity {
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @SetFromFlag("filter")
+    ConfigKey<Predicate<? super Entity>> ENTITY_FILTER = new BasicConfigKey(Predicate.class, "zabbix.server.filter", "Filter for entities which will automatically be monitored", Predicates.instanceOf(ZabbixMonitored.class));
+
+    @SetFromFlag("serverApiUrl")
+    ConfigKey<String> ZABBIX_SERVER_API_URL = new BasicConfigKey<String>(String.class, "zabbix.server.apiUrl", "Main Zabbix server API URL");
+
+    @SetFromFlag("username")
+    ConfigKey<String> ZABBIX_SERVER_USERNAME = new BasicConfigKey<String>(String.class, "zabbix.server.username", "Zabbix server API login user");
+
+    @SetFromFlag("password")
+    ConfigKey<String> ZABBIX_SERVER_PASSWORD = new BasicConfigKey<String>(String.class, "zabbix.server.password", "Zabbix server API login password");
+
+    ConfigKey<Integer> ZABBIX_SESSION_TIMEOUT = new BasicConfigKey<Integer>(Integer.class, "zabbix.server.sessionTimeout", "Zabbix server API session timeout period (seconds)", 3600);
+
+    AttributeSensor<String> ZABBIX_TOKEN = new BasicAttributeSensor<String>(String.class, "zabbix.server.token", "Zabbix server API authentication token");
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixServerImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixServerImpl.java b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixServerImpl.java
new file mode 100644
index 0000000..9ad8ac0
--- /dev/null
+++ b/sandbox/monitoring/src/main/java/org/apache/brooklyn/entity/monitoring/zabbix/ZabbixServerImpl.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.monitoring.zabbix;
+
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.trait.Startable;
+import brooklyn.event.feed.http.HttpFeed;
+import brooklyn.event.feed.http.HttpPollConfig;
+import brooklyn.event.feed.http.HttpValueFunctions;
+import brooklyn.location.Location;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.policy.PolicySpec;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+
+public class ZabbixServerImpl extends AbstractEntity implements ZabbixServer {
+
+    private static final Logger log = LoggerFactory.getLogger(ZabbixServerImpl.class);
+
+    private Object[] mutex = new Object[0];
+    private DynamicGroup monitoredEntities;
+    private AgentTrackingPolicy policy;
+    private Multimap<Location, Entity> entityLocations = HashMultimap.create();
+
+    private transient HttpFeed login;
+
+    @Override
+    public void init() {
+        super.init();
+        Predicate<? super Entity> filter = getConfig(ENTITY_FILTER);
+        monitoredEntities = addChild(EntitySpec.create(DynamicGroup.class)
+                .configure(DynamicGroup.ENTITY_FILTER, filter)
+                .displayName("agents"));
+    }
+
+    @Override
+    public void onManagementStarted() {
+        final byte[] jsonData = ZabbixFeed.JSON_USER_LOGIN
+                .replace("{{username}}", getConfig(ZABBIX_SERVER_USERNAME))
+                .replace("{{password}}", getConfig(ZABBIX_SERVER_PASSWORD))
+                .getBytes();
+        login = HttpFeed.builder()
+                .entity(this)
+                .baseUri(getConfig(ZABBIX_SERVER_API_URL))
+                .headers(ImmutableMap.of("Content-Type", "application/json"))
+                .poll(new HttpPollConfig<String>(ZABBIX_TOKEN)
+                        .method("POST")
+                        .body(jsonData)
+                        .onFailure(Functions.constant(""))
+                        .onSuccess(HttpValueFunctions.jsonContents("result", String.class)))
+                .build();
+
+        policy = addPolicy(PolicySpec.create(AgentTrackingPolicy.class)
+                .displayName("Zabbix Agent Tracker")
+                .configure("group", monitoredEntities));
+
+        for (Entity each : monitoredEntities.getMembers()) {
+            added(each);
+        }
+
+        setAttribute(Startable.SERVICE_UP, true);
+    }
+
+    public static class AgentTrackingPolicy extends AbstractMembershipTrackingPolicy {
+        @Override
+        protected void onEntityChange(Entity member) {
+            ((ZabbixServerImpl)entity).added(member); }
+        @Override
+        protected void onEntityAdded(Entity member) {
+        } // Ignore
+        @Override
+        protected void onEntityRemoved(Entity member) {
+            ((ZabbixServerImpl)entity).removed(member);
+        }
+    }
+    
+    public void added(Entity member) {
+        synchronized (mutex) {
+            Optional<Location> location = Iterables.tryFind(member.getLocations(), Predicates.instanceOf(SshMachineLocation.class));
+            if (location.isPresent() && member.getAttribute(Startable.SERVICE_UP)) {
+                SshMachineLocation machine = (SshMachineLocation) location.get();
+                if (!entityLocations.containsKey(machine)) {
+                    entityLocations.put(machine, member);
+                    // Configure the Zabbix agent
+                    List<String> commands = ImmutableList.<String>builder()
+                            .add("sed -i.bk 's/\\$HOSTNAME/" + machine.getDisplayName() + "/' /etc/zabbix/zabbix_agentd.conf")
+                            .add("zabbix_agentd")
+                            .build();
+                    int result = machine.execCommands("configuring zabbix_agentd", commands);
+                    if (result == 0) {
+                        log.info("zabbix_agentd configured on {} at {}", member, machine);
+                    } else {
+                        log.warn("failed to configure zabbix_agentd on {}, status {}", machine, result);
+                    }
+                }
+            } else {
+                log.warn("zabbix added({}) called but no location or service not started", member);
+            }
+        }
+    }
+
+    public void removed(Entity member) {
+        synchronized (mutex) {
+            for (Location location : member.getLocations()) {
+                entityLocations.remove(location, member);
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/pom.xml
----------------------------------------------------------------------
diff --git a/sandbox/nosql/pom.xml b/sandbox/nosql/pom.xml
index af576d0..ccf1487 100644
--- a/sandbox/nosql/pom.xml
+++ b/sandbox/nosql/pom.xml
@@ -101,7 +101,7 @@
                             the given components. These are files "without any degree of creativity" from the
                             perspective of the Brooklyn/Apache contribution.
                         -->
-                        <exclude>src/main/resources/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml</exclude>
+                        <exclude>src/main/resources/org/apache/brooklyn/entity/nosql/hazelcast/hazelcast-brooklyn.xml</exclude>
                     </excludes>
                 </configuration>
             </plugin>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastCluster.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastCluster.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastCluster.java
deleted file mode 100644
index c91b78c..0000000
--- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastCluster.java
+++ /dev/null
@@ -1,59 +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.nosql.hazelcast;
-
-import java.util.List;
-
-import com.google.common.reflect.TypeToken;
-
-import org.apache.brooklyn.catalog.Catalog;
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.group.DynamicCluster;
-import brooklyn.entity.proxying.ImplementedBy;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
-import brooklyn.event.basic.Sensors;
-import brooklyn.util.flags.SetFromFlag;
-
-/**
- * A cluster of {@link HazelcastNode}s based on {@link DynamicCluster}.
- */
-@Catalog(name="Hazelcast Cluster", description="Hazelcast is a clustering and highly scalable data distribution platform for Java.")
-
-@ImplementedBy(HazelcastClusterImpl.class)
-public interface HazelcastCluster extends DynamicCluster {
-
-    @SetFromFlag("clusterName")
-    BasicAttributeSensorAndConfigKey<String> CLUSTER_NAME = new BasicAttributeSensorAndConfigKey<String>(String.class, 
-            "hazelcast.cluster.name", "Name of the Hazelcast cluster", "HazelcastCluster");
-    
-    @SetFromFlag("clusterPassword")
-    ConfigKey<String> CLUSTER_PASSWORD =
-            ConfigKeys.newStringConfigKey("hazelcast.cluster.password", "Hazelcast cluster password.");
-    
-    @SuppressWarnings("serial")
-    AttributeSensor<List<String>> PUBLIC_CLUSTER_NODES = Sensors.newSensor(new TypeToken<List<String>>() {},
-        "hazelcast.cluster.public.nodes", "List of public addresses of all nodes in the cluster");
-    
-    String getClusterName();
-    
-    String getClusterPassword();
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterImpl.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterImpl.java
deleted file mode 100644
index e911318..0000000
--- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastClusterImpl.java
+++ /dev/null
@@ -1,124 +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.nosql.hazelcast;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.collect.Lists;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
-import brooklyn.entity.group.DynamicClusterImpl;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.location.Location;
-import brooklyn.policy.PolicySpec;
-import brooklyn.util.text.Strings;
-
-public class HazelcastClusterImpl extends DynamicClusterImpl implements HazelcastCluster {
-    private static final Logger LOG = LoggerFactory.getLogger(HazelcastClusterImpl.class);
-    
-    private static final AtomicInteger nextMemberId = new AtomicInteger(0);
-    
-    @Override
-    protected EntitySpec<?> getMemberSpec() {
-        EntitySpec<?> spec = EntitySpec.create(getConfig(MEMBER_SPEC, EntitySpec.create(HazelcastNode.class)));
-        
-        spec.configure(HazelcastNode.GROUP_NAME, getConfig(HazelcastClusterImpl.CLUSTER_NAME));
-        
-        if (LOG.isInfoEnabled()) {
-            LOG.info("Cluster name : {} : used as a group name", getConfig(HazelcastNode.GROUP_NAME));
-        }
-        
-        spec.configure(HazelcastNode.GROUP_PASSWORD, getClusterPassword());
-        
-        return spec;
-    }
-  
-    @Override
-    public void init() {
-        super.init();
-
-        String clusterPassword = getClusterPassword();
-        
-        if (Strings.isBlank(clusterPassword)) {
-            if (LOG.isInfoEnabled()) {
-                LOG.info(this + " cluster password not provided for " + CLUSTER_PASSWORD.getName() + " : generating random password");
-            }
-            setConfig(CLUSTER_PASSWORD, Strings.makeRandomId(12));
-        }
-        
-        addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
-                .displayName("Hazelcast members tracker")
-                .configure("group", this));
-    }
-    
-    public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy {
-        @Override
-        protected void onEntityChange(Entity member) {
-        }
-
-        @Override
-        protected void onEntityAdded(Entity member) {
-            if (member.getAttribute(HazelcastNode.NODE_NAME) == null) {
-                ((EntityInternal) member).setAttribute(HazelcastNode.NODE_NAME, "hazelcast-" + nextMemberId.incrementAndGet());
-                if (LOG.isInfoEnabled()) {
-                    LOG.info("Node {} added to the cluster", member);
-                }
-            }
-        }
-
-        @Override
-        protected void onEntityRemoved(Entity member) {
-        }
-    };
-    
-    @Override
-    public String getClusterName() {
-        return getConfig(CLUSTER_NAME);
-    }
-
-    @Override
-    public String getClusterPassword() {
-        return getConfig(CLUSTER_PASSWORD);
-    }
-
-    @Override
-    protected void initEnrichers() {
-        super.initEnrichers();
-        
-    }
-    
-    @Override
-    public void start(Collection<? extends Location> locations) {
-        super.start(locations);
-        
-        
-        List<String> clusterNodes = Lists.newArrayList();
-        for (Entity member : getMembers()) {
-            clusterNodes.add(member.getAttribute(Attributes.ADDRESS));
-        }
-        setAttribute(PUBLIC_CLUSTER_NODES, clusterNodes);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNode.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNode.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNode.java
deleted file mode 100644
index 40aa330..0000000
--- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNode.java
+++ /dev/null
@@ -1,97 +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.nosql.hazelcast;
-
-import org.apache.brooklyn.catalog.Catalog;
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.java.UsesJava;
-import brooklyn.entity.java.UsesJmx;
-import brooklyn.entity.proxying.ImplementedBy;
-import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
-import brooklyn.event.basic.BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey;
-import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
-import brooklyn.location.basic.PortRanges;
-import brooklyn.util.flags.SetFromFlag;
-import brooklyn.util.javalang.JavaClassNames;
-
-/**
- * An {@link brooklyn.entity.Entity} that represents an Hazelcast node
- */
-@Catalog(name="Hazelcast Node", description="Hazelcast is a clustering and highly scalable data distribution platform for Java.")
-
-@ImplementedBy(HazelcastNodeImpl.class)
-public interface HazelcastNode extends SoftwareProcess, UsesJava, UsesJmx {
-    @SetFromFlag("version")
-    ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "3.4.2");
-    
-    @SetFromFlag("downloadUrl")
-    BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>(
-            SoftwareProcess.DOWNLOAD_URL, "https://repo1.maven.org/maven2/com/hazelcast/hazelcast/${version}/hazelcast-${version}.jar");
-    
-    @SetFromFlag("configTemplateUrl")
-    ConfigKey<String> CONFIG_TEMPLATE_URL = ConfigKeys.newStringConfigKey(
-            "hazelcast.node.config.templateUrl", "Template file (in freemarker format) for the Hazelcat config file", 
-            JavaClassNames.resolveClasspathUrl(HazelcastNode.class, "hazelcast-brooklyn.xml"));
-    
-    @SetFromFlag("configFileName")
-    ConfigKey<String> CONFIG_FILE_NAME = ConfigKeys.newStringConfigKey(
-            "hazelcast.node.config.fileName", "Name of the Hazelcast config file", "hazelcast.xml");    
-    
-    @SetFromFlag("nodeName")
-    StringAttributeSensorAndConfigKey NODE_NAME = new StringAttributeSensorAndConfigKey("hazelcast.node.name", 
-            "Node name (or randomly selected if not set", null);
-
-    @SetFromFlag("nodeHeapMemorySize")
-    ConfigKey<String> NODE_HEAP_MEMORY_SIZE = ConfigKeys.newStringConfigKey(
-            "hazelcast.node.heap.memory.size", "Node's heap memory size (-Xmx and -Xms) in megabytes. Default: 256m", "256m");
-    
-    @SetFromFlag("nodePort")
-    PortAttributeSensorAndConfigKey NODE_PORT = new PortAttributeSensorAndConfigKey("hazelcast.node.port", "Hazelcast communication port", PortRanges.fromString("5701+"));
-
-    /**
-     * Specifies the group name in the configuration file. Each Hazelcast cluster has a separate group.
-     */ 
-    @SetFromFlag("groupName")
-    ConfigKey<String> GROUP_NAME = ConfigKeys.newStringConfigKey("hazelcast.group.name", 
-            "Group name", "brooklyn");
-  
-    @SetFromFlag("groupPassword")
-    ConfigKey<String> GROUP_PASSWORD = ConfigKeys.newStringConfigKey("hazelcast.group.password", 
-            "Group password", "brooklyn");
-    
-    String getNodeName();
-    
-    Integer getNodePort();
-    
-    String getGroupName();
-    
-    String getGroupPassword();
-    
-    String getHostname();
-    
-    String getHostAddress();
-
-    String getPrivateIpAddress();
-
-    String getListenAddress();
-    
-    String getHeapMemorySize();
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeDriver.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeDriver.java
deleted file mode 100644
index 4e53add..0000000
--- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeDriver.java
+++ /dev/null
@@ -1,25 +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.nosql.hazelcast;
-
-import brooklyn.entity.basic.SoftwareProcessDriver;
-
-public interface HazelcastNodeDriver extends SoftwareProcessDriver {
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeImpl.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeImpl.java
deleted file mode 100644
index 0369934..0000000
--- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeImpl.java
+++ /dev/null
@@ -1,148 +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.nosql.hazelcast;
-
-import java.net.URI;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.SoftwareProcessImpl;
-import brooklyn.event.feed.http.HttpFeed;
-import brooklyn.event.feed.http.HttpPollConfig;
-import brooklyn.event.feed.http.HttpValueFunctions;
-import brooklyn.location.access.BrooklynAccessUtils;
-import brooklyn.util.text.Strings;
-
-import com.google.common.base.Functions;
-import com.google.common.net.HostAndPort;
-
-public class HazelcastNodeImpl extends SoftwareProcessImpl implements HazelcastNode {
-    
-    private static final Logger LOG = LoggerFactory.getLogger(HazelcastNodeImpl.class);
-    
-    HttpFeed httpFeed;
-
-    @Override
-    public Class<HazelcastNodeDriver> getDriverInterface() {
-        return HazelcastNodeDriver.class;
-    }
-    
-    @Override
-    protected void connectSensors() {
-        super.connectSensors();
-
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Connecting sensors for node: {} ", getAttribute(Attributes.HOSTNAME));
-        }
-        
-        HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, getNodePort());
-
-        String nodeUri = String.format("http://%s:%d/hazelcast/rest/cluster", hp.getHostText(), hp.getPort());
-        setAttribute(Attributes.MAIN_URI, URI.create(nodeUri));
-
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Node {} is using {} as a main URI", this, nodeUri);
-        }
-        
-        httpFeed = HttpFeed.builder()
-                .entity(this)
-                .period(3000, TimeUnit.MILLISECONDS)
-                .baseUri(nodeUri)
-                .poll(new HttpPollConfig<Boolean>(SERVICE_UP)
-                        .onSuccess(HttpValueFunctions.responseCodeEquals(200))
-                        .onFailureOrException(Functions.constant(false)))
-                .build();
-    }
-    
-    @Override
-    protected void disconnectSensors() {
-        if (httpFeed != null) {
-            httpFeed.stop();
-        }
-
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("Disconnecting sensors for node: {} ", getAttribute(Attributes.HOSTNAME));
-        }
-        
-        super.disconnectSensors();
-        disconnectServiceUpIsRunning();
-    }
-
-
-    @Override
-    public String getGroupName() {
-        return getConfig(HazelcastNode.GROUP_NAME);
-    }
-
-    @Override
-    public String getGroupPassword() {
-        return getConfig(HazelcastNode.GROUP_PASSWORD);
-    }
-
-    @Override
-    public String getNodeName() {
-        return getAttribute(HazelcastNode.NODE_NAME);
-    }
-
-    @Override
-    public Integer getNodePort() {
-        return getAttribute(HazelcastNode.NODE_PORT);
-    }
-
-    @Override
-    public String getHostname() { 
-        return getAttribute(HOSTNAME); 
-    }
-    
-    @Override
-    public String getHostAddress() { 
-        return getAttribute(ADDRESS); 
-    }
-    
-    @Override
-    public String getPrivateIpAddress() {
-        return getAttribute(SUBNET_ADDRESS);
-    }
-    
-    @Override
-    public String getListenAddress() {
-        String listenAddress = getPrivateIpAddress();
-        
-        if (Strings.isBlank(listenAddress)) {
-            listenAddress = getAttribute(ADDRESS);
-        }
-        
-        if (LOG.isInfoEnabled()) {
-            LOG.info("Node {} is listening on {}", this, listenAddress);
-        }
-
-         
-        return listenAddress;
-    }
-
-
-    @Override
-    public String getHeapMemorySize() {
-        return getConfig(HazelcastNode.NODE_HEAP_MEMORY_SIZE);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeSshDriver.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeSshDriver.java
deleted file mode 100644
index 5527a69..0000000
--- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/hazelcast/HazelcastNodeSshDriver.java
+++ /dev/null
@@ -1,159 +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.nosql.hazelcast;
-
-import static java.lang.String.format;
-
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.EntityLocal;
-import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.os.Os;
-import brooklyn.util.ssh.BashCommands;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-public class HazelcastNodeSshDriver extends JavaSoftwareProcessSshDriver implements HazelcastNodeDriver {
-    
-    private static final Logger LOG = LoggerFactory.getLogger(HazelcastNodeSshDriver.class);
-
-    public HazelcastNodeSshDriver(EntityLocal entity, SshMachineLocation machine) {
-        super(entity, machine);
-    }
-
-    @Override
-    public void preInstall() {
-        resolver = Entities.newDownloader(this);
-    }
-
-    @Override
-    public void install() {
-        List<String> urls = resolver.getTargets();
-        String saveAs = resolver.getFilename();
-        
-        List<String> commands = ImmutableList.<String>builder()
-            .add(BashCommands.installJavaLatestOrWarn())
-            .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs))
-            .build();
-        
-        newScript(INSTALLING).body.append(commands).execute();
-    }
-
-    @Override
-    public void customize() {
-        if (LOG.isInfoEnabled()) {
-            LOG.info("Customizing {}", entity.getAttribute(HazelcastNode.NODE_NAME));
-        }
-        
-        ImmutableList.Builder<String> commands = new ImmutableList.Builder<String>()
-                .add("mkdir -p lib conf log")
-                .add(String.format("cp %s/%s %s/lib/", getInstallDir(), resolver.getFilename(), getRunDir()));
-
-        newScript(CUSTOMIZING)
-                .body.append(commands.build())
-                .failOnNonZeroResultCode()
-                .execute();
-        
-        copyTemplate(entity.getConfig(HazelcastNode.CONFIG_TEMPLATE_URL), Os.mergePathsUnix(getRunDir(), "conf", getConfigFileName()));
-        
-    }
-
-    @Override
-    public void launch() {
-        
-        entity.setAttribute(HazelcastNode.PID_FILE, Os.mergePathsUnix(getRunDir(), PID_FILENAME));
-        
-        String maxHeapMemorySize = getHeapMemorySize();
-        
-        if (LOG.isInfoEnabled()) {
-            LOG.info("Launching {} with heap memory of {}", entity, maxHeapMemorySize);
-        }
-        
-        // Setting initial heap size (Xms) size to match max heap size (Xms) at first
-        String initialHeapMemorySize = maxHeapMemorySize;
-        
-        StringBuilder commandBuilder = new StringBuilder()
-            .append(format("nohup java -cp ./lib/%s", resolver.getFilename()))
-            .append(format(" -Xmx%s -Xms%s", maxHeapMemorySize, initialHeapMemorySize))
-            .append(format(" -Dhazelcast.config=./conf/%s", getConfigFileName()))
-            .append(format(" com.hazelcast.core.server.StartServer >> %s 2>&1 </dev/null &", getLogFileLocation()));
-        
-        newScript(MutableMap.of(USE_PID_FILE, true), LAUNCHING)
-            .updateTaskAndFailOnNonZeroResultCode()
-            .body.append(commandBuilder.toString())
-            .execute();
-    }
-       
-    public String getConfigFileName() {
-        return entity.getConfig(HazelcastNode.CONFIG_FILE_NAME);
-    }
-    
-    public String getHeapMemorySize() {
-        return entity.getConfig(HazelcastNode.NODE_HEAP_MEMORY_SIZE);
-    }
-    
-    @Override
-    public boolean isRunning() {       
-        return newScript(MutableMap.of(USE_PID_FILE, true), CHECK_RUNNING).execute() == 0;
-    }
-    
-    @Override
-    public void stop() {
-        newScript(MutableMap.of(USE_PID_FILE, true), STOPPING).execute();
-    }
-    
-    @Override
-    public void kill() {
-        newScript(MutableMap.of(USE_PID_FILE, true), KILLING).execute();
-    }
-
-    public List<String> getHazelcastNodesList() throws ExecutionException, InterruptedException {
-        HazelcastCluster cluster = (HazelcastCluster) entity.getParent();
-        List<String> result = Lists.newArrayList();
-
-        for (Entity member : cluster.getMembers()) {
-            String address = Entities.attributeSupplierWhenReady(member, HazelcastNode.SUBNET_ADDRESS).get();
-            Integer port = Entities.attributeSupplierWhenReady(member, HazelcastNode.NODE_PORT).get();
-            
-            String addressAndPort = String.format("%s:%d", address, port);
-            
-            if (LOG.isInfoEnabled()) {
-                LOG.info("Adding {} to the members' list of {}", addressAndPort, entity.getAttribute(HazelcastNode.NODE_NAME));
-            }
-            result.add(addressAndPort);
-        }
-        
-        return result;
-    }
-
-    @Override
-    protected String getLogFileLocation() {
-        return Os.mergePathsUnix(getRunDir(),"/log/out.log");
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5Driver.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5Driver.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5Driver.java
deleted file mode 100644
index ebd5fb8..0000000
--- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5Driver.java
+++ /dev/null
@@ -1,23 +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.nosql.infinispan;
-
-public interface Infinispan5Driver {
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5Server.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5Server.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5Server.java
deleted file mode 100644
index e029cf1..0000000
--- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5Server.java
+++ /dev/null
@@ -1,88 +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.nosql.infinispan;
-
-import java.util.Map;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.basic.SoftwareProcessImpl;
-import brooklyn.entity.java.UsesJmx;
-import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
-import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.flags.SetFromFlag;
-
-/**
- * An {@link brooklyn.entity.Entity} that represents an Infinispan service
- */
-public class Infinispan5Server extends SoftwareProcessImpl implements UsesJmx {
-    private static final Logger log = LoggerFactory.getLogger(Infinispan5Server.class);
-    
-    public static final BasicAttributeSensorAndConfigKey<String> PROTOCOL = new BasicAttributeSensorAndConfigKey<String>(
-            String.class, "infinispan.server.protocol", 
-            "Infinispan protocol (e.g. memcached, hotrod, or websocket)", "memcached");
-    
-    public static final PortAttributeSensorAndConfigKey PORT = new PortAttributeSensorAndConfigKey(
-            "infinispan.server.port", "TCP port number to listen on");
-
-    @SetFromFlag("version")
-    public static final ConfigKey<String> SUGGESTED_VERSION =
-            ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "5.0.0.CR8");
-
-    // Default filename is "infinispan-${version}-all.zip"
-    @SetFromFlag("downloadUrl")
-    public static final BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>(
-            SoftwareProcess.DOWNLOAD_URL, "http://sourceforge.net/projects/infinispan/files/infinispan/${version}/infinispan-${version}-all.zip/download");
-
-    public Infinispan5Server() {
-        this(MutableMap.of(), null);
-    }
-    public Infinispan5Server(Map properties) {
-        this(properties, null);
-    }
-    public Infinispan5Server(Entity parent) {
-        this(MutableMap.of(), parent);
-    }
-    public Infinispan5Server(Map properties, Entity parent) {
-        super(properties, parent);
-    }
-
-    @Override
-    public Class getDriverInterface() {
-        return Infinispan5Driver.class;
-    }
-
-    @Override
-    protected void connectSensors() {
-        super.connectSensors();
-        super.connectServiceUpIsRunning();
-    }
-    
-    @Override
-    protected void disconnectSensors() {
-        super.disconnectServiceUpIsRunning();
-        super.disconnectSensors();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java b/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java
deleted file mode 100644
index 361a6ab..0000000
--- a/sandbox/nosql/src/main/java/brooklyn/entity/nosql/infinispan/Infinispan5SshDriver.java
+++ /dev/null
@@ -1,124 +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.nosql.infinispan;
-
-import static java.lang.String.format;
-
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
-import brooklyn.location.Location;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.net.Networking;
-import brooklyn.util.ssh.BashCommands;
-
-import com.google.common.collect.ImmutableList;
-
-/**
- * Start a {@link TomcatServer} in a {@link Location} accessible over ssh.
- */
-public class Infinispan5SshDriver extends JavaSoftwareProcessSshDriver implements Infinispan5Driver {
-
-    public Infinispan5SshDriver(Infinispan5Server entity, SshMachineLocation machine) {
-        super(entity, machine);
-    }
-
-    @Override
-    protected String getLogFileLocation() {
-        throw new UnsupportedOperationException("Work in progress");
-    }
-
-    protected String getProtocol() {
-        return entity.getAttribute(Infinispan5Server.PROTOCOL);
-    }
-
-    protected Integer getPort() {
-        return entity.getAttribute(Infinispan5Server.PORT);
-    }
-
-    @Override
-    public void install() {
-        List<String> urls = resolver.getTargets();
-        String saveAs = resolver.getFilename();
-
-        List<String> commands = ImmutableList.<String>builder()
-                .addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs))
-                .add(BashCommands.INSTALL_ZIP)
-                .add("unzip " + saveAs)
-                .build();
-
-        newScript(INSTALLING).
-                failOnNonZeroResultCode().
-                body.append(commands).execute();
-    }
-
-    @Override
-    public void customize() {
-        // TODO create and reference a conf.xml? And start with --cache_config <path>
-        Map ports = MutableMap.of("port", getPort(), "jmxPort", getJmxPort());
-        Networking.checkPortsValid(ports);
-
-        newScript(CUSTOMIZING)
-                .body.append()
-                .execute();
-    }
-
-    @Override
-    public void launch() {
-        // FIXME Do we want to redirect stdout/stderr: >> %s/console 2>&1 </dev/null &", getRunDir())
-        newScript(MutableMap.of("usePidFile", true), LAUNCHING).
-                body.append(
-                        format("%s/bin/startServer.sh --protocol %s "
-                                +(getPort() != null ? " --port %s" : "")+" &", 
-                                getExpandedInstallDir(), getProtocol(), getPort()))
-                .execute();
-    }
-
-
-    @Override
-    public boolean isRunning() {
-        Map flags = MutableMap.of("usePidFile", true);
-        return newScript(flags, CHECK_RUNNING).execute() == 0;
-    }
-    
-    @Override
-    public void stop() {
-        Map flags = MutableMap.of("usePidFile", true);
-        newScript(flags, STOPPING).execute();
-    }
-
-    @Override
-    public void kill() {
-        Map flags = MutableMap.of("usePidFile", true);
-        newScript(flags, KILLING).execute();
-    }
-
-    @Override
-    protected List<String> getCustomJavaConfigOptions() {
-        List<String> options = new LinkedList<String>();
-        options.addAll(super.getCustomJavaConfigOptions());
-        options.add("-Xms200m");
-        options.add("-Xmx800m");
-        options.add("-XX:MaxPermSize=400m");
-        return options;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastCluster.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastCluster.java b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastCluster.java
new file mode 100644
index 0000000..ef4894f
--- /dev/null
+++ b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastCluster.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.hazelcast;
+
+import java.util.List;
+
+import com.google.common.reflect.TypeToken;
+
+import org.apache.brooklyn.catalog.Catalog;
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.group.DynamicCluster;
+import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.event.basic.Sensors;
+import brooklyn.util.flags.SetFromFlag;
+
+/**
+ * A cluster of {@link HazelcastNode}s based on {@link DynamicCluster}.
+ */
+@Catalog(name="Hazelcast Cluster", description="Hazelcast is a clustering and highly scalable data distribution platform for Java.")
+
+@ImplementedBy(HazelcastClusterImpl.class)
+public interface HazelcastCluster extends DynamicCluster {
+
+    @SetFromFlag("clusterName")
+    BasicAttributeSensorAndConfigKey<String> CLUSTER_NAME = new BasicAttributeSensorAndConfigKey<String>(String.class, 
+            "hazelcast.cluster.name", "Name of the Hazelcast cluster", "HazelcastCluster");
+    
+    @SetFromFlag("clusterPassword")
+    ConfigKey<String> CLUSTER_PASSWORD =
+            ConfigKeys.newStringConfigKey("hazelcast.cluster.password", "Hazelcast cluster password.");
+    
+    @SuppressWarnings("serial")
+    AttributeSensor<List<String>> PUBLIC_CLUSTER_NODES = Sensors.newSensor(new TypeToken<List<String>>() {},
+        "hazelcast.cluster.public.nodes", "List of public addresses of all nodes in the cluster");
+    
+    String getClusterName();
+    
+    String getClusterPassword();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterImpl.java b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterImpl.java
new file mode 100644
index 0000000..4f837d7
--- /dev/null
+++ b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastClusterImpl.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.hazelcast;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.group.AbstractMembershipTrackingPolicy;
+import brooklyn.entity.group.DynamicClusterImpl;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.policy.PolicySpec;
+import brooklyn.util.text.Strings;
+
+public class HazelcastClusterImpl extends DynamicClusterImpl implements HazelcastCluster {
+    private static final Logger LOG = LoggerFactory.getLogger(HazelcastClusterImpl.class);
+    
+    private static final AtomicInteger nextMemberId = new AtomicInteger(0);
+    
+    @Override
+    protected EntitySpec<?> getMemberSpec() {
+        EntitySpec<?> spec = EntitySpec.create(getConfig(MEMBER_SPEC, EntitySpec.create(HazelcastNode.class)));
+        
+        spec.configure(HazelcastNode.GROUP_NAME, getConfig(HazelcastClusterImpl.CLUSTER_NAME));
+        
+        if (LOG.isInfoEnabled()) {
+            LOG.info("Cluster name : {} : used as a group name", getConfig(HazelcastNode.GROUP_NAME));
+        }
+        
+        spec.configure(HazelcastNode.GROUP_PASSWORD, getClusterPassword());
+        
+        return spec;
+    }
+  
+    @Override
+    public void init() {
+        super.init();
+
+        String clusterPassword = getClusterPassword();
+        
+        if (Strings.isBlank(clusterPassword)) {
+            if (LOG.isInfoEnabled()) {
+                LOG.info(this + " cluster password not provided for " + CLUSTER_PASSWORD.getName() + " : generating random password");
+            }
+            setConfig(CLUSTER_PASSWORD, Strings.makeRandomId(12));
+        }
+        
+        addPolicy(PolicySpec.create(MemberTrackingPolicy.class)
+                .displayName("Hazelcast members tracker")
+                .configure("group", this));
+    }
+    
+    public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy {
+        @Override
+        protected void onEntityChange(Entity member) {
+        }
+
+        @Override
+        protected void onEntityAdded(Entity member) {
+            if (member.getAttribute(HazelcastNode.NODE_NAME) == null) {
+                ((EntityInternal) member).setAttribute(HazelcastNode.NODE_NAME, "hazelcast-" + nextMemberId.incrementAndGet());
+                if (LOG.isInfoEnabled()) {
+                    LOG.info("Node {} added to the cluster", member);
+                }
+            }
+        }
+
+        @Override
+        protected void onEntityRemoved(Entity member) {
+        }
+    };
+    
+    @Override
+    public String getClusterName() {
+        return getConfig(CLUSTER_NAME);
+    }
+
+    @Override
+    public String getClusterPassword() {
+        return getConfig(CLUSTER_PASSWORD);
+    }
+
+    @Override
+    protected void initEnrichers() {
+        super.initEnrichers();
+        
+    }
+    
+    @Override
+    public void start(Collection<? extends Location> locations) {
+        super.start(locations);
+        
+        
+        List<String> clusterNodes = Lists.newArrayList();
+        for (Entity member : getMembers()) {
+            clusterNodes.add(member.getAttribute(Attributes.ADDRESS));
+        }
+        setAttribute(PUBLIC_CLUSTER_NODES, clusterNodes);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNode.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNode.java b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNode.java
new file mode 100644
index 0000000..8f3521c
--- /dev/null
+++ b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNode.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.hazelcast;
+
+import org.apache.brooklyn.catalog.Catalog;
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.java.UsesJava;
+import brooklyn.entity.java.UsesJmx;
+import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.location.basic.PortRanges;
+import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.javalang.JavaClassNames;
+
+/**
+ * An {@link brooklyn.entity.Entity} that represents an Hazelcast node
+ */
+@Catalog(name="Hazelcast Node", description="Hazelcast is a clustering and highly scalable data distribution platform for Java.")
+
+@ImplementedBy(HazelcastNodeImpl.class)
+public interface HazelcastNode extends SoftwareProcess, UsesJava, UsesJmx {
+    @SetFromFlag("version")
+    ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "3.4.2");
+    
+    @SetFromFlag("downloadUrl")
+    BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>(
+            SoftwareProcess.DOWNLOAD_URL, "https://repo1.maven.org/maven2/com/hazelcast/hazelcast/${version}/hazelcast-${version}.jar");
+    
+    @SetFromFlag("configTemplateUrl")
+    ConfigKey<String> CONFIG_TEMPLATE_URL = ConfigKeys.newStringConfigKey(
+            "hazelcast.node.config.templateUrl", "Template file (in freemarker format) for the Hazelcat config file", 
+            JavaClassNames.resolveClasspathUrl(HazelcastNode.class, "hazelcast-brooklyn.xml"));
+    
+    @SetFromFlag("configFileName")
+    ConfigKey<String> CONFIG_FILE_NAME = ConfigKeys.newStringConfigKey(
+            "hazelcast.node.config.fileName", "Name of the Hazelcast config file", "hazelcast.xml");    
+    
+    @SetFromFlag("nodeName")
+    StringAttributeSensorAndConfigKey NODE_NAME = new StringAttributeSensorAndConfigKey("hazelcast.node.name", 
+            "Node name (or randomly selected if not set", null);
+
+    @SetFromFlag("nodeHeapMemorySize")
+    ConfigKey<String> NODE_HEAP_MEMORY_SIZE = ConfigKeys.newStringConfigKey(
+            "hazelcast.node.heap.memory.size", "Node's heap memory size (-Xmx and -Xms) in megabytes. Default: 256m", "256m");
+    
+    @SetFromFlag("nodePort")
+    PortAttributeSensorAndConfigKey NODE_PORT = new PortAttributeSensorAndConfigKey("hazelcast.node.port", "Hazelcast communication port", PortRanges.fromString("5701+"));
+
+    /**
+     * Specifies the group name in the configuration file. Each Hazelcast cluster has a separate group.
+     */ 
+    @SetFromFlag("groupName")
+    ConfigKey<String> GROUP_NAME = ConfigKeys.newStringConfigKey("hazelcast.group.name", 
+            "Group name", "brooklyn");
+  
+    @SetFromFlag("groupPassword")
+    ConfigKey<String> GROUP_PASSWORD = ConfigKeys.newStringConfigKey("hazelcast.group.password", 
+            "Group password", "brooklyn");
+    
+    String getNodeName();
+    
+    Integer getNodePort();
+    
+    String getGroupName();
+    
+    String getGroupPassword();
+    
+    String getHostname();
+    
+    String getHostAddress();
+
+    String getPrivateIpAddress();
+
+    String getListenAddress();
+    
+    String getHeapMemorySize();
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeDriver.java b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeDriver.java
new file mode 100644
index 0000000..e5aa759
--- /dev/null
+++ b/sandbox/nosql/src/main/java/org/apache/brooklyn/entity/nosql/hazelcast/HazelcastNodeDriver.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.hazelcast;
+
+import brooklyn.entity.basic.SoftwareProcessDriver;
+
+public interface HazelcastNodeDriver extends SoftwareProcessDriver {
+
+}



[5/6] incubator-brooklyn git commit: package rename to org.apache.brooklyn: sandbox

Posted by ha...@apache.org.
package rename to org.apache.brooklyn: sandbox


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

Branch: refs/heads/master
Commit: 070b5ca7c85ec8819c2208c5fa7802af783edf1e
Parents: 08662a7
Author: Aled Sage <al...@gmail.com>
Authored: Thu Aug 6 11:43:58 2015 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Thu Aug 6 17:17:54 2015 +0100

----------------------------------------------------------------------
 .../customsnitch/MultiCloudSnitch.java          | 222 ---------
 .../customsnitch/MultiCloudSnitch.java          | 222 +++++++++
 .../brooklyn/entity/database/Database.groovy    |  53 ---
 .../entity/database/derby/DerbyDatabase.java    | 172 -------
 .../database/derby/DerbyDatabaseDriver.java     |  25 -
 .../database/derby/DerbyDatabaseSshDriver.java  | 116 -----
 .../entity/database/derby/DerbySchema.java      | 147 ------
 .../brooklyn/entity/database/Database.groovy    |  53 +++
 .../entity/database/derby/DerbyDatabase.java    | 172 +++++++
 .../database/derby/DerbyDatabaseDriver.java     |  25 +
 .../database/derby/DerbyDatabaseSshDriver.java  | 116 +++++
 .../entity/database/derby/DerbySchema.java      | 148 ++++++
 .../entity/database/PlaceholderTest.java        |  26 --
 .../entity/database/PlaceholderTest.java        |  26 ++
 .../postgresql/PostgreSqlNodeSaltImpl.java      | 177 -------
 .../brooklyn/entity/salt/SaltBashCommands.java  |  92 ----
 .../java/brooklyn/entity/salt/SaltConfig.java   | 100 ----
 .../java/brooklyn/entity/salt/SaltConfigs.java  |  89 ----
 .../entity/salt/SaltLifecycleEffectorTasks.java | 220 ---------
 .../brooklyn/entity/salt/SaltStackMaster.java   |  70 ---
 .../entity/salt/SaltStackMasterDriver.java      |  25 -
 .../entity/salt/SaltStackMasterImpl.java        |  56 ---
 .../entity/salt/SaltStackMasterSshDriver.java   |  95 ----
 .../java/brooklyn/entity/salt/SaltTasks.java    | 145 ------
 .../postgresql/PostgreSqlNodeSaltImpl.java      | 179 +++++++
 .../brooklyn/entity/salt/SaltBashCommands.java  |  92 ++++
 .../apache/brooklyn/entity/salt/SaltConfig.java | 100 ++++
 .../brooklyn/entity/salt/SaltConfigs.java       |  89 ++++
 .../entity/salt/SaltLifecycleEffectorTasks.java | 220 +++++++++
 .../brooklyn/entity/salt/SaltStackMaster.java   |  70 +++
 .../entity/salt/SaltStackMasterDriver.java      |  25 +
 .../entity/salt/SaltStackMasterImpl.java        |  56 +++
 .../entity/salt/SaltStackMasterSshDriver.java   |  95 ++++
 .../apache/brooklyn/entity/salt/SaltTasks.java  | 145 ++++++
 .../main/resources/brooklyn/entity/salt/master  |  65 ---
 .../resources/brooklyn/entity/salt/masterless   |  53 ---
 .../main/resources/brooklyn/entity/salt/minion  |  52 ---
 .../org/apache/brooklyn/entity/salt/master      |  65 +++
 .../org/apache/brooklyn/entity/salt/masterless  |  53 +++
 .../org/apache/brooklyn/entity/salt/minion      |  52 +++
 .../postgresql/PostgreSqlSaltLiveTest.java      | 109 -----
 .../brooklyn/entity/salt/SaltConfigsTest.java   |  69 ---
 .../entity/salt/SaltLiveTestSupport.java        |  68 ---
 .../postgresql/PostgreSqlSaltLiveTest.java      | 112 +++++
 .../brooklyn/entity/salt/SaltConfigsTest.java   |  71 +++
 .../entity/salt/SaltLiveTestSupport.java        |  68 +++
 .../entity/monitoring/zabbix/ZabbixFeed.java    | 463 -------------------
 .../monitoring/zabbix/ZabbixMonitored.java      |  38 --
 .../monitoring/zabbix/ZabbixPollConfig.java     |  75 ---
 .../entity/monitoring/zabbix/ZabbixServer.java  |  52 ---
 .../monitoring/zabbix/ZabbixServerImpl.java     | 143 ------
 .../entity/monitoring/zabbix/ZabbixFeed.java    | 463 +++++++++++++++++++
 .../monitoring/zabbix/ZabbixMonitored.java      |  38 ++
 .../monitoring/zabbix/ZabbixPollConfig.java     |  75 +++
 .../entity/monitoring/zabbix/ZabbixServer.java  |  52 +++
 .../monitoring/zabbix/ZabbixServerImpl.java     | 143 ++++++
 sandbox/nosql/pom.xml                           |   2 +-
 .../nosql/hazelcast/HazelcastCluster.java       |  59 ---
 .../nosql/hazelcast/HazelcastClusterImpl.java   | 124 -----
 .../entity/nosql/hazelcast/HazelcastNode.java   |  97 ----
 .../nosql/hazelcast/HazelcastNodeDriver.java    |  25 -
 .../nosql/hazelcast/HazelcastNodeImpl.java      | 148 ------
 .../nosql/hazelcast/HazelcastNodeSshDriver.java | 159 -------
 .../nosql/infinispan/Infinispan5Driver.java     |  23 -
 .../nosql/infinispan/Infinispan5Server.java     |  88 ----
 .../nosql/infinispan/Infinispan5SshDriver.java  | 124 -----
 .../nosql/hazelcast/HazelcastCluster.java       |  59 +++
 .../nosql/hazelcast/HazelcastClusterImpl.java   | 124 +++++
 .../entity/nosql/hazelcast/HazelcastNode.java   |  97 ++++
 .../nosql/hazelcast/HazelcastNodeDriver.java    |  25 +
 .../nosql/hazelcast/HazelcastNodeImpl.java      | 148 ++++++
 .../nosql/hazelcast/HazelcastNodeSshDriver.java | 159 +++++++
 .../nosql/infinispan/Infinispan5Driver.java     |  23 +
 .../nosql/infinispan/Infinispan5Server.java     |  88 ++++
 .../nosql/infinispan/Infinispan5SshDriver.java  | 124 +++++
 .../nosql/hazelcast/hazelcast-brooklyn.xml      |  65 ---
 .../nosql/hazelcast/hazelcast-brooklyn.xml      |  65 +++
 .../hazelcast/HazelcastClusterEc2LiveTest.java  |  71 ---
 .../HazelcastClusterSoftlayerLiveTest.java      |  71 ---
 .../Infinispan5ServerIntegrationTest.groovy     | 102 ----
 .../hazelcast/HazelcastClusterEc2LiveTest.java  |  73 +++
 .../HazelcastClusterSoftlayerLiveTest.java      |  73 +++
 .../Infinispan5ServerIntegrationTest.groovy     | 103 +++++
 83 files changed, 4187 insertions(+), 4174 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/cassandra-multicloud-snitch/src/main/java/brooklyn/entity/nosql/cassandra/customsnitch/MultiCloudSnitch.java
----------------------------------------------------------------------
diff --git a/sandbox/cassandra-multicloud-snitch/src/main/java/brooklyn/entity/nosql/cassandra/customsnitch/MultiCloudSnitch.java b/sandbox/cassandra-multicloud-snitch/src/main/java/brooklyn/entity/nosql/cassandra/customsnitch/MultiCloudSnitch.java
deleted file mode 100644
index ada56e2..0000000
--- a/sandbox/cassandra-multicloud-snitch/src/main/java/brooklyn/entity/nosql/cassandra/customsnitch/MultiCloudSnitch.java
+++ /dev/null
@@ -1,222 +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.nosql.cassandra.customsnitch;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.io.InputStream;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.HashMap;
-import java.util.Properties;
-
-import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.cassandra.gms.ApplicationState;
-import org.apache.cassandra.gms.EndpointState;
-import org.apache.cassandra.gms.Gossiper;
-import org.apache.cassandra.gms.IEndpointStateChangeSubscriber;
-import org.apache.cassandra.gms.VersionedValue;
-import org.apache.cassandra.io.util.FileUtils;
-import org.apache.cassandra.locator.AbstractNetworkTopologySnitch;
-import org.apache.cassandra.net.MessagingService;
-import org.apache.cassandra.service.StorageService;
-import org.apache.cassandra.utils.FBUtilities;
-import org.apache.cassandra.utils.ResourceWatcher;
-import org.apache.cassandra.utils.WrappedRunnable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A snitch that can be configured to work across clouds. It uses and
- * extends the cassandra-rackdc.properties (which is used by the 
- * GossipingPropertyFileSnitch) to add a publicip and privateip 
- * configuration.
- * <p>
- * The code is very similar to Ec2MultiRegionSnitch, except that it uses
- * the the config file rather than querying EC2 to get the IPs.
- * <p>
- * If two nodes are in the same datacenter, they will attempt to communicate
- * using the privateip. If they are in different datacenters, they will use
- * the publicip.
- */
-public class MultiCloudSnitch extends AbstractNetworkTopologySnitch implements IEndpointStateChangeSubscriber
-{
-    // FIXME Need to submit a pull request to Cassandra (1.2.x branch) for this snitch.
-    // Or could enhance GossipingPropertyFileSnitch instead, and submit PR for that.
-    
-    protected static final Logger logger = LoggerFactory.getLogger(MultiCloudSnitch.class);
-    
-    public static final String SNITCH_PROPERTIES_FILENAME = "cassandra-rackdc.properties";
-
-    private static final String DEFAULT_DC = "UNKNOWN-DC";
-    private static final String DEFAULT_RACK = "UNKNOWN-RACK";
-    
-    protected String rack;
-    protected String datacenter;
-    protected InetAddress public_ip;
-    protected String private_ip;
-
-    private volatile boolean gossipStarted;
-
-    public MultiCloudSnitch() throws ConfigurationException
-    {
-        reloadConfiguration();
-        logger.info("CustomSnitch using datacenter: " + datacenter + ", rack: " + rack + ", publicip: " + public_ip + ", privateip: " + private_ip);
-
-        try
-        {
-            FBUtilities.resourceToFile(SNITCH_PROPERTIES_FILENAME);
-            Runnable runnable = new WrappedRunnable()
-            {
-                protected void runMayThrow() throws ConfigurationException
-                {
-                    reloadConfiguration();
-                }
-            };
-            ResourceWatcher.watch(SNITCH_PROPERTIES_FILENAME, runnable, 60 * 1000);
-        }
-        catch (ConfigurationException ex)
-        {
-            logger.debug(SNITCH_PROPERTIES_FILENAME + " found, but does not look like a plain file. Will not watch it for changes");
-        }
-    }
-
-    public void reloadConfiguration() throws ConfigurationException
-    {
-        HashMap<InetAddress, String[]> reloadedMap = new HashMap<InetAddress, String[]>();
-        String DC_PROPERTY = "dc";
-        String RACK_PROPERTY = "rack";
-        String PUBLIC_IP_PROPERTY = "publicip";
-        String PRIVATE_IP_PROPERTY = "privateip";
-
-        Properties properties = new Properties();
-        InputStream stream = null;
-        try
-        {
-            stream = getClass().getClassLoader().getResourceAsStream(SNITCH_PROPERTIES_FILENAME);
-            properties.load(stream);
-        }
-        catch (Exception e)
-        {
-            throw new ConfigurationException("Unable to read " + SNITCH_PROPERTIES_FILENAME, e);
-        }
-        finally
-        {
-            FileUtils.closeQuietly(stream);
-        }
-
-        datacenter = properties.getProperty(DC_PROPERTY);
-        rack = properties.getProperty(RACK_PROPERTY);
-        private_ip = checkNotNull(properties.getProperty(PRIVATE_IP_PROPERTY), "%s in %s", PRIVATE_IP_PROPERTY, SNITCH_PROPERTIES_FILENAME);
-        String public_ip_str = checkNotNull(properties.getProperty(PUBLIC_IP_PROPERTY), "%s in %s", PUBLIC_IP_PROPERTY, SNITCH_PROPERTIES_FILENAME);
-        try {
-            public_ip = InetAddress.getByName(public_ip_str);
-        }
-        catch (UnknownHostException e)
-        {
-            throw new ConfigurationException("Unknown host " + public_ip_str, e);
-        }
-        
-        logger.debug("CustomSnitch reloaded, using datacenter: " + datacenter + ", rack: " + rack + ", publicip: " + public_ip + ", privateip: " + private_ip);
-
-        if (StorageService.instance != null) // null check tolerates circular dependency; see CASSANDRA-4145
-            StorageService.instance.getTokenMetadata().invalidateCaches();
-
-        if (gossipStarted)
-            StorageService.instance.gossipSnitchInfo();
-    }
-
-
-    public String getRack(InetAddress endpoint)
-    {
-        if (endpoint.equals(FBUtilities.getBroadcastAddress()))
-            return rack;
-        EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
-        if (state == null || state.getApplicationState(ApplicationState.RACK) == null)
-            return DEFAULT_RACK;
-        return state.getApplicationState(ApplicationState.RACK).value;
-    }
-
-    public String getDatacenter(InetAddress endpoint)
-    {
-        if (endpoint.equals(FBUtilities.getBroadcastAddress()))
-            return datacenter;
-        EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
-        if (state == null || state.getApplicationState(ApplicationState.DC) == null)
-            return DEFAULT_DC;
-        return state.getApplicationState(ApplicationState.DC).value;
-    }
-    
-    public void onJoin(InetAddress endpoint, EndpointState epState)
-    {
-        if (epState.getApplicationState(ApplicationState.INTERNAL_IP) != null)
-            reConnect(endpoint, epState.getApplicationState(ApplicationState.INTERNAL_IP));
-    }
-
-    public void onChange(InetAddress endpoint, ApplicationState state, VersionedValue value)
-    {
-        if (state == ApplicationState.INTERNAL_IP)
-            reConnect(endpoint, value);
-    }
-
-    public void onAlive(InetAddress endpoint, EndpointState state)
-    {
-        if (state.getApplicationState(ApplicationState.INTERNAL_IP) != null)
-            reConnect(endpoint, state.getApplicationState(ApplicationState.INTERNAL_IP));
-    }
-
-    public void onDead(InetAddress endpoint, EndpointState state)
-    {
-        // do nothing
-    }
-
-    public void onRestart(InetAddress endpoint, EndpointState state)
-    {
-        // do nothing
-    }
-
-    public void onRemove(InetAddress endpoint)
-    {
-        // do nothing.
-    }
-
-    private void reConnect(InetAddress endpoint, VersionedValue versionedValue)
-    {
-        if (!getDatacenter(endpoint).equals(getDatacenter(public_ip)))
-            return; // do nothing return back...
-
-        try
-        {
-            InetAddress remoteIP = InetAddress.getByName(versionedValue.value);
-            MessagingService.instance().getConnectionPool(endpoint).reset(remoteIP);
-            logger.debug(String.format("Intiated reconnect to an Internal IP %s for the %s", remoteIP, endpoint));
-        } catch (UnknownHostException e)
-        {
-            logger.error("Error in getting the IP address resolved: ", e);
-        }
-    }
-
-    @Override
-    public void gossiperStarting()
-    {
-        super.gossiperStarting();
-        Gossiper.instance.addLocalApplicationState(ApplicationState.INTERNAL_IP, StorageService.instance.valueFactory.internalIP(private_ip));
-        Gossiper.instance.register(this);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/cassandra-multicloud-snitch/src/main/java/org/apache/brooklyn/entity/nosql/cassandra/customsnitch/MultiCloudSnitch.java
----------------------------------------------------------------------
diff --git a/sandbox/cassandra-multicloud-snitch/src/main/java/org/apache/brooklyn/entity/nosql/cassandra/customsnitch/MultiCloudSnitch.java b/sandbox/cassandra-multicloud-snitch/src/main/java/org/apache/brooklyn/entity/nosql/cassandra/customsnitch/MultiCloudSnitch.java
new file mode 100644
index 0000000..0199290
--- /dev/null
+++ b/sandbox/cassandra-multicloud-snitch/src/main/java/org/apache/brooklyn/entity/nosql/cassandra/customsnitch/MultiCloudSnitch.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.nosql.cassandra.customsnitch;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Properties;
+
+import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.gms.ApplicationState;
+import org.apache.cassandra.gms.EndpointState;
+import org.apache.cassandra.gms.Gossiper;
+import org.apache.cassandra.gms.IEndpointStateChangeSubscriber;
+import org.apache.cassandra.gms.VersionedValue;
+import org.apache.cassandra.io.util.FileUtils;
+import org.apache.cassandra.locator.AbstractNetworkTopologySnitch;
+import org.apache.cassandra.net.MessagingService;
+import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.utils.FBUtilities;
+import org.apache.cassandra.utils.ResourceWatcher;
+import org.apache.cassandra.utils.WrappedRunnable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A snitch that can be configured to work across clouds. It uses and
+ * extends the cassandra-rackdc.properties (which is used by the 
+ * GossipingPropertyFileSnitch) to add a publicip and privateip 
+ * configuration.
+ * <p>
+ * The code is very similar to Ec2MultiRegionSnitch, except that it uses
+ * the the config file rather than querying EC2 to get the IPs.
+ * <p>
+ * If two nodes are in the same datacenter, they will attempt to communicate
+ * using the privateip. If they are in different datacenters, they will use
+ * the publicip.
+ */
+public class MultiCloudSnitch extends AbstractNetworkTopologySnitch implements IEndpointStateChangeSubscriber
+{
+    // FIXME Need to submit a pull request to Cassandra (1.2.x branch) for this snitch.
+    // Or could enhance GossipingPropertyFileSnitch instead, and submit PR for that.
+    
+    protected static final Logger logger = LoggerFactory.getLogger(MultiCloudSnitch.class);
+    
+    public static final String SNITCH_PROPERTIES_FILENAME = "cassandra-rackdc.properties";
+
+    private static final String DEFAULT_DC = "UNKNOWN-DC";
+    private static final String DEFAULT_RACK = "UNKNOWN-RACK";
+    
+    protected String rack;
+    protected String datacenter;
+    protected InetAddress public_ip;
+    protected String private_ip;
+
+    private volatile boolean gossipStarted;
+
+    public MultiCloudSnitch() throws ConfigurationException
+    {
+        reloadConfiguration();
+        logger.info("CustomSnitch using datacenter: " + datacenter + ", rack: " + rack + ", publicip: " + public_ip + ", privateip: " + private_ip);
+
+        try
+        {
+            FBUtilities.resourceToFile(SNITCH_PROPERTIES_FILENAME);
+            Runnable runnable = new WrappedRunnable()
+            {
+                protected void runMayThrow() throws ConfigurationException
+                {
+                    reloadConfiguration();
+                }
+            };
+            ResourceWatcher.watch(SNITCH_PROPERTIES_FILENAME, runnable, 60 * 1000);
+        }
+        catch (ConfigurationException ex)
+        {
+            logger.debug(SNITCH_PROPERTIES_FILENAME + " found, but does not look like a plain file. Will not watch it for changes");
+        }
+    }
+
+    public void reloadConfiguration() throws ConfigurationException
+    {
+        HashMap<InetAddress, String[]> reloadedMap = new HashMap<InetAddress, String[]>();
+        String DC_PROPERTY = "dc";
+        String RACK_PROPERTY = "rack";
+        String PUBLIC_IP_PROPERTY = "publicip";
+        String PRIVATE_IP_PROPERTY = "privateip";
+
+        Properties properties = new Properties();
+        InputStream stream = null;
+        try
+        {
+            stream = getClass().getClassLoader().getResourceAsStream(SNITCH_PROPERTIES_FILENAME);
+            properties.load(stream);
+        }
+        catch (Exception e)
+        {
+            throw new ConfigurationException("Unable to read " + SNITCH_PROPERTIES_FILENAME, e);
+        }
+        finally
+        {
+            FileUtils.closeQuietly(stream);
+        }
+
+        datacenter = properties.getProperty(DC_PROPERTY);
+        rack = properties.getProperty(RACK_PROPERTY);
+        private_ip = checkNotNull(properties.getProperty(PRIVATE_IP_PROPERTY), "%s in %s", PRIVATE_IP_PROPERTY, SNITCH_PROPERTIES_FILENAME);
+        String public_ip_str = checkNotNull(properties.getProperty(PUBLIC_IP_PROPERTY), "%s in %s", PUBLIC_IP_PROPERTY, SNITCH_PROPERTIES_FILENAME);
+        try {
+            public_ip = InetAddress.getByName(public_ip_str);
+        }
+        catch (UnknownHostException e)
+        {
+            throw new ConfigurationException("Unknown host " + public_ip_str, e);
+        }
+        
+        logger.debug("CustomSnitch reloaded, using datacenter: " + datacenter + ", rack: " + rack + ", publicip: " + public_ip + ", privateip: " + private_ip);
+
+        if (StorageService.instance != null) // null check tolerates circular dependency; see CASSANDRA-4145
+            StorageService.instance.getTokenMetadata().invalidateCaches();
+
+        if (gossipStarted)
+            StorageService.instance.gossipSnitchInfo();
+    }
+
+
+    public String getRack(InetAddress endpoint)
+    {
+        if (endpoint.equals(FBUtilities.getBroadcastAddress()))
+            return rack;
+        EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
+        if (state == null || state.getApplicationState(ApplicationState.RACK) == null)
+            return DEFAULT_RACK;
+        return state.getApplicationState(ApplicationState.RACK).value;
+    }
+
+    public String getDatacenter(InetAddress endpoint)
+    {
+        if (endpoint.equals(FBUtilities.getBroadcastAddress()))
+            return datacenter;
+        EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint);
+        if (state == null || state.getApplicationState(ApplicationState.DC) == null)
+            return DEFAULT_DC;
+        return state.getApplicationState(ApplicationState.DC).value;
+    }
+    
+    public void onJoin(InetAddress endpoint, EndpointState epState)
+    {
+        if (epState.getApplicationState(ApplicationState.INTERNAL_IP) != null)
+            reConnect(endpoint, epState.getApplicationState(ApplicationState.INTERNAL_IP));
+    }
+
+    public void onChange(InetAddress endpoint, ApplicationState state, VersionedValue value)
+    {
+        if (state == ApplicationState.INTERNAL_IP)
+            reConnect(endpoint, value);
+    }
+
+    public void onAlive(InetAddress endpoint, EndpointState state)
+    {
+        if (state.getApplicationState(ApplicationState.INTERNAL_IP) != null)
+            reConnect(endpoint, state.getApplicationState(ApplicationState.INTERNAL_IP));
+    }
+
+    public void onDead(InetAddress endpoint, EndpointState state)
+    {
+        // do nothing
+    }
+
+    public void onRestart(InetAddress endpoint, EndpointState state)
+    {
+        // do nothing
+    }
+
+    public void onRemove(InetAddress endpoint)
+    {
+        // do nothing.
+    }
+
+    private void reConnect(InetAddress endpoint, VersionedValue versionedValue)
+    {
+        if (!getDatacenter(endpoint).equals(getDatacenter(public_ip)))
+            return; // do nothing return back...
+
+        try
+        {
+            InetAddress remoteIP = InetAddress.getByName(versionedValue.value);
+            MessagingService.instance().getConnectionPool(endpoint).reset(remoteIP);
+            logger.debug(String.format("Intiated reconnect to an Internal IP %s for the %s", remoteIP, endpoint));
+        } catch (UnknownHostException e)
+        {
+            logger.error("Error in getting the IP address resolved: ", e);
+        }
+    }
+
+    @Override
+    public void gossiperStarting()
+    {
+        super.gossiperStarting();
+        Gossiper.instance.addLocalApplicationState(ApplicationState.INTERNAL_IP, StorageService.instance.valueFactory.internalIP(private_ip));
+        Gossiper.instance.register(this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/brooklyn/entity/database/Database.groovy
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/brooklyn/entity/database/Database.groovy b/sandbox/database/src/main/java/brooklyn/entity/database/Database.groovy
deleted file mode 100644
index a86c862..0000000
--- a/sandbox/database/src/main/java/brooklyn/entity/database/Database.groovy
+++ /dev/null
@@ -1,53 +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.database
-
-import brooklyn.event.basic.BasicConfigKey
-
-/**
- * Intended to represent a SQL relational database service.
- *
- * TODO work in progress
- */
-public interface Database {
-    BasicConfigKey<String> SQL_VERSION = [ String, "database.sql.version", "SQL version" ]
-
-    Collection<Schema> getSchemas();
-
-    void createSchema(String name, Map properties);
-
-    void addSchema(Schema schema);
-
-    void removeSchema(String schemaName);
-}
-
-/**
- * Intended to represent a SQL database schema.
- *
- * TODO work in progress
- */
-public interface Schema {
-    BasicConfigKey<String> SCHEMA_NAME = [ String, "database.schema", "Database schema name" ]
-
-    void create();
-    
-    void remove();
-    
-    String getName();
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabase.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabase.java b/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabase.java
deleted file mode 100644
index aad881f..0000000
--- a/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabase.java
+++ /dev/null
@@ -1,172 +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.database.derby;
-
-import java.util.Collection;
-import java.util.Map;
-
-import javax.management.ObjectName;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.basic.SoftwareProcessImpl;
-import brooklyn.entity.database.Database;
-import brooklyn.entity.database.Schema;
-import brooklyn.entity.java.UsesJava;
-import brooklyn.entity.java.UsesJmx;
-import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
-import brooklyn.event.feed.jmx.JmxHelper;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.flags.SetFromFlag;
-
-import com.google.common.base.Objects.ToStringHelper;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-/**
- * An {@link Entity} that represents a single Derby SQL database server instance.
- *
- * TODO work in progress
- */
-public class DerbyDatabase extends SoftwareProcessImpl implements Database, UsesJava, UsesJmx {
-    private static final Logger log = LoggerFactory.getLogger(DerbyDatabase.class);
-
-    @SetFromFlag("version")
-    public static final ConfigKey<String> SUGGESTED_VERSION =
-            ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "10.8.1.2");
-
-    public static final PortAttributeSensorAndConfigKey JDBC_PORT = new PortAttributeSensorAndConfigKey(
-            "derby.jdbcPort", "Suggested JDBC port");
-    
-    public static final ConfigKey<String> VIRTUAL_HOST_NAME = new BasicConfigKey<String>(
-            String.class, "derby.virtualHost", "Derby virtual host name", "localhost");
-
-    public static final BasicAttributeSensorAndConfigKey<String> JMX_USER = new BasicAttributeSensorAndConfigKey<String>(
-            UsesJmx.JMX_USER, "admin");
-    
-    public static final BasicAttributeSensorAndConfigKey<String> JMX_PASSWORD = new BasicAttributeSensorAndConfigKey<String>(
-            UsesJmx.JMX_PASSWORD, "admin");
-
-    @SetFromFlag
-    protected Collection<String> schemaNames;
-    
-    @SetFromFlag
-    protected Map<String, DerbySchema> schemas;
-
-    protected transient JmxHelper jmxHelper;
-    
-    public DerbyDatabase() {
-        this(MutableMap.of(), null);
-    }
-    public DerbyDatabase(Map properties) {
-        this(properties, null);
-    }
-    public DerbyDatabase(Entity parent) {
-        this(MutableMap.of(), parent);
-    }
-    public DerbyDatabase(Map properties, Entity parent) {
-        super(properties, parent);
-
-        if (schemaNames == null) schemaNames = Lists.newArrayList();
-        if (schemas == null) schemas = Maps.newLinkedHashMap();
-    }
-
-    @Override
-    public Class<? extends DerbyDatabaseDriver> getDriverInterface() {
-        return DerbyDatabaseDriver.class;
-    }
-
-    @Override
-    public void connectSensors() {
-        super.connectSensors();
-        connectServiceUpIsRunning();
-    }
-
-    @Override
-    public void disconnectSensors() {
-        super.disconnectSensors();
-        disconnectServiceUpIsRunning();
-    }
-
-    @Override
-    public void postStart() {
-        super.postStart();
-        for (String name : schemaNames) {
-            createSchema(name);
-        }
-    }
-
-    @Override
-    public void preStop() {
-        super.preStop();
-        for (DerbySchema schema : schemas.values()) {
-            schema.destroy();
-        }
-        if (jmxHelper != null) jmxHelper.terminate();
-    }
-
-    public void createSchema(String name) {
-        createSchema(name, ImmutableMap.of());
-    }
-    
-    public void createSchema(String name, Map properties) {
-        Map allprops = MutableMap.builder().putAll(properties).put("name", name).build();
-        DerbySchema schema = new DerbySchema(allprops);
-        schema.init();
-        schema.create();
-        schemas.put(name, schema);
-    }
-
-    public Collection<Schema> getSchemas() {
-        return ImmutableList.<Schema>copyOf(schemas.values());
-    }
-    
-    public void addSchema(Schema schema) {
-        schemas.put(schema.getName(), (DerbySchema) schema);
-    }
-    
-    public void removeSchema(String schemaName) {
-        schemas.remove(schemaName);
-    }
-
-    @Override
-    protected ToStringHelper toStringHelper() {
-        return super.toStringHelper().add("jdbcPort", getAttribute(JDBC_PORT));
-    }
-
-    protected boolean computeNodeUp() {
-        // FIXME Use the JmxAdapter.reachable() stuff instead of getAttribute
-        try {
-            ObjectName serverInfoObjectName = ObjectName.getInstance("org.apache.derby:type=ServerInformation,name=ServerInformation");
-            String productVersion = (String) jmxHelper.getAttribute(serverInfoObjectName, "ProductVersion");
-            return (productVersion != null);
-        } catch (Exception e) {
-            return false;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabaseDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabaseDriver.java b/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabaseDriver.java
deleted file mode 100644
index ed76e78..0000000
--- a/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabaseDriver.java
+++ /dev/null
@@ -1,25 +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.database.derby;
-
-import brooklyn.entity.java.JavaSoftwareProcessDriver;
-
-public interface DerbyDatabaseDriver extends JavaSoftwareProcessDriver {
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabaseSshDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabaseSshDriver.java b/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabaseSshDriver.java
deleted file mode 100644
index 172f823..0000000
--- a/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbyDatabaseSshDriver.java
+++ /dev/null
@@ -1,116 +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.database.derby;
-
-import static java.lang.String.format;
-
-import java.util.List;
-import java.util.Map;
-
-import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
-import brooklyn.location.Location;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.ssh.BashCommands;
-
-import com.google.common.collect.ImmutableList;
-
-/**
- * Start a {@link DerbyDatabase} in a {@link Location} accessible over ssh.
- *
- * TODO work in progress
- */
-public class DerbyDatabaseSshDriver extends JavaSoftwareProcessSshDriver implements DerbyDatabaseDriver {
-
-    // TOD Previous comment said "JMX is configured using command line switch"; how should that be set here?
-
-    public DerbyDatabaseSshDriver(DerbyDatabase entity, SshMachineLocation machine) {
-        super(entity, machine);
-    }
-
-    public String getPidFile() { return "derby.pid"; }
-
-    @Override
-    protected String getLogFileLocation() {
-        throw new UnsupportedOperationException("Work in progress");
-    }
-
-    @Override
-    public void install() {
-        String url = format("http://www.mirrorservice.org/sites/ftp.apache.org/db/derby/db-derby-%s/db-derby-%s-lib.tar.gz", getVersion(), getVersion());
-        String saveAs = format("db-derby-%s-lib.tar.gz", getVersion());
-
-        List<String> commands = ImmutableList.<String>builder()
-                .add(BashCommands.commandToDownloadUrlAs(url, saveAs))
-                .add(BashCommands.INSTALL_TAR)
-                .add("tar xzfv " + saveAs)
-                .build();
-
-        newScript(INSTALLING)
-                .failOnNonZeroResultCode()
-                .body.append(commands).execute();
-    }
-
-    @Override
-    public void customize() {
-        newScript(CUSTOMIZING)
-                .failOnNonZeroResultCode()
-                .body.append(
-                        format("cp -R %s/derby-broker-%s/{bin,etc,lib} .", getInstallDir(), getVersion()),
-                        "make install PREFIX="+getRunDir())
-                .execute();
-    }
-    
-    @Override
-    public void launch() {
-        // TODO Should we redirect stdout/stderr: format(" >> %s/console 2>&1 </dev/null &", getRunDir())
-        newScript(MutableMap.of("usePidFile", getPidFile()), LAUNCHING)
-                .failOnNonZeroResultCode()
-                .body.append("nohup ./bin/derby &")
-                .execute();
-    }
- 
-
-    @Override
-    public boolean isRunning() {
-        return newScript(MutableMap.of("usePidFile", getPidFile()), CHECK_RUNNING)
-                .execute() == 0;
-    }
-
-    /**
-     * Restarts redis with the current configuration.
-     */
-    @Override
-    public void stop() {
-        newScript(MutableMap.of("usePidFile", getPidFile()), STOPPING)
-                .execute();
-    }
-
-    @Override
-    public Map<String, String> getShellEnvironment() {
-        Map<String, String> orig = super.getShellEnvironment();
-        
-        return MutableMap.<String, String>builder()
-                .putAll(orig)
-                .put("DERBY_HOME", getRunDir())
-                .put("DERBY_WORK", getRunDir())
-                .putIfNotNull("DERBY_OPTS", orig.get("JAVA_OPTS"))
-                .build();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbySchema.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbySchema.java b/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbySchema.java
deleted file mode 100644
index 9ea26b5..0000000
--- a/sandbox/database/src/main/java/brooklyn/entity/database/derby/DerbySchema.java
+++ /dev/null
@@ -1,147 +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.database.derby;
-
-import static java.lang.String.format;
-
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import javax.management.ObjectName;
-
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.AbstractEntity;
-import brooklyn.entity.basic.EntityLocal;
-import brooklyn.entity.database.Schema;
-import brooklyn.entity.java.UsesJmx;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensor;
-import brooklyn.event.feed.jmx.JmxAttributePollConfig;
-import brooklyn.event.feed.jmx.JmxFeed;
-import brooklyn.event.feed.jmx.JmxHelper;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.flags.SetFromFlag;
-
-import com.google.common.base.Objects.ToStringHelper;
-
-public class DerbySchema extends AbstractEntity implements Schema {
-
-    // FIXME Needs reviewed and implemented properly; while fixing compilation errors
-    // I added enough for it to look mostly plausible but it's completely untested.
-    // And I have not looked up the derby docs to check that the attributes etc are valid. 
-    
-    // TODO Somehow share jmx connection with DerbyDatabase instance
-    
-    // TODO Declare effectors
-    
-    public static AttributeSensor<Integer> SCHEMA_DEPTH = new BasicAttributeSensor<Integer>(
-            Integer.class, "derby.schema.depth", "schema depth");
-    
-    public static AttributeSensor<Integer> MESSAGE_COUNT = new BasicAttributeSensor<Integer>(
-            Integer.class, "derby.schema.messageCount", "message count");
-    
-    @SetFromFlag(defaultVal="localhost")
-    String virtualHost;
-    
-    @SetFromFlag(nullable=false)
-    String name;
-
-    protected ObjectName virtualHostManager;
-    protected ObjectName exchange;
-
-    transient JmxHelper jmxHelper;
-    transient JmxFeed jmxFeed;
-    
-    public DerbySchema() {
-        super(MutableMap.of(), null);
-    }
-    public DerbySchema(Map properties) {
-        super(properties, null);
-    }
-    public DerbySchema(Entity parent) {
-        this(MutableMap.of(), parent);
-    }
-    public DerbySchema(Map properties, Entity parent) {
-        super(properties, parent);
-    }
-    
-    @Override
-    public String getName() {
-        return name;
-    }
-    
-    @Override
-    public DerbyDatabase getParent() {
-        return (DerbyDatabase) super.getParent();
-    }
-    
-    /**
-     * Return the JDBC connection URL for the schema.
-     */
-    public String getConnectionUrl() { return String.format("jdbc:derby:%s", name); }
-
-    public void init() {
-        try {
-            virtualHostManager = new ObjectName(format("org.apache.derby:type=VirtualHost.VirtualHostManager,VirtualHost=\"%s\"", virtualHost));
-            exchange = new ObjectName(format("org.apache.derby:type=VirtualHost.Exchange,VirtualHost=\"%s\",name=\"amq.direct\",ExchangeType=direct", virtualHost));
-            create();
-
-            jmxHelper = new JmxHelper((EntityLocal)getParent());
-
-            ObjectName schemaMBeanName = new ObjectName(format("org.apache.derby:type=VirtualHost.Schema,VirtualHost=\"%s\",name=\"%s\"", virtualHost, name));
-
-            jmxFeed = JmxFeed.builder()
-                    .entity(this)
-                    .helper(jmxHelper)
-                    .period(500, TimeUnit.MILLISECONDS)
-                    .pollAttribute(new JmxAttributePollConfig<Integer>(SCHEMA_DEPTH)
-                            .objectName(schemaMBeanName)
-                            .attributeName("SchemaDepth"))
-                    .pollAttribute(new JmxAttributePollConfig<Integer>(MESSAGE_COUNT)
-                            .objectName(schemaMBeanName)
-                            .attributeName("MessageCount"))
-                    .build();
-            
-        } catch (Exception e) {
-            throw Exceptions.propagate(e);
-        }
-    }
-
-    public void create() {
-        jmxHelper.operation(virtualHostManager, "createNewSchema", name, getParent().getAttribute(UsesJmx.JMX_USER), true);
-        jmxHelper.operation(exchange, "createNewBinding", name, name);
-    }
-
-    public void remove() {
-        jmxHelper.operation(exchange, "removeBinding", name, name);
-        jmxHelper.operation(virtualHostManager, "deleteSchema", name);
-    }
-
-    @Override
-    public void destroy() {
-        if (jmxFeed != null) jmxFeed.stop();
-        super.destroy();
-    }
-
-    @Override
-    protected ToStringHelper toStringHelper() {
-        return super.toStringHelper().add("name", name);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/Database.groovy
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/Database.groovy b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/Database.groovy
new file mode 100644
index 0000000..5118dd0
--- /dev/null
+++ b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/Database.groovy
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database
+
+import brooklyn.event.basic.BasicConfigKey
+
+/**
+ * Intended to represent a SQL relational database service.
+ *
+ * TODO work in progress
+ */
+public interface Database {
+    BasicConfigKey<String> SQL_VERSION = [ String, "database.sql.version", "SQL version" ]
+
+    Collection<Schema> getSchemas();
+
+    void createSchema(String name, Map properties);
+
+    void addSchema(Schema schema);
+
+    void removeSchema(String schemaName);
+}
+
+/**
+ * Intended to represent a SQL database schema.
+ *
+ * TODO work in progress
+ */
+public interface Schema {
+    BasicConfigKey<String> SCHEMA_NAME = [ String, "database.schema", "Database schema name" ]
+
+    void create();
+    
+    void remove();
+    
+    String getName();
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabase.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabase.java b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabase.java
new file mode 100644
index 0000000..00b0ab2
--- /dev/null
+++ b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabase.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.derby;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.management.ObjectName;
+
+import org.apache.brooklyn.entity.database.Database;
+import org.apache.brooklyn.entity.database.Schema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.entity.java.UsesJava;
+import brooklyn.entity.java.UsesJmx;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.event.feed.jmx.JmxHelper;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * An {@link Entity} that represents a single Derby SQL database server instance.
+ *
+ * TODO work in progress
+ */
+public class DerbyDatabase extends SoftwareProcessImpl implements Database, UsesJava, UsesJmx {
+    private static final Logger log = LoggerFactory.getLogger(DerbyDatabase.class);
+
+    @SetFromFlag("version")
+    public static final ConfigKey<String> SUGGESTED_VERSION =
+            ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "10.8.1.2");
+
+    public static final PortAttributeSensorAndConfigKey JDBC_PORT = new PortAttributeSensorAndConfigKey(
+            "derby.jdbcPort", "Suggested JDBC port");
+    
+    public static final ConfigKey<String> VIRTUAL_HOST_NAME = new BasicConfigKey<String>(
+            String.class, "derby.virtualHost", "Derby virtual host name", "localhost");
+
+    public static final BasicAttributeSensorAndConfigKey<String> JMX_USER = new BasicAttributeSensorAndConfigKey<String>(
+            UsesJmx.JMX_USER, "admin");
+    
+    public static final BasicAttributeSensorAndConfigKey<String> JMX_PASSWORD = new BasicAttributeSensorAndConfigKey<String>(
+            UsesJmx.JMX_PASSWORD, "admin");
+
+    @SetFromFlag
+    protected Collection<String> schemaNames;
+    
+    @SetFromFlag
+    protected Map<String, DerbySchema> schemas;
+
+    protected transient JmxHelper jmxHelper;
+    
+    public DerbyDatabase() {
+        this(MutableMap.of(), null);
+    }
+    public DerbyDatabase(Map properties) {
+        this(properties, null);
+    }
+    public DerbyDatabase(Entity parent) {
+        this(MutableMap.of(), parent);
+    }
+    public DerbyDatabase(Map properties, Entity parent) {
+        super(properties, parent);
+
+        if (schemaNames == null) schemaNames = Lists.newArrayList();
+        if (schemas == null) schemas = Maps.newLinkedHashMap();
+    }
+
+    @Override
+    public Class<? extends DerbyDatabaseDriver> getDriverInterface() {
+        return DerbyDatabaseDriver.class;
+    }
+
+    @Override
+    public void connectSensors() {
+        super.connectSensors();
+        connectServiceUpIsRunning();
+    }
+
+    @Override
+    public void disconnectSensors() {
+        super.disconnectSensors();
+        disconnectServiceUpIsRunning();
+    }
+
+    @Override
+    public void postStart() {
+        super.postStart();
+        for (String name : schemaNames) {
+            createSchema(name);
+        }
+    }
+
+    @Override
+    public void preStop() {
+        super.preStop();
+        for (DerbySchema schema : schemas.values()) {
+            schema.destroy();
+        }
+        if (jmxHelper != null) jmxHelper.terminate();
+    }
+
+    public void createSchema(String name) {
+        createSchema(name, ImmutableMap.of());
+    }
+    
+    public void createSchema(String name, Map properties) {
+        Map allprops = MutableMap.builder().putAll(properties).put("name", name).build();
+        DerbySchema schema = new DerbySchema(allprops);
+        schema.init();
+        schema.create();
+        schemas.put(name, schema);
+    }
+
+    public Collection<Schema> getSchemas() {
+        return ImmutableList.<Schema>copyOf(schemas.values());
+    }
+    
+    public void addSchema(Schema schema) {
+        schemas.put(schema.getName(), (DerbySchema) schema);
+    }
+    
+    public void removeSchema(String schemaName) {
+        schemas.remove(schemaName);
+    }
+
+    @Override
+    protected ToStringHelper toStringHelper() {
+        return super.toStringHelper().add("jdbcPort", getAttribute(JDBC_PORT));
+    }
+
+    protected boolean computeNodeUp() {
+        // FIXME Use the JmxAdapter.reachable() stuff instead of getAttribute
+        try {
+            ObjectName serverInfoObjectName = ObjectName.getInstance("org.apache.derby:type=ServerInformation,name=ServerInformation");
+            String productVersion = (String) jmxHelper.getAttribute(serverInfoObjectName, "ProductVersion");
+            return (productVersion != null);
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabaseDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabaseDriver.java b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabaseDriver.java
new file mode 100644
index 0000000..832c2b0
--- /dev/null
+++ b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabaseDriver.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.derby;
+
+import brooklyn.entity.java.JavaSoftwareProcessDriver;
+
+public interface DerbyDatabaseDriver extends JavaSoftwareProcessDriver {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabaseSshDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabaseSshDriver.java b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabaseSshDriver.java
new file mode 100644
index 0000000..32bc58c
--- /dev/null
+++ b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbyDatabaseSshDriver.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.derby;
+
+import static java.lang.String.format;
+
+import java.util.List;
+import java.util.Map;
+
+import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
+import brooklyn.location.Location;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.ssh.BashCommands;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Start a {@link DerbyDatabase} in a {@link Location} accessible over ssh.
+ *
+ * TODO work in progress
+ */
+public class DerbyDatabaseSshDriver extends JavaSoftwareProcessSshDriver implements DerbyDatabaseDriver {
+
+    // TOD Previous comment said "JMX is configured using command line switch"; how should that be set here?
+
+    public DerbyDatabaseSshDriver(DerbyDatabase entity, SshMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    public String getPidFile() { return "derby.pid"; }
+
+    @Override
+    protected String getLogFileLocation() {
+        throw new UnsupportedOperationException("Work in progress");
+    }
+
+    @Override
+    public void install() {
+        String url = format("http://www.mirrorservice.org/sites/ftp.apache.org/db/derby/db-derby-%s/db-derby-%s-lib.tar.gz", getVersion(), getVersion());
+        String saveAs = format("db-derby-%s-lib.tar.gz", getVersion());
+
+        List<String> commands = ImmutableList.<String>builder()
+                .add(BashCommands.commandToDownloadUrlAs(url, saveAs))
+                .add(BashCommands.INSTALL_TAR)
+                .add("tar xzfv " + saveAs)
+                .build();
+
+        newScript(INSTALLING)
+                .failOnNonZeroResultCode()
+                .body.append(commands).execute();
+    }
+
+    @Override
+    public void customize() {
+        newScript(CUSTOMIZING)
+                .failOnNonZeroResultCode()
+                .body.append(
+                        format("cp -R %s/derby-broker-%s/{bin,etc,lib} .", getInstallDir(), getVersion()),
+                        "make install PREFIX="+getRunDir())
+                .execute();
+    }
+    
+    @Override
+    public void launch() {
+        // TODO Should we redirect stdout/stderr: format(" >> %s/console 2>&1 </dev/null &", getRunDir())
+        newScript(MutableMap.of("usePidFile", getPidFile()), LAUNCHING)
+                .failOnNonZeroResultCode()
+                .body.append("nohup ./bin/derby &")
+                .execute();
+    }
+ 
+
+    @Override
+    public boolean isRunning() {
+        return newScript(MutableMap.of("usePidFile", getPidFile()), CHECK_RUNNING)
+                .execute() == 0;
+    }
+
+    /**
+     * Restarts redis with the current configuration.
+     */
+    @Override
+    public void stop() {
+        newScript(MutableMap.of("usePidFile", getPidFile()), STOPPING)
+                .execute();
+    }
+
+    @Override
+    public Map<String, String> getShellEnvironment() {
+        Map<String, String> orig = super.getShellEnvironment();
+        
+        return MutableMap.<String, String>builder()
+                .putAll(orig)
+                .put("DERBY_HOME", getRunDir())
+                .put("DERBY_WORK", getRunDir())
+                .putIfNotNull("DERBY_OPTS", orig.get("JAVA_OPTS"))
+                .build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbySchema.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbySchema.java b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbySchema.java
new file mode 100644
index 0000000..c4fb2b1
--- /dev/null
+++ b/sandbox/database/src/main/java/org/apache/brooklyn/entity/database/derby/DerbySchema.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.derby;
+
+import static java.lang.String.format;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.management.ObjectName;
+
+import org.apache.brooklyn.entity.database.Schema;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.java.UsesJmx;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.feed.jmx.JmxAttributePollConfig;
+import brooklyn.event.feed.jmx.JmxFeed;
+import brooklyn.event.feed.jmx.JmxHelper;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.base.Objects.ToStringHelper;
+
+public class DerbySchema extends AbstractEntity implements Schema {
+
+    // FIXME Needs reviewed and implemented properly; while fixing compilation errors
+    // I added enough for it to look mostly plausible but it's completely untested.
+    // And I have not looked up the derby docs to check that the attributes etc are valid. 
+    
+    // TODO Somehow share jmx connection with DerbyDatabase instance
+    
+    // TODO Declare effectors
+    
+    public static AttributeSensor<Integer> SCHEMA_DEPTH = new BasicAttributeSensor<Integer>(
+            Integer.class, "derby.schema.depth", "schema depth");
+    
+    public static AttributeSensor<Integer> MESSAGE_COUNT = new BasicAttributeSensor<Integer>(
+            Integer.class, "derby.schema.messageCount", "message count");
+    
+    @SetFromFlag(defaultVal="localhost")
+    String virtualHost;
+    
+    @SetFromFlag(nullable=false)
+    String name;
+
+    protected ObjectName virtualHostManager;
+    protected ObjectName exchange;
+
+    transient JmxHelper jmxHelper;
+    transient JmxFeed jmxFeed;
+    
+    public DerbySchema() {
+        super(MutableMap.of(), null);
+    }
+    public DerbySchema(Map properties) {
+        super(properties, null);
+    }
+    public DerbySchema(Entity parent) {
+        this(MutableMap.of(), parent);
+    }
+    public DerbySchema(Map properties, Entity parent) {
+        super(properties, parent);
+    }
+    
+    @Override
+    public String getName() {
+        return name;
+    }
+    
+    @Override
+    public DerbyDatabase getParent() {
+        return (DerbyDatabase) super.getParent();
+    }
+    
+    /**
+     * Return the JDBC connection URL for the schema.
+     */
+    public String getConnectionUrl() { return String.format("jdbc:derby:%s", name); }
+
+    public void init() {
+        try {
+            virtualHostManager = new ObjectName(format("org.apache.derby:type=VirtualHost.VirtualHostManager,VirtualHost=\"%s\"", virtualHost));
+            exchange = new ObjectName(format("org.apache.derby:type=VirtualHost.Exchange,VirtualHost=\"%s\",name=\"amq.direct\",ExchangeType=direct", virtualHost));
+            create();
+
+            jmxHelper = new JmxHelper((EntityLocal)getParent());
+
+            ObjectName schemaMBeanName = new ObjectName(format("org.apache.derby:type=VirtualHost.Schema,VirtualHost=\"%s\",name=\"%s\"", virtualHost, name));
+
+            jmxFeed = JmxFeed.builder()
+                    .entity(this)
+                    .helper(jmxHelper)
+                    .period(500, TimeUnit.MILLISECONDS)
+                    .pollAttribute(new JmxAttributePollConfig<Integer>(SCHEMA_DEPTH)
+                            .objectName(schemaMBeanName)
+                            .attributeName("SchemaDepth"))
+                    .pollAttribute(new JmxAttributePollConfig<Integer>(MESSAGE_COUNT)
+                            .objectName(schemaMBeanName)
+                            .attributeName("MessageCount"))
+                    .build();
+            
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    public void create() {
+        jmxHelper.operation(virtualHostManager, "createNewSchema", name, getParent().getAttribute(UsesJmx.JMX_USER), true);
+        jmxHelper.operation(exchange, "createNewBinding", name, name);
+    }
+
+    public void remove() {
+        jmxHelper.operation(exchange, "removeBinding", name, name);
+        jmxHelper.operation(virtualHostManager, "deleteSchema", name);
+    }
+
+    @Override
+    public void destroy() {
+        if (jmxFeed != null) jmxFeed.stop();
+        super.destroy();
+    }
+
+    @Override
+    protected ToStringHelper toStringHelper() {
+        return super.toStringHelper().add("name", name);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/test/java/brooklyn/entity/database/PlaceholderTest.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/test/java/brooklyn/entity/database/PlaceholderTest.java b/sandbox/database/src/test/java/brooklyn/entity/database/PlaceholderTest.java
deleted file mode 100644
index 55b5d22..0000000
--- a/sandbox/database/src/test/java/brooklyn/entity/database/PlaceholderTest.java
+++ /dev/null
@@ -1,26 +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.database;
-
-import org.testng.annotations.Test;
-
-public class PlaceholderTest {
-    @Test
-    public void noop() {}
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/database/src/test/java/org/apache/brooklyn/entity/database/PlaceholderTest.java
----------------------------------------------------------------------
diff --git a/sandbox/database/src/test/java/org/apache/brooklyn/entity/database/PlaceholderTest.java b/sandbox/database/src/test/java/org/apache/brooklyn/entity/database/PlaceholderTest.java
new file mode 100644
index 0000000..564249d1
--- /dev/null
+++ b/sandbox/database/src/test/java/org/apache/brooklyn/entity/database/PlaceholderTest.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database;
+
+import org.testng.annotations.Test;
+
+public class PlaceholderTest {
+    @Test
+    public void noop() {}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java b/sandbox/extra/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java
deleted file mode 100644
index af0845f..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java
+++ /dev/null
@@ -1,177 +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.database.postgresql;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Effector;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.EffectorStartableImpl;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.effector.EffectorBody;
-import brooklyn.entity.effector.Effectors;
-import brooklyn.entity.salt.SaltConfig;
-import brooklyn.entity.salt.SaltConfigs;
-import brooklyn.entity.salt.SaltLifecycleEffectorTasks;
-import brooklyn.entity.software.SshEffectorTasks;
-import brooklyn.event.basic.DependentConfiguration;
-import brooklyn.event.feed.ssh.SshFeed;
-import brooklyn.event.feed.ssh.SshPollConfig;
-import brooklyn.location.Location;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.ResourceUtils;
-import brooklyn.util.config.ConfigBag;
-import brooklyn.util.ssh.BashCommands;
-import brooklyn.util.task.DynamicTasks;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-
-public class PostgreSqlNodeSaltImpl extends EffectorStartableImpl implements PostgreSqlNode, SoftwareProcess {
-
-    private static final Logger LOG = LoggerFactory.getLogger(PostgreSqlNodeSaltImpl.class);
-
-    public static final Effector<String> EXECUTE_SCRIPT = Effectors.effector(String.class, "executeScript")
-            .description("invokes a script")
-            .parameter(ExecuteScriptEffectorBody.SCRIPT)
-            .impl(new ExecuteScriptEffectorBody())
-            .build();
-
-    private SshFeed feed;
-
-    @Override
-    public void init() {
-        super.init();
-        new SaltPostgreSqlLifecycle().attachLifecycleEffectors(this);
-    }
-
-    public static class SaltPostgreSqlLifecycle extends SaltLifecycleEffectorTasks {
-        public SaltPostgreSqlLifecycle() {
-            usePidFile("/var/run/postgresql/*.pid");
-            useService("postgresql");
-        }
-
-        @Override
-        protected void startMinionAsync() {
-            Entities.warnOnIgnoringConfig(entity(), SaltConfig.SALT_FORMULAS);
-            Entities.warnOnIgnoringConfig(entity(), SaltConfig.SALT_RUN_LIST);
-            Entities.warnOnIgnoringConfig(entity(), SaltConfig.SALT_LAUNCH_ATTRIBUTES);
-
-            // TODO Set these as defaults, rather than replacing user's value!?
-            SaltConfigs.addToFormulas(entity(), "postgres", "https://github.com/saltstack-formulas/postgres-formula/archive/master.tar.gz");
-            SaltConfigs.addToRunList(entity(), "postgres");
-            SaltConfigs.addLaunchAttributes(entity(), ImmutableMap.<String,Object>builder()
-                    .put("port", DependentConfiguration.attributeWhenReady(entity(), PostgreSqlNode.POSTGRESQL_PORT))
-                    .put("listen_addresses", "*")
-                    .put("pg_hba.type", "host")
-                    .put("pg_hba.db", "all")
-                    .put("pg_hba.user", "all")
-                    .put("pg_hba.addr", "0.0.0.0/0")
-                    .put("pg_hba.method", "md5")
-                    .build());
-
-            super.startMinionAsync();
-        }
-
-        @Override
-        protected void postStartCustom() {
-            super.postStartCustom();
-
-            // now run the creation script
-            String creationScriptUrl = entity().getConfig(PostgreSqlNode.CREATION_SCRIPT_URL);
-            String creationScript;
-            if (creationScriptUrl != null) {
-                creationScript = new ResourceUtils(entity()).getResourceAsString(creationScriptUrl);
-            } else {
-                creationScript = entity().getConfig(PostgreSqlNode.CREATION_SCRIPT_CONTENTS);
-            }
-            entity().invoke(PostgreSqlNodeSaltImpl.EXECUTE_SCRIPT,
-                    ConfigBag.newInstance().configure(ExecuteScriptEffectorBody.SCRIPT, creationScript).getAllConfig()).getUnchecked();
-
-            // and finally connect sensors
-            ((PostgreSqlNodeSaltImpl) entity()).connectSensors();
-        }
-
-        @Override
-        protected void preStopCustom() {
-            ((PostgreSqlNodeSaltImpl) entity()).disconnectSensors();
-            super.preStopCustom();
-        }
-    }
-
-    public static class ExecuteScriptEffectorBody extends EffectorBody<String> {
-        public static final ConfigKey<String> SCRIPT = ConfigKeys.newStringConfigKey("script", "contents of script to run");
-
-        @Override
-        public String call(ConfigBag parameters) {
-            return DynamicTasks.queue(SshEffectorTasks.ssh(
-                    BashCommands.pipeTextTo(
-                            parameters.get(SCRIPT),
-                            BashCommands.sudoAsUser("postgres", "psql --file -")))
-                    .requiringExitCodeZero()).getStdout();
-        }
-    }
-
-    protected void connectSensors() {
-        setAttribute(DATASTORE_URL, String.format("postgresql://%s:%s/", getAttribute(HOSTNAME), getAttribute(POSTGRESQL_PORT)));
-
-        Location machine = Iterables.get(getLocations(), 0, null);
-
-        if (machine instanceof SshMachineLocation) {
-            feed = SshFeed.builder()
-                    .entity(this)
-                    .machine((SshMachineLocation)machine)
-                    .poll(new SshPollConfig<Boolean>(SERVICE_UP)
-                            .command("ps -ef | grep [p]ostgres")
-                            .setOnSuccess(true)
-                            .setOnFailureOrException(false))
-                    .build();
-        } else {
-            LOG.warn("Location(s) %s not an ssh-machine location, so not polling for status; setting serviceUp immediately", getLocations());
-        }
-    }
-
-    protected void disconnectSensors() {
-        if (feed != null) feed.stop();
-    }
-
-    @Override
-    public Integer getPostgreSqlPort() { return getAttribute(POSTGRESQL_PORT); }
-
-    @Override
-    public String getSharedMemory() { return getConfig(SHARED_MEMORY); }
-
-    @Override
-    public Integer getMaxConnections() { return getConfig(MAX_CONNECTIONS); }
-
-    @Override
-    public String getShortName() {
-        return "PostgreSQL";
-    }
-
-    @Override
-    public String executeScript(String commands) {
-        return Entities.invokeEffector(this, this, EXECUTE_SCRIPT,
-                ConfigBag.newInstance().configure(ExecuteScriptEffectorBody.SCRIPT, commands).getAllConfig()).getUnchecked();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltBashCommands.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltBashCommands.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltBashCommands.java
deleted file mode 100644
index 70379f7..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltBashCommands.java
+++ /dev/null
@@ -1,92 +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.salt;
-
-import static brooklyn.util.ssh.BashCommands.downloadToStdout;
-
-import javax.annotation.Nullable;
-
-import org.apache.commons.io.FilenameUtils;
-
-import brooklyn.entity.chef.ChefBashCommands;
-import brooklyn.util.ssh.BashCommands;
-import brooklyn.util.text.Identifiers;
-import brooklyn.util.text.Strings;
-
-import com.google.common.annotations.Beta;
-import com.google.common.io.Files;
-
-/**
- * BASH commands useful for setting up SaltStack.
- */
-@Beta
-public class SaltBashCommands {
-
-    /**
-     * SaltStack formulas can be found at {@code https://github.com/saltstack-formulas} as repositories.
-     * <p>
-     * This assumes the download is an archive containing a single directory on the root which will
-     * be renamed to {@code formulaName}. if that directory already has the correct name {@code formulaName}
-     * can be null, but if taking from a GitHub tarball it will typically be of the form {@code formulaName-master/}
-     * hence the renaming.
-     */
-    // TODO support installing from classpath, and using the repository (tie in with those methods)
-    public static final String downloadAndExpandFormula(String source, @Nullable String formulaName, boolean force) {
-        String dl = downloadAndExpandFormula(source);
-        if (formulaName==null) return dl;
-        String tmpName = "tmp-"+Strings.makeValidFilename(formulaName)+"-"+Identifiers.makeRandomId(4);
-        String installCmd = BashCommands.chain("mkdir "+tmpName, "cd "+tmpName, dl,
-                BashCommands.requireTest("`ls | wc -w` -eq 1", "The archive must contain exactly one directory"),
-                "FORMULA_EXPANDED_DIR=`ls`",
-                "mv $FORMULA_EXPANDED_DIR '../"+formulaName+"'",
-                "cd ..",
-                "rm -rf "+tmpName);
-        if (!force) return BashCommands.alternatives("ls "+formulaName, installCmd);
-        else return BashCommands.alternatives("rm -rf "+formulaName, installCmd);
-    }
-
-    /**
-     * Same as {@link #downloadAndExpandFormula(String, String)} with no formula name.
-     * <p>
-     * Equivalent to the following command, but substituting the given {@code sourceUrl}.
-     * <pre>{@code
-     * curl -f -L  https://github.com/saltstack-formulas/nginx-formula/archive/master.tar.gz | tar xvz
-     * }</pre>
-     */
-    public static final String downloadAndExpandFormula(String sourceUrl) {
-        String ext = Files.getFileExtension(sourceUrl);
-        if ("tar".equalsIgnoreCase(ext))
-            return downloadToStdout(sourceUrl) + " | tar xv";
-        if ("tgz".equalsIgnoreCase(ext) || sourceUrl.toLowerCase().endsWith(".tar.gz"))
-            return downloadToStdout(sourceUrl) + " | tar xvz";
-
-        String target = FilenameUtils.getName(sourceUrl);
-        if (target==null) target = ""; else target = target.trim();
-        target += "_"+Strings.makeRandomId(4);
-
-        if ("zip".equalsIgnoreCase(ext) || "tar.gz".equalsIgnoreCase(ext))
-            return BashCommands.chain(
-                BashCommands.commandToDownloadUrlAs(sourceUrl, target),
-                "unzip "+target,
-                "rm "+target);
-
-        throw new UnsupportedOperationException("No way to expand "+sourceUrl+" (yet)");
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfig.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfig.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfig.java
deleted file mode 100644
index b14ba98..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfig.java
+++ /dev/null
@@ -1,100 +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.salt;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensor;
-import brooklyn.event.basic.BasicConfigKey;
-import brooklyn.event.basic.MapConfigKey;
-import brooklyn.event.basic.SetConfigKey;
-import brooklyn.management.TaskAdaptable;
-import brooklyn.management.TaskFactory;
-import brooklyn.util.flags.SetFromFlag;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Function;
-import com.google.common.base.Functions;
-import com.google.common.reflect.TypeToken;
-
-/**
- * {@link ConfigKey}s used to configure Salt entities.
- *
- * @see SaltConfigs
- * @see SaltLifecycleEffectorTasks
- */
-@Beta
-public interface SaltConfig {
-
-    MapConfigKey<String> SALT_FORMULAS = new MapConfigKey<String>(String.class,
-            "salt.formulaUrls", "Map of Salt formula URLs (normally GutHub repository archives from the salt-formulas user)");
-    SetConfigKey<String> SALT_RUN_LIST = new SetConfigKey<String>(String.class,
-            "salt.runList", "Set of Salt states to install from the formula URLs");
-    MapConfigKey<Object> SALT_LAUNCH_ATTRIBUTES = new MapConfigKey<Object>(Object.class, "salt.launch.attributes", "TODO");
-
-    @SetFromFlag("master")
-    ConfigKey<SaltStackMaster> MASTER = ConfigKeys.newConfigKey(SaltStackMaster.class,
-            "salt.master.entity", "The Salt master server");
-
-    AttributeSensor<String> MINION_ID = new BasicAttributeSensor<String>(String.class,
-            "salt.minionId", "The ID for a Salt minion");
-
-    @SetFromFlag("masterless")
-    ConfigKey<Boolean> MASTERLESS_MODE = ConfigKeys.newBooleanConfigKey(
-            "salt.masterless", "Salt masterless, minion only configuration (default uses master and minion)",
-            Boolean.FALSE);
-
-    @SetFromFlag("masterConfigUrl")
-    ConfigKey<String> MASTER_CONFIGURATION_URL = ConfigKeys.newStringConfigKey(
-            "salt.master.templateUrl", "The Salt master configuration file template URL",
-            "classpath://brooklyn/entity/salt/master");
-
-    @SetFromFlag("minionConfigUrl")
-    ConfigKey<String> MINION_CONFIGURATION_URL = ConfigKeys.newStringConfigKey(
-            "salt.minion.templateUrl", "The Salt minion configuration file template URL",
-            "classpath://brooklyn/entity/salt/minion");
-
-    @SetFromFlag("masterlessConfigUrl")
-    ConfigKey<String> MASTERLESS_CONFIGURATION_URL = ConfigKeys.newStringConfigKey(
-            "salt.masterless.templateUrl", "The Salt minion masterless configuration file template URL",
-            "classpath://brooklyn/entity/salt/masterless");
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @SetFromFlag("minionIdFunction")
-    ConfigKey<Function<Entity, String>> MINION_ID_FUNCTION = new BasicConfigKey(Function.class,
-            "salt.minionId.function", "Function to generate the ID of a Salt minion for an entity", Functions.toStringFunction());
-
-    @SuppressWarnings("serial")
-    ConfigKey<TaskFactory<? extends TaskAdaptable<Boolean>>> IS_RUNNING_TASK = ConfigKeys.newConfigKey(
-            new TypeToken<TaskFactory<? extends TaskAdaptable<Boolean>>>() {}, 
-            "salt.driver.isRunningTask");
-
-    @SuppressWarnings("serial")
-    ConfigKey<TaskFactory<?>> STOP_TASK = ConfigKeys.newConfigKey(
-            new TypeToken<TaskFactory<?>>() {}, 
-            "salt.driver.stopTask");
-
-    /**
-     * The {@link SaltStackMaster master} entity.
-     */
-    SaltStackMaster getMaster();
-
-}



[4/6] incubator-brooklyn git commit: package rename to org.apache.brooklyn: sandbox

Posted by ha...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfigs.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfigs.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfigs.java
deleted file mode 100644
index 2de0e5d..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltConfigs.java
+++ /dev/null
@@ -1,89 +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.salt;
-
-import java.util.Map;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.EntityInternal;
-import brooklyn.entity.proxying.EntitySpec;
-import brooklyn.event.basic.MapConfigKey.MapModifications;
-import brooklyn.event.basic.SetConfigKey.SetModifications;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Preconditions;
-
-/**
- * Conveniences for configuring brooklyn Salt entities 
- *
- * @since 0.6.0
- */
-@Beta
-public class SaltConfigs {
-
-    public static void addToRunList(EntitySpec<?> entity, String...states) {
-        for (String state : states) {
-            entity.configure(SaltConfig.SALT_RUN_LIST, SetModifications.addItem(state));
-        }
-    }
-
-    public static void addToRunList(EntityInternal entity, String...states) {
-        for (String state : states) {
-            entity.setConfig(SaltConfig.SALT_RUN_LIST, SetModifications.addItem(state));
-        }
-    }
-
-    public static void addToFormuals(EntitySpec<?> entity, String formulaName, String formulaUrl) {
-        entity.configure(SaltConfig.SALT_FORMULAS.subKey(formulaName), formulaUrl);
-    }
-
-    public static void addToFormulas(EntityInternal entity, String formulaName, String formulaUrl) {
-        entity.setConfig(SaltConfig.SALT_FORMULAS.subKey(formulaName), formulaUrl);
-    }
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public static void addLaunchAttributes(EntitySpec<?> entity, Map<? extends Object,? extends Object> attributesMap) {
-        entity.configure(SaltConfig.SALT_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap));
-    }
-    
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public static void addLaunchAttributes(EntityInternal entity, Map<? extends Object,? extends Object> attributesMap) {
-        entity.setConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap));
-    }
-    
-    /** replaces the attributes underneath the rootAttribute parameter with the given value;
-     * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */
-    public static void setLaunchAttribute(EntitySpec<?> entity, String rootAttribute, Object value) {
-        entity.configure(SaltConfig.SALT_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value);
-    }
-    
-    /** replaces the attributes underneath the rootAttribute parameter with the given value;
-     * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */
-    public static void setLaunchAttribute(EntityInternal entity, String rootAttribute, Object value) {
-        entity.setConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value);
-    }
-
-    public static <T> T getRequiredConfig(Entity entity, ConfigKey<T> key) {
-        return Preconditions.checkNotNull(
-                Preconditions.checkNotNull(entity, "Entity must be supplied").getConfig(key), 
-                "Key "+key+" is required on "+entity);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java
deleted file mode 100644
index 82e1b17..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java
+++ /dev/null
@@ -1,220 +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.salt;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.config.BrooklynServerConfig;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.Lifecycle;
-import brooklyn.entity.software.MachineLifecycleEffectorTasks;
-import brooklyn.entity.software.SshEffectorTasks;
-import brooklyn.location.MachineLocation;
-import brooklyn.util.net.Urls;
-import brooklyn.util.ssh.BashCommands;
-import brooklyn.util.task.DynamicTasks;
-import brooklyn.util.task.Tasks;
-import brooklyn.util.time.Duration;
-import brooklyn.util.time.Time;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Supplier;
-
-/**
- * Creates effectors to start, restart, and stop processes using SaltStack.
- * <p>
- * Instances of this should use the {@link SaltConfig} config attributes to configure startup,
- * and invoke {@link #usePidFile(String)} or {@link #useService(String)} to determine check-running and stop behaviour.
- * Alternatively this can be subclassed and {@link #postStartCustom()} and {@link #stopProcessesAtMachine()} overridden.
- *
- * @since 0.6.0
- */
-@Beta
-public class SaltLifecycleEffectorTasks extends MachineLifecycleEffectorTasks implements SaltConfig {
-
-    private static final Logger log = LoggerFactory.getLogger(SaltLifecycleEffectorTasks.class);
-
-    protected SaltStackMaster master = null;
-    protected String pidFile, serviceName, windowsServiceName;
-
-    public SaltLifecycleEffectorTasks() {
-    }
-
-    public SaltLifecycleEffectorTasks usePidFile(String pidFile) {
-        this.pidFile = pidFile;
-        return this;
-    }
-    public SaltLifecycleEffectorTasks useService(String serviceName) {
-        this.serviceName = serviceName;
-        return this;
-    }
-    public SaltLifecycleEffectorTasks useWindowsService(String serviceName) {
-        this.windowsServiceName = serviceName;
-        return this;
-    }
-    public SaltLifecycleEffectorTasks master(SaltStackMaster master) {
-        this.master = master;
-        return this;
-    }
-
-    @Override
-    public void attachLifecycleEffectors(Entity entity) {
-        if (pidFile==null && serviceName==null && getClass().equals(SaltLifecycleEffectorTasks.class)) {
-            // warn on incorrect usage
-            log.warn("Uses of "+getClass()+" must define a PID file or a service name (or subclass and override {start,stop} methods as per javadoc) " +
-                    "in order for check-running and stop to work");
-        }
-
-        super.attachLifecycleEffectors(entity);
-    }
-
-    @Override
-    protected String startProcessesAtMachine(Supplier<MachineLocation> machineS) {
-        startMinionAsync();
-        return "salt start tasks submitted";
-    }
-
-    protected void startMinionAsync() {
-        // TODO make directories more configurable (both for ssh-drivers and for this)
-        String installDir = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir(entity().getManagementContext()), "salt-install");
-        String runDir = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir(entity().getManagementContext()),
-                "apps/"+entity().getApplicationId()+"/salt-entities/"+entity().getId());
-
-        Boolean masterless = entity().getConfig(SaltConfig.MASTERLESS_MODE);
-        if (masterless) {
-            DynamicTasks.queue(
-                    SaltTasks.installFormulas(installDir, SaltConfigs.getRequiredConfig(entity(), SALT_FORMULAS), false),
-                    SaltTasks.buildSaltFile(runDir,
-                            SaltConfigs.getRequiredConfig(entity(), SALT_RUN_LIST),
-                            entity().getConfig(SALT_LAUNCH_ATTRIBUTES)),
-                    SaltTasks.installSaltMinion(entity(), runDir, installDir, false),
-                    SaltTasks.runSalt(runDir));
-        } else {
-            throw new UnsupportedOperationException("Salt master mode not yet supported for minions");
-        }
-    }
-
-    @Override
-    protected void postStartCustom() {
-        boolean result = false;
-        result |= tryCheckStartPid();
-        result |= tryCheckStartService();
-        result |= tryCheckStartWindowsService();
-        if (!result) {
-            throw new IllegalStateException("The process for "+entity()+" appears not to be running (no way to check!)");
-        }
-    }
-
-    protected boolean tryCheckStartPid() {
-        if (pidFile==null) return false;
-
-        // if it's still up after 5s assume we are good (default behaviour)
-        Time.sleep(Duration.FIVE_SECONDS);
-        if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(pidFile).runAsRoot()).get()) {
-            throw new IllegalStateException("The process for "+entity()+" appears not to be running (pid file "+pidFile+")");
-        }
-
-        // and set the PID
-        entity().setAttribute(Attributes.PID,
-                Integer.parseInt(DynamicTasks.queue(SshEffectorTasks.ssh("cat "+pidFile).runAsRoot()).block().getStdout().trim()));
-        return true;
-    }
-
-    protected boolean tryCheckStartService() {
-        if (serviceName==null) return false;
-
-        // if it's still up after 5s assume we are good (default behaviour)
-        Time.sleep(Duration.FIVE_SECONDS);
-        if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+serviceName+" status").runAsRoot()).get())) {
-            throw new IllegalStateException("The process for "+entity()+" appears not to be running (service "+serviceName+")");
-        }
-
-        return true;
-    }
-
-    protected boolean tryCheckStartWindowsService() {
-        if (windowsServiceName==null) return false;
-
-        // if it's still up after 5s assume we are good (default behaviour)
-        Time.sleep(Duration.FIVE_SECONDS);
-        if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("sc query \""+serviceName+"\" | find \"RUNNING\"").runAsCommand()).get())) {
-            throw new IllegalStateException("The process for "+entity()+" appears not to be running (windowsService "+windowsServiceName+")");
-        }
-
-        return true;
-    }
-
-    @Override
-    protected String stopProcessesAtMachine() {
-        boolean result = false;
-        result |= tryStopService();
-        result |= tryStopPid();
-        if (!result) {
-            throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (no impl!)");
-        }
-        return "stopped";
-    }
-
-    protected boolean tryStopService() {
-        if (serviceName==null) return false;
-        int result = DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+serviceName+" stop").runAsRoot()).get();
-        if (0==result) return true;
-        if (entity().getAttribute(Attributes.SERVICE_STATE)!=Lifecycle.RUNNING)
-            return true;
-
-        throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)");
-    }
-
-    protected boolean tryStopPid() {
-        Integer pid = entity().getAttribute(Attributes.PID);
-        if (pid==null) {
-            if (entity().getAttribute(Attributes.SERVICE_STATE)==Lifecycle.RUNNING && pidFile==null)
-                log.warn("No PID recorded for "+entity()+" when running, with PID file "+pidFile+"; skipping kill in "+Tasks.current());
-            else
-                if (log.isDebugEnabled())
-                    log.debug("No PID recorded for "+entity()+"; skipping ("+entity().getAttribute(Attributes.SERVICE_STATE)+" / "+pidFile+")");
-            return false;
-        }
-
-        // allow non-zero exit as process may have already been killed
-        DynamicTasks.queue(SshEffectorTasks.ssh(
-                "kill "+pid, "sleep 5", BashCommands.ok("kill -9 "+pid)).allowingNonZeroExitCode().runAsRoot()).block();
-
-        if (DynamicTasks.queue(SshEffectorTasks.isPidRunning(pid).runAsRoot()).get()) {
-            throw new IllegalStateException("Process for "+entity()+" in "+pid+" still running after kill");
-        }
-        entity().setAttribute(Attributes.PID, null);
-        return true;
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * @return the Salt master entity if it exists.
-     * @see #master(SaltStackMaster)
-     * @see SaltConfig#MASTERLESS_MODE
-     */
-    @Override
-    public SaltStackMaster getMaster() {
-        return master;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMaster.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMaster.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMaster.java
deleted file mode 100644
index 0db1841..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMaster.java
+++ /dev/null
@@ -1,70 +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.salt;
-
-import java.util.List;
-
-import org.apache.brooklyn.catalog.Catalog;
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.BrooklynConfigKeys;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.proxying.ImplementedBy;
-import brooklyn.event.AttributeSensor;
-import brooklyn.event.basic.BasicAttributeSensor;
-import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
-import brooklyn.util.flags.SetFromFlag;
-
-import com.google.common.reflect.TypeToken;
-
-@ImplementedBy(SaltStackMasterImpl.class)
-@Catalog(name="SaltStack Master", description="The Salt master server")
-public interface SaltStackMaster extends SoftwareProcess {
-
-    @SetFromFlag("version")
-    ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(BrooklynConfigKeys.SUGGESTED_VERSION, "stable");
-
-    @SetFromFlag("bootstrapUrl")
-    ConfigKey<String> BOOTSTRAP_URL = ConfigKeys.newStringConfigKey(
-            "salt.bootstrap.url", "The URL that returns the Salt boostrap commands",
-            "http://bootstrap.saltstack.org/");
-
-    @SetFromFlag("masterUser")
-    ConfigKey<String> MASTER_USER = ConfigKeys.newStringConfigKey(
-            "salt.master.user", "The user that runs the Salt master daemon process",
-            "root");
-
-    @SetFromFlag("masterConfigTemplate")
-    ConfigKey<String> MASTER_CONFIG_TEMPLATE_URL = ConfigKeys.newStringConfigKey(
-            "salt.master.config.templateUrl", "The template for the Salt master configuration (URL)",
-            "classpath:///brooklyn/entity/salt/master");
-
-    @SetFromFlag("saltPort")
-    PortAttributeSensorAndConfigKey SALT_PORT = new PortAttributeSensorAndConfigKey(
-            "salt.port", "Port used for communication between Salt master and minion processes", "4506+");
-
-    @SetFromFlag("publishPort")
-    PortAttributeSensorAndConfigKey PUBLISH_PORT = new PortAttributeSensorAndConfigKey(
-            "salt.publish.port", "Port used by the Salt master publisher", "4505+");
-
-    @SuppressWarnings("serial")
-    AttributeSensor<List<String>> MINION_IDS = new BasicAttributeSensor<List<String>>(new TypeToken<List<String>>() {},
-            "salt.minions", "List of Salt minion IDs");
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterDriver.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterDriver.java
deleted file mode 100644
index 55d46da..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterDriver.java
+++ /dev/null
@@ -1,25 +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.salt;
-
-import brooklyn.entity.basic.SoftwareProcessDriver;
-
-public interface SaltStackMasterDriver extends SoftwareProcessDriver {
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterImpl.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterImpl.java
deleted file mode 100644
index 2a6815c..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterImpl.java
+++ /dev/null
@@ -1,56 +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.salt;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.SoftwareProcessImpl;
-import brooklyn.event.feed.ConfigToAttributes;
-
-public class SaltStackMasterImpl extends SoftwareProcessImpl implements SaltStackMaster {
-
-    private static final Logger log = LoggerFactory.getLogger(SaltStackMasterImpl.class);
-
-    public SaltStackMasterImpl() {
-        super();
-    }
-
-    @Override
-    public Class getDriverInterface() {
-        return SaltStackMasterDriver.class;
-    }
-
-    @Override
-    protected void connectSensors() {
-        super.connectSensors();
-
-        // TODO what sensors should we poll?
-        ConfigToAttributes.apply(this);
-
-        connectServiceUpIsRunning();
-    }
-
-    @Override
-    protected void disconnectSensors() {
-        disconnectServiceUpIsRunning();
-
-        super.disconnectSensors();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterSshDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterSshDriver.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterSshDriver.java
deleted file mode 100644
index 8a326e7..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltStackMasterSshDriver.java
+++ /dev/null
@@ -1,95 +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.salt;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
-import brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.ssh.BashCommands;
-import brooklyn.util.task.DynamicTasks;
-
-import com.google.common.collect.ImmutableMap;
-
-public class SaltStackMasterSshDriver extends JavaSoftwareProcessSshDriver implements SaltStackMasterDriver {
-
-    public SaltStackMasterSshDriver(SaltStackMasterImpl entity, SshMachineLocation machine) {
-        super(entity, machine);
-    }
-
-    @Override
-    public SaltStackMasterImpl getEntity() {
-        return (SaltStackMasterImpl) super.getEntity();
-    }
-
-    @Override
-    protected String getLogFileLocation() {
-        return "master.log";
-    }
-
-    private String getPidFile() {
-        return "master.pid";
-    }
-
-    @Override
-    public void install() {
-        String url = Entities.getRequiredUrlConfig(getEntity(), SaltStackMaster.BOOTSTRAP_URL);
-        copyTemplate(url, "/etc/salt/master");
-
-        // Copy the file contents to the remote machine
-//        DynamicTasks.queue(SshEffectorTasks.put("/tmp/cumulus.yaml").contents(contents)).get();
-
-        // Run Salt bootstrap task to install master
-        DynamicTasks.queue(SaltTasks.installSaltMaster(getEntity(), getRunDir(), true));
-
-
-        newScript("createInstallDir")
-                .body.append("mkdir -p "+getInstallDir())
-                .failOnNonZeroResultCode()
-                .execute();
-
-        newScript(INSTALLING).
-                failOnNonZeroResultCode().
-                body.append("").execute();
-    }
-
-    @Override
-    public void customize() {
-    }
-
-    @Override
-    public void launch() {
-        newScript(ImmutableMap.of("usePidFile", false), LAUNCHING)
-                .body.append(BashCommands.sudo("start salt-master"))
-                .execute();
-    }
-
-    @Override
-    public boolean isRunning() {
-        return newScript(ImmutableMap.of("usePidFile", false), CHECK_RUNNING)
-                .body.append(BashCommands.sudo("status salt-master"))
-                .execute() == 0;
-    }
-
-    @Override
-    public void stop() {
-        newScript(ImmutableMap.of("usePidFile", false), STOPPING)
-                .body.append(BashCommands.sudo("stop salt-master"))
-                .execute();
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltTasks.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltTasks.java b/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltTasks.java
deleted file mode 100644
index 5b4c4ee..0000000
--- a/sandbox/extra/src/main/java/brooklyn/entity/salt/SaltTasks.java
+++ /dev/null
@@ -1,145 +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.salt;
-
-import static brooklyn.util.ssh.BashCommands.INSTALL_CURL;
-import static brooklyn.util.ssh.BashCommands.INSTALL_TAR;
-import static brooklyn.util.ssh.BashCommands.INSTALL_UNZIP;
-import static brooklyn.util.ssh.BashCommands.downloadToStdout;
-import static brooklyn.util.ssh.BashCommands.sudo;
-
-import java.util.Map;
-
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.effector.EffectorTasks;
-import brooklyn.entity.software.SshEffectorTasks;
-import brooklyn.management.TaskFactory;
-import brooklyn.util.ResourceUtils;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.net.Urls;
-import brooklyn.util.ssh.BashCommands;
-import brooklyn.util.task.DynamicTasks;
-import brooklyn.util.task.Tasks;
-import brooklyn.util.text.TemplateProcessor;
-
-import com.google.common.annotations.Beta;
-
-@Beta
-public class SaltTasks {
-
-    public static TaskFactory<?> installSaltMaster(Entity master, String saltDirectory, boolean force) {
-        // TODO check on entity whether it is salt _server_
-        String boostrapUrl = master.getConfig(SaltStackMaster.BOOTSTRAP_URL);
-        String version = master.getConfig(SaltStackMaster.SUGGESTED_VERSION);
-        String installCmd = cdAndRun(saltDirectory,
-                BashCommands.chain(
-                        INSTALL_CURL,
-                        INSTALL_TAR,
-                        INSTALL_UNZIP,
-                        "( "+downloadToStdout(boostrapUrl) + " | " + sudo("sh -s -- -M -N "+version)+" )"));
-        if (!force) installCmd = BashCommands.alternatives("which salt-master", installCmd);
-        return SshEffectorTasks.ssh(installCmd).summary("install salt master");
-    }
-
-    public static TaskFactory<?> installSaltMinion(final Entity minion, final String runDir, final String installDir, final boolean force) {
-        return Tasks.<Void>builder().name("install minion").body(
-                new Runnable() {
-                    public void run() {
-                        // Setup bootstrap installation command for minion
-                        String boostrapUrl = minion.getConfig(SaltStackMaster.BOOTSTRAP_URL);
-                        String installCmd = cdAndRun(runDir, BashCommands.chain(
-                                INSTALL_CURL,
-                                INSTALL_TAR,
-                                INSTALL_UNZIP,
-                                "( "+downloadToStdout(boostrapUrl) + " | " + sudo("sh")+" )"));
-                        if (!force) installCmd = BashCommands.alternatives("which salt-minion", installCmd);
-
-                        // Process the minion configuration template
-                        Boolean masterless = minion.getConfig(SaltConfig.MASTERLESS_MODE);
-                        String url = masterless ? Entities.getRequiredUrlConfig(minion, SaltConfig.MASTERLESS_CONFIGURATION_URL)
-                                                : Entities.getRequiredUrlConfig(minion, SaltConfig.MINION_CONFIGURATION_URL);
-                        Map<String, Object> config = MutableMap.<String, Object>builder()
-                                .put("entity", minion)
-                                .put("runDir", runDir)
-                                .put("installDir", installDir)
-                                .put("formulas", minion.getConfig(SaltConfig.SALT_FORMULAS))
-                                .build();
-                        String contents = TemplateProcessor.processTemplateContents(new ResourceUtils(minion).getResourceAsString(url), config);
-
-                        // Copy the file contents to the remote machine and install/start salt-minion
-                        DynamicTasks.queue(
-                                SshEffectorTasks.ssh(installCmd),
-                                SshEffectorTasks.put("/tmp/minion")
-                                        .contents(contents)
-                                        .createDirectory(),
-                                SshEffectorTasks.ssh(sudo("mv /tmp/minion /etc/salt/minion")), // TODO clunky
-                                SshEffectorTasks.ssh(sudo("restart salt-minion"))
-                            );
-                    }
-                }).buildFactory();
-    }
-
-    public static TaskFactory<?> installFormulas(final String installDir, final Map<String,String> formulasAndUrls, final boolean force) {
-        return Tasks.<Void>builder().name("install formulas").body(
-                new Runnable() {
-                    public void run() {
-                        Entity e = EffectorTasks.findEntity();
-                        if (formulasAndUrls==null)
-                            throw new IllegalStateException("No formulas defined to install at "+e);
-                        for (String formula: formulasAndUrls.keySet())
-                            DynamicTasks.queue(installFormula(installDir, formula, formulasAndUrls.get(formula), force));
-                    }
-                }).buildFactory();
-    }
-
-    public static TaskFactory<?> installFormula(String installDir, String formula, String url, boolean force) {
-        return SshEffectorTasks.ssh(cdAndRun(installDir, SaltBashCommands.downloadAndExpandFormula(url, formula, force)))
-                .summary("install formula "+formula)
-                .requiringExitCodeZero();
-    }
-
-    protected static String cdAndRun(String targetDirectory, String command) {
-        return BashCommands.chain(
-                "mkdir -p "+targetDirectory,
-                "cd "+targetDirectory,
-                command);
-    }
-
-    public static TaskFactory<?> buildSaltFile(String runDir, Iterable<? extends String> runList, Map<String, Object> attributes) {
-        StringBuilder top =  new StringBuilder()
-                .append("base:\n")
-                .append("    '*':\n");
-        for (String run : runList) {
-            top.append("      - " + run + "\n");
-        }
-
-        return SshEffectorTasks.put(Urls.mergePaths(runDir, "base", "top.sls"))
-                .contents(top.toString())
-                .summary("build salt top file")
-                .createDirectory();
-    }
-
-    public static TaskFactory<?> runSalt(String runDir) {
-        return SshEffectorTasks.ssh(cdAndRun(runDir, BashCommands.sudo("salt-call state.highstate")))
-                .summary("run salt install")
-                .requiringExitCodeZero();
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java
new file mode 100644
index 0000000..66c7acb
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNodeSaltImpl.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.postgresql;
+
+import org.apache.brooklyn.entity.database.postgresql.PostgreSqlNodeSaltImpl;
+import org.apache.brooklyn.entity.salt.SaltConfig;
+import org.apache.brooklyn.entity.salt.SaltConfigs;
+import org.apache.brooklyn.entity.salt.SaltLifecycleEffectorTasks;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Effector;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.EffectorStartableImpl;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.database.postgresql.PostgreSqlNode;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.entity.software.SshEffectorTasks;
+import brooklyn.event.basic.DependentConfiguration;
+import brooklyn.event.feed.ssh.SshFeed;
+import brooklyn.event.feed.ssh.SshPollConfig;
+import brooklyn.location.Location;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.ssh.BashCommands;
+import brooklyn.util.task.DynamicTasks;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+public class PostgreSqlNodeSaltImpl extends EffectorStartableImpl implements PostgreSqlNode, SoftwareProcess {
+
+    private static final Logger LOG = LoggerFactory.getLogger(PostgreSqlNodeSaltImpl.class);
+
+    public static final Effector<String> EXECUTE_SCRIPT = Effectors.effector(String.class, "executeScript")
+            .description("invokes a script")
+            .parameter(ExecuteScriptEffectorBody.SCRIPT)
+            .impl(new ExecuteScriptEffectorBody())
+            .build();
+
+    private SshFeed feed;
+
+    @Override
+    public void init() {
+        super.init();
+        new SaltPostgreSqlLifecycle().attachLifecycleEffectors(this);
+    }
+
+    public static class SaltPostgreSqlLifecycle extends SaltLifecycleEffectorTasks {
+        public SaltPostgreSqlLifecycle() {
+            usePidFile("/var/run/postgresql/*.pid");
+            useService("postgresql");
+        }
+
+        @Override
+        protected void startMinionAsync() {
+            Entities.warnOnIgnoringConfig(entity(), SaltConfig.SALT_FORMULAS);
+            Entities.warnOnIgnoringConfig(entity(), SaltConfig.SALT_RUN_LIST);
+            Entities.warnOnIgnoringConfig(entity(), SaltConfig.SALT_LAUNCH_ATTRIBUTES);
+
+            // TODO Set these as defaults, rather than replacing user's value!?
+            SaltConfigs.addToFormulas(entity(), "postgres", "https://github.com/saltstack-formulas/postgres-formula/archive/master.tar.gz");
+            SaltConfigs.addToRunList(entity(), "postgres");
+            SaltConfigs.addLaunchAttributes(entity(), ImmutableMap.<String,Object>builder()
+                    .put("port", DependentConfiguration.attributeWhenReady(entity(), PostgreSqlNode.POSTGRESQL_PORT))
+                    .put("listen_addresses", "*")
+                    .put("pg_hba.type", "host")
+                    .put("pg_hba.db", "all")
+                    .put("pg_hba.user", "all")
+                    .put("pg_hba.addr", "0.0.0.0/0")
+                    .put("pg_hba.method", "md5")
+                    .build());
+
+            super.startMinionAsync();
+        }
+
+        @Override
+        protected void postStartCustom() {
+            super.postStartCustom();
+
+            // now run the creation script
+            String creationScriptUrl = entity().getConfig(PostgreSqlNode.CREATION_SCRIPT_URL);
+            String creationScript;
+            if (creationScriptUrl != null) {
+                creationScript = new ResourceUtils(entity()).getResourceAsString(creationScriptUrl);
+            } else {
+                creationScript = entity().getConfig(PostgreSqlNode.CREATION_SCRIPT_CONTENTS);
+            }
+            entity().invoke(PostgreSqlNodeSaltImpl.EXECUTE_SCRIPT,
+                    ConfigBag.newInstance().configure(ExecuteScriptEffectorBody.SCRIPT, creationScript).getAllConfig()).getUnchecked();
+
+            // and finally connect sensors
+            ((PostgreSqlNodeSaltImpl) entity()).connectSensors();
+        }
+
+        @Override
+        protected void preStopCustom() {
+            ((PostgreSqlNodeSaltImpl) entity()).disconnectSensors();
+            super.preStopCustom();
+        }
+    }
+
+    public static class ExecuteScriptEffectorBody extends EffectorBody<String> {
+        public static final ConfigKey<String> SCRIPT = ConfigKeys.newStringConfigKey("script", "contents of script to run");
+
+        @Override
+        public String call(ConfigBag parameters) {
+            return DynamicTasks.queue(SshEffectorTasks.ssh(
+                    BashCommands.pipeTextTo(
+                            parameters.get(SCRIPT),
+                            BashCommands.sudoAsUser("postgres", "psql --file -")))
+                    .requiringExitCodeZero()).getStdout();
+        }
+    }
+
+    protected void connectSensors() {
+        setAttribute(DATASTORE_URL, String.format("postgresql://%s:%s/", getAttribute(HOSTNAME), getAttribute(POSTGRESQL_PORT)));
+
+        Location machine = Iterables.get(getLocations(), 0, null);
+
+        if (machine instanceof SshMachineLocation) {
+            feed = SshFeed.builder()
+                    .entity(this)
+                    .machine((SshMachineLocation)machine)
+                    .poll(new SshPollConfig<Boolean>(SERVICE_UP)
+                            .command("ps -ef | grep [p]ostgres")
+                            .setOnSuccess(true)
+                            .setOnFailureOrException(false))
+                    .build();
+        } else {
+            LOG.warn("Location(s) %s not an ssh-machine location, so not polling for status; setting serviceUp immediately", getLocations());
+        }
+    }
+
+    protected void disconnectSensors() {
+        if (feed != null) feed.stop();
+    }
+
+    @Override
+    public Integer getPostgreSqlPort() { return getAttribute(POSTGRESQL_PORT); }
+
+    @Override
+    public String getSharedMemory() { return getConfig(SHARED_MEMORY); }
+
+    @Override
+    public Integer getMaxConnections() { return getConfig(MAX_CONNECTIONS); }
+
+    @Override
+    public String getShortName() {
+        return "PostgreSQL";
+    }
+
+    @Override
+    public String executeScript(String commands) {
+        return Entities.invokeEffector(this, this, EXECUTE_SCRIPT,
+                ConfigBag.newInstance().configure(ExecuteScriptEffectorBody.SCRIPT, commands).getAllConfig()).getUnchecked();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltBashCommands.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltBashCommands.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltBashCommands.java
new file mode 100644
index 0000000..a9a07bf
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltBashCommands.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import static brooklyn.util.ssh.BashCommands.downloadToStdout;
+
+import javax.annotation.Nullable;
+
+import org.apache.commons.io.FilenameUtils;
+
+import brooklyn.entity.chef.ChefBashCommands;
+import brooklyn.util.ssh.BashCommands;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
+
+import com.google.common.annotations.Beta;
+import com.google.common.io.Files;
+
+/**
+ * BASH commands useful for setting up SaltStack.
+ */
+@Beta
+public class SaltBashCommands {
+
+    /**
+     * SaltStack formulas can be found at {@code https://github.com/saltstack-formulas} as repositories.
+     * <p>
+     * This assumes the download is an archive containing a single directory on the root which will
+     * be renamed to {@code formulaName}. if that directory already has the correct name {@code formulaName}
+     * can be null, but if taking from a GitHub tarball it will typically be of the form {@code formulaName-master/}
+     * hence the renaming.
+     */
+    // TODO support installing from classpath, and using the repository (tie in with those methods)
+    public static final String downloadAndExpandFormula(String source, @Nullable String formulaName, boolean force) {
+        String dl = downloadAndExpandFormula(source);
+        if (formulaName==null) return dl;
+        String tmpName = "tmp-"+Strings.makeValidFilename(formulaName)+"-"+Identifiers.makeRandomId(4);
+        String installCmd = BashCommands.chain("mkdir "+tmpName, "cd "+tmpName, dl,
+                BashCommands.requireTest("`ls | wc -w` -eq 1", "The archive must contain exactly one directory"),
+                "FORMULA_EXPANDED_DIR=`ls`",
+                "mv $FORMULA_EXPANDED_DIR '../"+formulaName+"'",
+                "cd ..",
+                "rm -rf "+tmpName);
+        if (!force) return BashCommands.alternatives("ls "+formulaName, installCmd);
+        else return BashCommands.alternatives("rm -rf "+formulaName, installCmd);
+    }
+
+    /**
+     * Same as {@link #downloadAndExpandFormula(String, String)} with no formula name.
+     * <p>
+     * Equivalent to the following command, but substituting the given {@code sourceUrl}.
+     * <pre>{@code
+     * curl -f -L  https://github.com/saltstack-formulas/nginx-formula/archive/master.tar.gz | tar xvz
+     * }</pre>
+     */
+    public static final String downloadAndExpandFormula(String sourceUrl) {
+        String ext = Files.getFileExtension(sourceUrl);
+        if ("tar".equalsIgnoreCase(ext))
+            return downloadToStdout(sourceUrl) + " | tar xv";
+        if ("tgz".equalsIgnoreCase(ext) || sourceUrl.toLowerCase().endsWith(".tar.gz"))
+            return downloadToStdout(sourceUrl) + " | tar xvz";
+
+        String target = FilenameUtils.getName(sourceUrl);
+        if (target==null) target = ""; else target = target.trim();
+        target += "_"+Strings.makeRandomId(4);
+
+        if ("zip".equalsIgnoreCase(ext) || "tar.gz".equalsIgnoreCase(ext))
+            return BashCommands.chain(
+                BashCommands.commandToDownloadUrlAs(sourceUrl, target),
+                "unzip "+target,
+                "rm "+target);
+
+        throw new UnsupportedOperationException("No way to expand "+sourceUrl+" (yet)");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfig.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfig.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfig.java
new file mode 100644
index 0000000..3b5962f
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfig.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.MapConfigKey;
+import brooklyn.event.basic.SetConfigKey;
+import brooklyn.management.TaskAdaptable;
+import brooklyn.management.TaskFactory;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * {@link ConfigKey}s used to configure Salt entities.
+ *
+ * @see SaltConfigs
+ * @see SaltLifecycleEffectorTasks
+ */
+@Beta
+public interface SaltConfig {
+
+    MapConfigKey<String> SALT_FORMULAS = new MapConfigKey<String>(String.class,
+            "salt.formulaUrls", "Map of Salt formula URLs (normally GutHub repository archives from the salt-formulas user)");
+    SetConfigKey<String> SALT_RUN_LIST = new SetConfigKey<String>(String.class,
+            "salt.runList", "Set of Salt states to install from the formula URLs");
+    MapConfigKey<Object> SALT_LAUNCH_ATTRIBUTES = new MapConfigKey<Object>(Object.class, "salt.launch.attributes", "TODO");
+
+    @SetFromFlag("master")
+    ConfigKey<SaltStackMaster> MASTER = ConfigKeys.newConfigKey(SaltStackMaster.class,
+            "salt.master.entity", "The Salt master server");
+
+    AttributeSensor<String> MINION_ID = new BasicAttributeSensor<String>(String.class,
+            "salt.minionId", "The ID for a Salt minion");
+
+    @SetFromFlag("masterless")
+    ConfigKey<Boolean> MASTERLESS_MODE = ConfigKeys.newBooleanConfigKey(
+            "salt.masterless", "Salt masterless, minion only configuration (default uses master and minion)",
+            Boolean.FALSE);
+
+    @SetFromFlag("masterConfigUrl")
+    ConfigKey<String> MASTER_CONFIGURATION_URL = ConfigKeys.newStringConfigKey(
+            "salt.master.templateUrl", "The Salt master configuration file template URL",
+            "classpath://org/apache/brooklyn/entity/salt/master");
+
+    @SetFromFlag("minionConfigUrl")
+    ConfigKey<String> MINION_CONFIGURATION_URL = ConfigKeys.newStringConfigKey(
+            "salt.minion.templateUrl", "The Salt minion configuration file template URL",
+            "classpath://org/apache/brooklyn/entity/salt/minion");
+
+    @SetFromFlag("masterlessConfigUrl")
+    ConfigKey<String> MASTERLESS_CONFIGURATION_URL = ConfigKeys.newStringConfigKey(
+            "salt.masterless.templateUrl", "The Salt minion masterless configuration file template URL",
+            "classpath://org/apache/brooklyn/entity/salt/masterless");
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @SetFromFlag("minionIdFunction")
+    ConfigKey<Function<Entity, String>> MINION_ID_FUNCTION = new BasicConfigKey(Function.class,
+            "salt.minionId.function", "Function to generate the ID of a Salt minion for an entity", Functions.toStringFunction());
+
+    @SuppressWarnings("serial")
+    ConfigKey<TaskFactory<? extends TaskAdaptable<Boolean>>> IS_RUNNING_TASK = ConfigKeys.newConfigKey(
+            new TypeToken<TaskFactory<? extends TaskAdaptable<Boolean>>>() {}, 
+            "salt.driver.isRunningTask");
+
+    @SuppressWarnings("serial")
+    ConfigKey<TaskFactory<?>> STOP_TASK = ConfigKeys.newConfigKey(
+            new TypeToken<TaskFactory<?>>() {}, 
+            "salt.driver.stopTask");
+
+    /**
+     * The {@link SaltStackMaster master} entity.
+     */
+    SaltStackMaster getMaster();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfigs.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfigs.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfigs.java
new file mode 100644
index 0000000..1622d85
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltConfigs.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import java.util.Map;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.event.basic.MapConfigKey.MapModifications;
+import brooklyn.event.basic.SetConfigKey.SetModifications;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+
+/**
+ * Conveniences for configuring brooklyn Salt entities 
+ *
+ * @since 0.6.0
+ */
+@Beta
+public class SaltConfigs {
+
+    public static void addToRunList(EntitySpec<?> entity, String...states) {
+        for (String state : states) {
+            entity.configure(SaltConfig.SALT_RUN_LIST, SetModifications.addItem(state));
+        }
+    }
+
+    public static void addToRunList(EntityInternal entity, String...states) {
+        for (String state : states) {
+            entity.setConfig(SaltConfig.SALT_RUN_LIST, SetModifications.addItem(state));
+        }
+    }
+
+    public static void addToFormuals(EntitySpec<?> entity, String formulaName, String formulaUrl) {
+        entity.configure(SaltConfig.SALT_FORMULAS.subKey(formulaName), formulaUrl);
+    }
+
+    public static void addToFormulas(EntityInternal entity, String formulaName, String formulaUrl) {
+        entity.setConfig(SaltConfig.SALT_FORMULAS.subKey(formulaName), formulaUrl);
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static void addLaunchAttributes(EntitySpec<?> entity, Map<? extends Object,? extends Object> attributesMap) {
+        entity.configure(SaltConfig.SALT_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap));
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static void addLaunchAttributes(EntityInternal entity, Map<? extends Object,? extends Object> attributesMap) {
+        entity.setConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES, MapModifications.add((Map)attributesMap));
+    }
+    
+    /** replaces the attributes underneath the rootAttribute parameter with the given value;
+     * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */
+    public static void setLaunchAttribute(EntitySpec<?> entity, String rootAttribute, Object value) {
+        entity.configure(SaltConfig.SALT_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value);
+    }
+    
+    /** replaces the attributes underneath the rootAttribute parameter with the given value;
+     * see {@link #addLaunchAttributesMap(EntitySpec, Map)} for richer functionality */
+    public static void setLaunchAttribute(EntityInternal entity, String rootAttribute, Object value) {
+        entity.setConfig(SaltConfig.SALT_LAUNCH_ATTRIBUTES.subKey(rootAttribute), value);
+    }
+
+    public static <T> T getRequiredConfig(Entity entity, ConfigKey<T> key) {
+        return Preconditions.checkNotNull(
+                Preconditions.checkNotNull(entity, "Entity must be supplied").getConfig(key), 
+                "Key "+key+" is required on "+entity);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java
new file mode 100644
index 0000000..7005db9
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltLifecycleEffectorTasks.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.BrooklynServerConfig;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Lifecycle;
+import brooklyn.entity.software.MachineLifecycleEffectorTasks;
+import brooklyn.entity.software.SshEffectorTasks;
+import brooklyn.location.MachineLocation;
+import brooklyn.util.net.Urls;
+import brooklyn.util.ssh.BashCommands;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Supplier;
+
+/**
+ * Creates effectors to start, restart, and stop processes using SaltStack.
+ * <p>
+ * Instances of this should use the {@link SaltConfig} config attributes to configure startup,
+ * and invoke {@link #usePidFile(String)} or {@link #useService(String)} to determine check-running and stop behaviour.
+ * Alternatively this can be subclassed and {@link #postStartCustom()} and {@link #stopProcessesAtMachine()} overridden.
+ *
+ * @since 0.6.0
+ */
+@Beta
+public class SaltLifecycleEffectorTasks extends MachineLifecycleEffectorTasks implements SaltConfig {
+
+    private static final Logger log = LoggerFactory.getLogger(SaltLifecycleEffectorTasks.class);
+
+    protected SaltStackMaster master = null;
+    protected String pidFile, serviceName, windowsServiceName;
+
+    public SaltLifecycleEffectorTasks() {
+    }
+
+    public SaltLifecycleEffectorTasks usePidFile(String pidFile) {
+        this.pidFile = pidFile;
+        return this;
+    }
+    public SaltLifecycleEffectorTasks useService(String serviceName) {
+        this.serviceName = serviceName;
+        return this;
+    }
+    public SaltLifecycleEffectorTasks useWindowsService(String serviceName) {
+        this.windowsServiceName = serviceName;
+        return this;
+    }
+    public SaltLifecycleEffectorTasks master(SaltStackMaster master) {
+        this.master = master;
+        return this;
+    }
+
+    @Override
+    public void attachLifecycleEffectors(Entity entity) {
+        if (pidFile==null && serviceName==null && getClass().equals(SaltLifecycleEffectorTasks.class)) {
+            // warn on incorrect usage
+            log.warn("Uses of "+getClass()+" must define a PID file or a service name (or subclass and override {start,stop} methods as per javadoc) " +
+                    "in order for check-running and stop to work");
+        }
+
+        super.attachLifecycleEffectors(entity);
+    }
+
+    @Override
+    protected String startProcessesAtMachine(Supplier<MachineLocation> machineS) {
+        startMinionAsync();
+        return "salt start tasks submitted";
+    }
+
+    protected void startMinionAsync() {
+        // TODO make directories more configurable (both for ssh-drivers and for this)
+        String installDir = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir(entity().getManagementContext()), "salt-install");
+        String runDir = Urls.mergePaths(BrooklynServerConfig.getMgmtBaseDir(entity().getManagementContext()),
+                "apps/"+entity().getApplicationId()+"/salt-entities/"+entity().getId());
+
+        Boolean masterless = entity().getConfig(SaltConfig.MASTERLESS_MODE);
+        if (masterless) {
+            DynamicTasks.queue(
+                    SaltTasks.installFormulas(installDir, SaltConfigs.getRequiredConfig(entity(), SALT_FORMULAS), false),
+                    SaltTasks.buildSaltFile(runDir,
+                            SaltConfigs.getRequiredConfig(entity(), SALT_RUN_LIST),
+                            entity().getConfig(SALT_LAUNCH_ATTRIBUTES)),
+                    SaltTasks.installSaltMinion(entity(), runDir, installDir, false),
+                    SaltTasks.runSalt(runDir));
+        } else {
+            throw new UnsupportedOperationException("Salt master mode not yet supported for minions");
+        }
+    }
+
+    @Override
+    protected void postStartCustom() {
+        boolean result = false;
+        result |= tryCheckStartPid();
+        result |= tryCheckStartService();
+        result |= tryCheckStartWindowsService();
+        if (!result) {
+            throw new IllegalStateException("The process for "+entity()+" appears not to be running (no way to check!)");
+        }
+    }
+
+    protected boolean tryCheckStartPid() {
+        if (pidFile==null) return false;
+
+        // if it's still up after 5s assume we are good (default behaviour)
+        Time.sleep(Duration.FIVE_SECONDS);
+        if (!DynamicTasks.queue(SshEffectorTasks.isPidFromFileRunning(pidFile).runAsRoot()).get()) {
+            throw new IllegalStateException("The process for "+entity()+" appears not to be running (pid file "+pidFile+")");
+        }
+
+        // and set the PID
+        entity().setAttribute(Attributes.PID,
+                Integer.parseInt(DynamicTasks.queue(SshEffectorTasks.ssh("cat "+pidFile).runAsRoot()).block().getStdout().trim()));
+        return true;
+    }
+
+    protected boolean tryCheckStartService() {
+        if (serviceName==null) return false;
+
+        // if it's still up after 5s assume we are good (default behaviour)
+        Time.sleep(Duration.FIVE_SECONDS);
+        if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+serviceName+" status").runAsRoot()).get())) {
+            throw new IllegalStateException("The process for "+entity()+" appears not to be running (service "+serviceName+")");
+        }
+
+        return true;
+    }
+
+    protected boolean tryCheckStartWindowsService() {
+        if (windowsServiceName==null) return false;
+
+        // if it's still up after 5s assume we are good (default behaviour)
+        Time.sleep(Duration.FIVE_SECONDS);
+        if (!((Integer)0).equals(DynamicTasks.queue(SshEffectorTasks.ssh("sc query \""+serviceName+"\" | find \"RUNNING\"").runAsCommand()).get())) {
+            throw new IllegalStateException("The process for "+entity()+" appears not to be running (windowsService "+windowsServiceName+")");
+        }
+
+        return true;
+    }
+
+    @Override
+    protected String stopProcessesAtMachine() {
+        boolean result = false;
+        result |= tryStopService();
+        result |= tryStopPid();
+        if (!result) {
+            throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (no impl!)");
+        }
+        return "stopped";
+    }
+
+    protected boolean tryStopService() {
+        if (serviceName==null) return false;
+        int result = DynamicTasks.queue(SshEffectorTasks.ssh("/etc/init.d/"+serviceName+" stop").runAsRoot()).get();
+        if (0==result) return true;
+        if (entity().getAttribute(Attributes.SERVICE_STATE)!=Lifecycle.RUNNING)
+            return true;
+
+        throw new IllegalStateException("The process for "+entity()+" appears could not be stopped (exit code "+result+" to service stop)");
+    }
+
+    protected boolean tryStopPid() {
+        Integer pid = entity().getAttribute(Attributes.PID);
+        if (pid==null) {
+            if (entity().getAttribute(Attributes.SERVICE_STATE)==Lifecycle.RUNNING && pidFile==null)
+                log.warn("No PID recorded for "+entity()+" when running, with PID file "+pidFile+"; skipping kill in "+Tasks.current());
+            else
+                if (log.isDebugEnabled())
+                    log.debug("No PID recorded for "+entity()+"; skipping ("+entity().getAttribute(Attributes.SERVICE_STATE)+" / "+pidFile+")");
+            return false;
+        }
+
+        // allow non-zero exit as process may have already been killed
+        DynamicTasks.queue(SshEffectorTasks.ssh(
+                "kill "+pid, "sleep 5", BashCommands.ok("kill -9 "+pid)).allowingNonZeroExitCode().runAsRoot()).block();
+
+        if (DynamicTasks.queue(SshEffectorTasks.isPidRunning(pid).runAsRoot()).get()) {
+            throw new IllegalStateException("Process for "+entity()+" in "+pid+" still running after kill");
+        }
+        entity().setAttribute(Attributes.PID, null);
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return the Salt master entity if it exists.
+     * @see #master(SaltStackMaster)
+     * @see SaltConfig#MASTERLESS_MODE
+     */
+    @Override
+    public SaltStackMaster getMaster() {
+        return master;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMaster.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMaster.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMaster.java
new file mode 100644
index 0000000..8ce2e3b
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMaster.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import java.util.List;
+
+import org.apache.brooklyn.catalog.Catalog;
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.BrooklynConfigKeys;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.SoftwareProcess;
+import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.reflect.TypeToken;
+
+@ImplementedBy(SaltStackMasterImpl.class)
+@Catalog(name="SaltStack Master", description="The Salt master server")
+public interface SaltStackMaster extends SoftwareProcess {
+
+    @SetFromFlag("version")
+    ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(BrooklynConfigKeys.SUGGESTED_VERSION, "stable");
+
+    @SetFromFlag("bootstrapUrl")
+    ConfigKey<String> BOOTSTRAP_URL = ConfigKeys.newStringConfigKey(
+            "salt.bootstrap.url", "The URL that returns the Salt boostrap commands",
+            "http://bootstrap.saltstack.org/");
+
+    @SetFromFlag("masterUser")
+    ConfigKey<String> MASTER_USER = ConfigKeys.newStringConfigKey(
+            "salt.master.user", "The user that runs the Salt master daemon process",
+            "root");
+
+    @SetFromFlag("masterConfigTemplate")
+    ConfigKey<String> MASTER_CONFIG_TEMPLATE_URL = ConfigKeys.newStringConfigKey(
+            "salt.master.config.templateUrl", "The template for the Salt master configuration (URL)",
+            "classpath://org/apache/brooklyn/entity/salt/master");
+
+    @SetFromFlag("saltPort")
+    PortAttributeSensorAndConfigKey SALT_PORT = new PortAttributeSensorAndConfigKey(
+            "salt.port", "Port used for communication between Salt master and minion processes", "4506+");
+
+    @SetFromFlag("publishPort")
+    PortAttributeSensorAndConfigKey PUBLISH_PORT = new PortAttributeSensorAndConfigKey(
+            "salt.publish.port", "Port used by the Salt master publisher", "4505+");
+
+    @SuppressWarnings("serial")
+    AttributeSensor<List<String>> MINION_IDS = new BasicAttributeSensor<List<String>>(new TypeToken<List<String>>() {},
+            "salt.minions", "List of Salt minion IDs");
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterDriver.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterDriver.java
new file mode 100644
index 0000000..703213a
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterDriver.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import brooklyn.entity.basic.SoftwareProcessDriver;
+
+public interface SaltStackMasterDriver extends SoftwareProcessDriver {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterImpl.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterImpl.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterImpl.java
new file mode 100644
index 0000000..1adfd1a
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterImpl.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 org.apache.brooklyn.entity.salt;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.event.feed.ConfigToAttributes;
+
+public class SaltStackMasterImpl extends SoftwareProcessImpl implements SaltStackMaster {
+
+    private static final Logger log = LoggerFactory.getLogger(SaltStackMasterImpl.class);
+
+    public SaltStackMasterImpl() {
+        super();
+    }
+
+    @Override
+    public Class getDriverInterface() {
+        return SaltStackMasterDriver.class;
+    }
+
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+
+        // TODO what sensors should we poll?
+        ConfigToAttributes.apply(this);
+
+        connectServiceUpIsRunning();
+    }
+
+    @Override
+    protected void disconnectSensors() {
+        disconnectServiceUpIsRunning();
+
+        super.disconnectSensors();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterSshDriver.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterSshDriver.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterSshDriver.java
new file mode 100644
index 0000000..4b88acc
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltStackMasterSshDriver.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.ssh.BashCommands;
+import brooklyn.util.task.DynamicTasks;
+
+import com.google.common.collect.ImmutableMap;
+
+public class SaltStackMasterSshDriver extends JavaSoftwareProcessSshDriver implements SaltStackMasterDriver {
+
+    public SaltStackMasterSshDriver(SaltStackMasterImpl entity, SshMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    @Override
+    public SaltStackMasterImpl getEntity() {
+        return (SaltStackMasterImpl) super.getEntity();
+    }
+
+    @Override
+    protected String getLogFileLocation() {
+        return "master.log";
+    }
+
+    private String getPidFile() {
+        return "master.pid";
+    }
+
+    @Override
+    public void install() {
+        String url = Entities.getRequiredUrlConfig(getEntity(), SaltStackMaster.BOOTSTRAP_URL);
+        copyTemplate(url, "/etc/salt/master");
+
+        // Copy the file contents to the remote machine
+//        DynamicTasks.queue(SshEffectorTasks.put("/tmp/cumulus.yaml").contents(contents)).get();
+
+        // Run Salt bootstrap task to install master
+        DynamicTasks.queue(SaltTasks.installSaltMaster(getEntity(), getRunDir(), true));
+
+
+        newScript("createInstallDir")
+                .body.append("mkdir -p "+getInstallDir())
+                .failOnNonZeroResultCode()
+                .execute();
+
+        newScript(INSTALLING).
+                failOnNonZeroResultCode().
+                body.append("").execute();
+    }
+
+    @Override
+    public void customize() {
+    }
+
+    @Override
+    public void launch() {
+        newScript(ImmutableMap.of("usePidFile", false), LAUNCHING)
+                .body.append(BashCommands.sudo("start salt-master"))
+                .execute();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return newScript(ImmutableMap.of("usePidFile", false), CHECK_RUNNING)
+                .body.append(BashCommands.sudo("status salt-master"))
+                .execute() == 0;
+    }
+
+    @Override
+    public void stop() {
+        newScript(ImmutableMap.of("usePidFile", false), STOPPING)
+                .body.append(BashCommands.sudo("stop salt-master"))
+                .execute();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltTasks.java
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltTasks.java b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltTasks.java
new file mode 100644
index 0000000..5cec099
--- /dev/null
+++ b/sandbox/extra/src/main/java/org/apache/brooklyn/entity/salt/SaltTasks.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.salt;
+
+import static brooklyn.util.ssh.BashCommands.INSTALL_CURL;
+import static brooklyn.util.ssh.BashCommands.INSTALL_TAR;
+import static brooklyn.util.ssh.BashCommands.INSTALL_UNZIP;
+import static brooklyn.util.ssh.BashCommands.downloadToStdout;
+import static brooklyn.util.ssh.BashCommands.sudo;
+
+import java.util.Map;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.effector.EffectorTasks;
+import brooklyn.entity.software.SshEffectorTasks;
+import brooklyn.management.TaskFactory;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Urls;
+import brooklyn.util.ssh.BashCommands;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.text.TemplateProcessor;
+
+import com.google.common.annotations.Beta;
+
+@Beta
+public class SaltTasks {
+
+    public static TaskFactory<?> installSaltMaster(Entity master, String saltDirectory, boolean force) {
+        // TODO check on entity whether it is salt _server_
+        String boostrapUrl = master.getConfig(SaltStackMaster.BOOTSTRAP_URL);
+        String version = master.getConfig(SaltStackMaster.SUGGESTED_VERSION);
+        String installCmd = cdAndRun(saltDirectory,
+                BashCommands.chain(
+                        INSTALL_CURL,
+                        INSTALL_TAR,
+                        INSTALL_UNZIP,
+                        "( "+downloadToStdout(boostrapUrl) + " | " + sudo("sh -s -- -M -N "+version)+" )"));
+        if (!force) installCmd = BashCommands.alternatives("which salt-master", installCmd);
+        return SshEffectorTasks.ssh(installCmd).summary("install salt master");
+    }
+
+    public static TaskFactory<?> installSaltMinion(final Entity minion, final String runDir, final String installDir, final boolean force) {
+        return Tasks.<Void>builder().name("install minion").body(
+                new Runnable() {
+                    public void run() {
+                        // Setup bootstrap installation command for minion
+                        String boostrapUrl = minion.getConfig(SaltStackMaster.BOOTSTRAP_URL);
+                        String installCmd = cdAndRun(runDir, BashCommands.chain(
+                                INSTALL_CURL,
+                                INSTALL_TAR,
+                                INSTALL_UNZIP,
+                                "( "+downloadToStdout(boostrapUrl) + " | " + sudo("sh")+" )"));
+                        if (!force) installCmd = BashCommands.alternatives("which salt-minion", installCmd);
+
+                        // Process the minion configuration template
+                        Boolean masterless = minion.getConfig(SaltConfig.MASTERLESS_MODE);
+                        String url = masterless ? Entities.getRequiredUrlConfig(minion, SaltConfig.MASTERLESS_CONFIGURATION_URL)
+                                                : Entities.getRequiredUrlConfig(minion, SaltConfig.MINION_CONFIGURATION_URL);
+                        Map<String, Object> config = MutableMap.<String, Object>builder()
+                                .put("entity", minion)
+                                .put("runDir", runDir)
+                                .put("installDir", installDir)
+                                .put("formulas", minion.getConfig(SaltConfig.SALT_FORMULAS))
+                                .build();
+                        String contents = TemplateProcessor.processTemplateContents(new ResourceUtils(minion).getResourceAsString(url), config);
+
+                        // Copy the file contents to the remote machine and install/start salt-minion
+                        DynamicTasks.queue(
+                                SshEffectorTasks.ssh(installCmd),
+                                SshEffectorTasks.put("/tmp/minion")
+                                        .contents(contents)
+                                        .createDirectory(),
+                                SshEffectorTasks.ssh(sudo("mv /tmp/minion /etc/salt/minion")), // TODO clunky
+                                SshEffectorTasks.ssh(sudo("restart salt-minion"))
+                            );
+                    }
+                }).buildFactory();
+    }
+
+    public static TaskFactory<?> installFormulas(final String installDir, final Map<String,String> formulasAndUrls, final boolean force) {
+        return Tasks.<Void>builder().name("install formulas").body(
+                new Runnable() {
+                    public void run() {
+                        Entity e = EffectorTasks.findEntity();
+                        if (formulasAndUrls==null)
+                            throw new IllegalStateException("No formulas defined to install at "+e);
+                        for (String formula: formulasAndUrls.keySet())
+                            DynamicTasks.queue(installFormula(installDir, formula, formulasAndUrls.get(formula), force));
+                    }
+                }).buildFactory();
+    }
+
+    public static TaskFactory<?> installFormula(String installDir, String formula, String url, boolean force) {
+        return SshEffectorTasks.ssh(cdAndRun(installDir, SaltBashCommands.downloadAndExpandFormula(url, formula, force)))
+                .summary("install formula "+formula)
+                .requiringExitCodeZero();
+    }
+
+    protected static String cdAndRun(String targetDirectory, String command) {
+        return BashCommands.chain(
+                "mkdir -p "+targetDirectory,
+                "cd "+targetDirectory,
+                command);
+    }
+
+    public static TaskFactory<?> buildSaltFile(String runDir, Iterable<? extends String> runList, Map<String, Object> attributes) {
+        StringBuilder top =  new StringBuilder()
+                .append("base:\n")
+                .append("    '*':\n");
+        for (String run : runList) {
+            top.append("      - " + run + "\n");
+        }
+
+        return SshEffectorTasks.put(Urls.mergePaths(runDir, "base", "top.sls"))
+                .contents(top.toString())
+                .summary("build salt top file")
+                .createDirectory();
+    }
+
+    public static TaskFactory<?> runSalt(String runDir) {
+        return SshEffectorTasks.ssh(cdAndRun(runDir, BashCommands.sudo("salt-call state.highstate")))
+                .summary("run salt install")
+                .requiringExitCodeZero();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/resources/brooklyn/entity/salt/master
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/resources/brooklyn/entity/salt/master b/sandbox/extra/src/main/resources/brooklyn/entity/salt/master
deleted file mode 100644
index 72d7eb9..0000000
--- a/sandbox/extra/src/main/resources/brooklyn/entity/salt/master
+++ /dev/null
@@ -1,65 +0,0 @@
-[#ftl]
-#
-# 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.
-#
-# /etc/salt/master
-##
-
-#interface: 0.0.0.0
-#ipv6: False
-#publish_port: ${entity.publishPort,c} # 4505
-
-#user: root
-#max_open_files: 100000
-#worker_threads: 5
-ret_port: ${entity.saltPort,c} # 4506
-
-root_dir: /
-pidfile: ${driver.pidFile}
-pki_dir: ${runDir}/pki
-cachedir: ${runDir}/cache
-log_file: ${driver.logFileLocation}
-key_logfile: ${runDir}/key.log
-
-#verify_env: True
-#keep_jobs: 24
-
-#timeout: 5
-#loop_interval: 60
-
-output: nested
-color: False
-log_level: info
-log_level_logfile: debug # Debugging
-
-#job_cache: True
-#minion_data_cache: True
-
-#open_mode: False
-#auto_accept: False
-#autosign_file: autosign.conf
-#permissive_pki_access: False
-
-fileserver_backend:
-  - git
-
-gitfs_remotes:
-  - git://github.com/saltstack/salt-states.git
-  - git://github.com/saltstack-formulas/postgres-formula.git
-  - ${entity.remoteUrl}
-  # TODO iterate through formula URLs

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/070b5ca7/sandbox/extra/src/main/resources/brooklyn/entity/salt/masterless
----------------------------------------------------------------------
diff --git a/sandbox/extra/src/main/resources/brooklyn/entity/salt/masterless b/sandbox/extra/src/main/resources/brooklyn/entity/salt/masterless
deleted file mode 100644
index ba41c6e..0000000
--- a/sandbox/extra/src/main/resources/brooklyn/entity/salt/masterless
+++ /dev/null
@@ -1,53 +0,0 @@
-[#ftl]
-##
-#
-# 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.
-#
-# SaltStack Masterless Minion Configuration
-#
-# /etc/salt/minion
-##
-
-# Minion configuration
-id: ${entity.id}
-user: root
-backup_mode: minion
-
-# Directory settings
-root_dir: /
-pidfile: ${runDir}/salt-minion.pid
-pki_dir: ${runDir}/pki
-cachedir: ${runDir}/cache
-log_file: ${runDir}/minion.log
-key_logfile: ${runDir}/key.log
-
-file_client: local
-file_roots:
-  base:
-    - ${runDir}/base
-[#list formulas?keys as formulaName]
-    - ${installDir}/${formulaName}
-[/#list]
-
-#verify_env: True
-#cache_jobs: True # Debugging
-
-output: nested
-color: False
-log_level: info
-log_level_logfile: debug # Debugging