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:53 UTC

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

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 {
+
+}