brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [58/64] incubator-brooklyn git commit: brooklyn-software-database: add org.apache package prefix
Date Tue, 18 Aug 2015 11:01:13 GMT
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
deleted file mode 100644
index 5ff0175..0000000
--- a/software/database/src/main/java/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
+++ /dev/null
@@ -1,425 +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 static brooklyn.util.ssh.BashCommands.INSTALL_WGET;
-import static brooklyn.util.ssh.BashCommands.alternativesGroup;
-import static brooklyn.util.ssh.BashCommands.chainGroup;
-import static brooklyn.util.ssh.BashCommands.dontRequireTtyForSudo;
-import static brooklyn.util.ssh.BashCommands.executeCommandThenAsUserTeeOutputToFile;
-import static brooklyn.util.ssh.BashCommands.fail;
-import static brooklyn.util.ssh.BashCommands.ifExecutableElse0;
-import static brooklyn.util.ssh.BashCommands.ifExecutableElse1;
-import static brooklyn.util.ssh.BashCommands.installPackage;
-import static brooklyn.util.ssh.BashCommands.sudo;
-import static brooklyn.util.ssh.BashCommands.sudoAsUser;
-import static brooklyn.util.ssh.BashCommands.warn;
-import static java.lang.String.format;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.annotation.Nullable;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.database.DatastoreMixins;
-import brooklyn.entity.software.SshEffectorTasks;
-
-import org.apache.brooklyn.api.location.OsDetails;
-import org.apache.brooklyn.core.util.task.DynamicTasks;
-import org.apache.brooklyn.core.util.task.ssh.SshTasks;
-import org.apache.brooklyn.core.util.task.ssh.SshTasks.OnFailingTask;
-import org.apache.brooklyn.core.util.task.system.ProcessTaskWrapper;
-import org.apache.brooklyn.location.basic.SshMachineLocation;
-
-import brooklyn.util.collections.MutableList;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.net.Urls;
-import brooklyn.util.os.Os;
-import brooklyn.util.stream.Streams;
-import brooklyn.util.text.Identifiers;
-import brooklyn.util.text.StringFunctions;
-import brooklyn.util.text.Strings;
-
-import com.google.common.base.Charsets;
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.io.Files;
-
-/**
- * The SSH implementation of the {@link PostgreSqlDriver}.
- */
-public class PostgreSqlSshDriver extends AbstractSoftwareProcessSshDriver implements PostgreSqlDriver {
-
-    public static final Logger log = LoggerFactory.getLogger(PostgreSqlSshDriver.class);
-
-    public PostgreSqlSshDriver(PostgreSqlNodeImpl entity, SshMachineLocation machine) {
-        super(entity, machine);
-
-        entity.setAttribute(Attributes.LOG_FILE_LOCATION, getLogFile());
-    }
-
-    /*
-     * TODO this is much messier than we would like because postgres runs as user postgres,
-     * meaning the dirs must be RW by that user, and accessible (thus all parent paths),
-     * which may rule out putting it in a location used by the default user.
-     * Two irritating things:
-     * * currently we sometimes make up a different onbox base dir;
-     * * currently we put files to /tmp for staging
-     * Could investigate if it really needs to run as user postgres;
-     * could also see whether default user can be added to group postgres,
-     * and the run dir (and all parents) made accessible to group postgres.
-     */
-    @Override
-    public void install() {
-        String version = getEntity().getConfig(SoftwareProcess.SUGGESTED_VERSION);
-        String majorMinorVersion = version.substring(0, version.lastIndexOf("-"));
-        String shortVersion = majorMinorVersion.replace(".", "");
-
-        String altTarget = "/opt/brooklyn/postgres/";
-        String altInstallDir = Urls.mergePaths(altTarget, "install/"+majorMinorVersion);
-        
-        Iterable<String> pgctlLocations = ImmutableList.of(
-            altInstallDir+"/bin",
-            "/usr/lib/postgresql/"+majorMinorVersion+"/bin/",
-            "/opt/local/lib/postgresql"+shortVersion+"/bin/",
-            "/usr/pgsql-"+majorMinorVersion+"/bin",
-            "/usr/local/bin/",
-            "/usr/bin/",
-            "/bin/");
-
-        DynamicTasks.queueIfPossible(SshTasks.dontRequireTtyForSudo(getMachine(),
-            // sudo is absolutely required here, in customize we set user to postgres
-            OnFailingTask.FAIL)).orSubmitAndBlock();
-        DynamicTasks.waitForLast();
-
-        // Check whether we can find a usable pg_ctl, and if not install one
-        MutableList<String> findOrInstall = MutableList.<String>of()
-            .append("which pg_ctl")
-            .appendAll(Iterables.transform(pgctlLocations, StringFunctions.formatter("test -x %s/pg_ctl")))
-            .append(installPackage(ImmutableMap.of(
-                "yum", "postgresql"+shortVersion+" postgresql"+shortVersion+"-server",
-                "apt", "postgresql-"+majorMinorVersion,
-                "port", "postgresql"+shortVersion+" postgresql"+shortVersion+"-server"
-                ), null))
-                // due to impl of installPackage, it will not come to the line below I don't think
-                .append(warn(format("WARNING: failed to find or install postgresql %s binaries", majorMinorVersion)));
-
-        // Link to correct binaries folder (different versions of pg_ctl and psql don't always play well together)
-        MutableList<String> linkFromHere = MutableList.<String>of()
-            .append(ifExecutableElse1("pg_ctl", chainGroup(
-                "PG_EXECUTABLE=`which pg_ctl`",
-                "PG_DIR=`dirname $PG_EXECUTABLE`",
-                "echo 'found pg_ctl in '$PG_DIR' on path so linking PG bin/ to that dir'",
-                "ln -s $PG_DIR bin")))
-                .appendAll(Iterables.transform(pgctlLocations, givenDirIfFileExistsInItLinkToDir("pg_ctl", "bin")))
-                .append(fail(format("WARNING: failed to find postgresql %s binaries for pg_ctl, may already have another version installed; aborting", majorMinorVersion), 9));
-
-        newScript(INSTALLING)
-        .body.append(
-            dontRequireTtyForSudo(),
-            ifExecutableElse0("yum", getYumRepository(version, majorMinorVersion, shortVersion)),
-            ifExecutableElse0("apt-get", getAptRepository()),
-            "rm -f bin", // if left over from previous incomplete/failed install (not sure why that keeps happening!)
-            alternativesGroup(findOrInstall),
-            alternativesGroup(linkFromHere))
-            .failOnNonZeroResultCode()
-            .queue();
-        
-        // check that the proposed install dir is one that user postgres can access
-        if (DynamicTasks.queue(SshEffectorTasks.ssh(sudoAsUser("postgres", "ls "+getInstallDir())).allowingNonZeroExitCode()
-                .summary("check postgres user can access install dir")).asTask().getUnchecked()!=0) {
-            log.info("Postgres install dir "+getInstallDir()+" for "+getEntity()+" is not accessible to user 'postgres'; " + "using "+altInstallDir+" instead");
-            String newRunDir = Urls.mergePaths(altTarget, "apps", getEntity().getApplication().getId(), getEntity().getId());
-            if (DynamicTasks.queue(SshEffectorTasks.ssh("ls "+altInstallDir+"/pg_ctl").allowingNonZeroExitCode()
-                    .summary("check whether "+altInstallDir+" is set up")).asTask().getUnchecked()==0) {
-                // alt target already exists with binary; nothing to do for install
-            } else {
-                DynamicTasks.queue(SshEffectorTasks.ssh(
-                    "mkdir -p "+altInstallDir,
-                    "rm -rf '"+altInstallDir+"'",
-                    "mv "+getInstallDir()+" "+altInstallDir,
-                    "rm -rf '"+getInstallDir()+"'",
-                    "ln -s "+altInstallDir+" "+getInstallDir(),
-                    "mkdir -p " + newRunDir,
-                    "chown -R postgres:postgres "+altTarget).runAsRoot().requiringExitCodeZero()
-                    .summary("move install dir from user to postgres owned space"));
-            }
-            DynamicTasks.waitForLast();
-            setInstallDir(altInstallDir);
-            setRunDir(newRunDir);
-        }
-    }
-
-    private String getYumRepository(String version, String majorMinorVersion, String shortVersion) {
-        // postgres becomes available if you add the repos using an RPM such as
-        // http://yum.postgresql.org/9.3/redhat/rhel-6-i386/pgdg-centos93-9.3-1.noarch.rpm
-        // fedora, rhel, sl, and centos supported for RPM's
-
-        OsDetails osDetails = getMachine().getMachineDetails().getOsDetails();
-        String arch = osDetails.getArch();
-        String osMajorVersion = osDetails.getVersion();
-        String osName = osDetails.getName();
-
-        log.debug("postgres detecting yum information for "+getEntity()+" at "+getMachine()+": "+osName+", "+osMajorVersion+", "+arch);
-
-        if (osName==null) osName = ""; else osName = osName.toLowerCase();
-
-        if (osName.equals("ubuntu")) return "echo skipping yum repo setup as this is not an rpm environment";
-
-        if (osName.equals("rhel")) osName = "redhat";
-        else if (osName.equals("centos")) osName = "centos";
-        else if (osName.equals("sl") || osName.startsWith("scientific")) osName = "sl";
-        else if (osName.equals("fedora")) osName = "fedora";
-        else {
-            log.debug("insufficient OS family information '"+osName+"' for "+getMachine()+" when installing "+getEntity()+" (yum repos); treating as centos");
-            osName = "centos";
-        }
-
-        if (Strings.isBlank(arch)) {
-            log.warn("Insuffient architecture information '"+arch+"' for "+getMachine()+"when installing "+getEntity()+"; treating as x86_64");
-            arch = "x86_64";
-        }
-
-        if (Strings.isBlank(osMajorVersion)) {
-            if (osName.equals("fedora")) osMajorVersion = "20";
-            else osMajorVersion = "6";
-            log.warn("Insuffient OS version information '"+getMachine().getOsDetails().getVersion()+"' for "+getMachine()+"when installing "+getEntity()+" (yum repos); treating as "+osMajorVersion);
-        } else {
-            if (osMajorVersion.indexOf(".")>0) 
-                osMajorVersion = osMajorVersion.substring(0, osMajorVersion.indexOf('.'));
-        }
-
-        return chainGroup(
-                INSTALL_WGET,
-                sudo(format("wget http://yum.postgresql.org/%s/redhat/rhel-%s-%s/pgdg-%s%s-%s.noarch.rpm", majorMinorVersion, osMajorVersion, arch, osName, shortVersion, version)),
-                sudo(format("rpm -Uvh pgdg-%s%s-%s.noarch.rpm", osName, shortVersion, version))
-            );
-    }
-
-    private String getAptRepository() {
-        return chainGroup(
-                INSTALL_WGET,
-                "wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | sudo tee -a apt-key add -",
-                "echo \"deb http://apt.postgresql.org/pub/repos/apt/   $(sudo lsb_release --codename --short)-pgdg main\" | sudo tee -a /etc/apt/sources.list.d/postgresql.list"
-            );
-    }
-
-    private static Function<String, String> givenDirIfFileExistsInItLinkToDir(final String filename, final String linkToMake) {
-        return new Function<String, String>() {
-            public String apply(@Nullable String dir) {
-                return ifExecutableElse1(Urls.mergePaths(dir, filename),
-                    chainGroup("echo 'found "+filename+" in "+dir+" so linking to it in "+linkToMake+"'", "ln -s "+dir+" "+linkToMake));
-            }
-        };
-    }
-
-    @Override
-    public void customize() {
-        // Some OSes start postgres during package installation
-        DynamicTasks.queue(SshEffectorTasks.ssh(sudoAsUser("postgres", "/etc/init.d/postgresql stop")).allowingNonZeroExitCode()).get();
-
-        newScript(CUSTOMIZING)
-        .body.append(
-            sudo("mkdir -p " + getDataDir()),
-            sudo("chown postgres:postgres " + getDataDir()),
-            sudo("chmod 700 " + getDataDir()),
-            sudo("touch " + getLogFile()),
-            sudo("chown postgres:postgres " + getLogFile()),
-            sudo("touch " + getPidFile()),
-            sudo("chown postgres:postgres " + getPidFile()),
-            alternativesGroup(
-                chainGroup(format("test -e %s", getInstallDir() + "/bin/initdb"),
-                    sudoAsUser("postgres", getInstallDir() + "/bin/initdb -D " + getDataDir())),
-                    callPgctl("initdb", true)))
-                    .failOnNonZeroResultCode()
-                    .execute();
-
-        String configUrl = getEntity().getConfig(PostgreSqlNode.CONFIGURATION_FILE_URL);
-        if (Strings.isBlank(configUrl)) {
-            // http://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server
-            // If the same setting is listed multiple times, the last one wins.
-            DynamicTasks.queue(SshEffectorTasks.ssh(
-                executeCommandThenAsUserTeeOutputToFile(
-                    chainGroup(
-                        "echo \"listen_addresses = '*'\"",
-                        "echo \"port = " + getEntity().getPostgreSqlPort() +  "\"",
-                        "echo \"max_connections = " + getEntity().getMaxConnections() +  "\"",
-                        "echo \"shared_buffers = " + getEntity().getSharedMemory() +  "\"",
-                        "echo \"external_pid_file = '" + getPidFile() +  "'\""),
-                        "postgres", getDataDir() + "/postgresql.conf")));
-        } else {
-            String contents = processTemplate(configUrl);
-            DynamicTasks.queue(
-                SshEffectorTasks.put("/tmp/postgresql.conf").contents(contents),
-                SshEffectorTasks.ssh(sudoAsUser("postgres", "cp /tmp/postgresql.conf " + getDataDir() + "/postgresql.conf")));
-        }
-
-        String authConfigUrl = getEntity().getConfig(PostgreSqlNode.AUTHENTICATION_CONFIGURATION_FILE_URL);
-        if (Strings.isBlank(authConfigUrl)) {
-            DynamicTasks.queue(SshEffectorTasks.ssh(
-                // TODO give users control which hosts can connect and the authentication mechanism
-                executeCommandThenAsUserTeeOutputToFile("echo \"host all all 0.0.0.0/0 md5\"", "postgres", getDataDir() + "/pg_hba.conf")));
-        } else {
-            String contents = processTemplate(authConfigUrl);
-            DynamicTasks.queue(
-                SshEffectorTasks.put("/tmp/pg_hba.conf").contents(contents),
-                SshEffectorTasks.ssh(sudoAsUser("postgres", "cp /tmp/pg_hba.conf " + getDataDir() + "/pg_hba.conf")));
-        }
-
-        // Wait for commands to complete before running the creation script
-        DynamicTasks.waitForLast();
-
-        // Capture log file contents if there is an error configuring the database
-        try {
-            executeDatabaseCreationScript();
-        } catch (RuntimeException r) {
-            logTailOfPostgresLog();
-            throw Exceptions.propagate(r);
-        }
-
-        // Try establishing an external connection. If you get a "Connection refused...accepting TCP/IP connections
-        // on port 5432?" error then the port is probably closed. Check that the firewall allows external TCP/IP
-        // connections (netstat -nap). You can open a port with lokkit or by configuring the iptables.
-    }
-
-    protected void executeDatabaseCreationScript() {
-        if (copyDatabaseCreationScript()) {
-            newScript("running postgres creation script")
-            .body.append(
-                "cd " + getInstallDir(),
-                callPgctl("start", true),
-                sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) + " --file " + getRunDir() + "/creation-script.sql"),
-                callPgctl("stop", true))
-                .failOnNonZeroResultCode()
-                .execute();
-        }
-    }
-
-    private boolean installFile(InputStream contents, String destName) {
-        String uid = Identifiers.makeRandomId(8);
-        // TODO currently put in /tmp for staging, since run dir may not be accessible to ssh user
-        getMachine().copyTo(contents, "/tmp/"+destName+"_"+uid);
-        DynamicTasks.queueIfPossible(SshEffectorTasks.ssh(
-            "cd "+getRunDir(), 
-            "mv /tmp/"+destName+"_"+uid+" "+destName,
-            "chown postgres:postgres "+destName,
-            "chmod 644 "+destName)
-            .runAsRoot().requiringExitCodeZero())
-            .orSubmitAndBlock(getEntity()).andWaitForSuccess();
-        return true;
-    }
-    private boolean copyDatabaseCreationScript() {
-        InputStream creationScript = DatastoreMixins.getDatabaseCreationScript(entity);
-        if (creationScript==null)
-            return false;
-        return installFile(creationScript, "creation-script.sql");
-    }
-
-    public String getDataDir() {
-        return getRunDir() + "/data";
-    }
-
-    public String getLogFile() {
-        return getRunDir() + "/postgresql.log";
-    }
-
-    public String getPidFile() {
-        return getRunDir() + "/postgresql.pid";
-    }
-
-    /** @deprecated since 0.7.0 renamed {@link #logTailOfPostgresLog()} */
-    @Deprecated
-    public void copyLogFileContents() { logTailOfPostgresLog(); }
-    public void logTailOfPostgresLog() {
-        try {
-            File file = Os.newTempFile("postgresql-"+getEntity().getId(), "log");
-            int result = getMachine().copyFrom(getLogFile(), file.getAbsolutePath());
-            if (result != 0) throw new IllegalStateException("Could not access log file " + getLogFile());
-            log.info("Saving {} contents as {}", getLogFile(), file);
-            Streams.logStreamTail(log, "postgresql.log", Streams.byteArrayOfString(Files.toString(file, Charsets.UTF_8)), 1024);
-            file.delete();
-        } catch (IOException ioe) {
-            log.debug("Error reading copied log file: {}", ioe);
-        }
-    }
-
-    protected String callPgctl(String command, boolean waitForIt) {
-        return sudoAsUser("postgres", getInstallDir() + "/bin/pg_ctl -D " + getDataDir() +
-            " -l " + getLogFile() + (waitForIt ? " -w " : " ") + command);
-    }
-
-    @Override
-    public void launch() {
-        log.info(String.format("Starting entity %s at %s", this, getLocation()));
-        newScript(MutableMap.of("usePidFile", false), LAUNCHING)
-        .body.append(callPgctl("start", false))
-        .execute();
-    }
-
-    @Override
-    public boolean isRunning() {
-        return newScript(MutableMap.of("usePidFile", getPidFile()), CHECK_RUNNING)
-            .body.append(getStatusCmd())
-            .execute() == 0;
-    }
-
-    @Override
-    public void stop() {
-        newScript(MutableMap.of("usePidFile", false), STOPPING)
-        .body.append(callPgctl((entity.getConfig(PostgreSqlNode.DISCONNECT_ON_STOP) ? "-m immediate " : "") + "stop", false))
-        .failOnNonZeroResultCode()
-        .execute();
-        newScript(MutableMap.of("usePidFile", getPidFile(), "processOwner", "postgres"), STOPPING).execute();
-    }
-
-    @Override
-    public PostgreSqlNodeImpl getEntity() {
-        return (PostgreSqlNodeImpl) super.getEntity();
-    }
-
-    @Override
-    public String getStatusCmd() {
-        return callPgctl("status", false);
-    }
-
-    public ProcessTaskWrapper<Integer> executeScriptAsync(String commands) {
-        String filename = "postgresql-commands-"+Identifiers.makeRandomId(8);
-        installFile(Streams.newInputStreamWithContents(commands), filename);
-        return executeScriptFromInstalledFileAsync(filename);
-    }
-
-    public ProcessTaskWrapper<Integer> executeScriptFromInstalledFileAsync(String filenameAlreadyInstalledAtServer) {
-        return DynamicTasks.queue(
-            SshEffectorTasks.ssh(
-                "cd "+getRunDir(),
-                sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) + " --file " + filenameAlreadyInstalledAtServer))
-                .summary("executing datastore script "+filenameAlreadyInstalledAtServer));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepDriver.java b/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepDriver.java
deleted file mode 100644
index 163e37d..0000000
--- a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepDriver.java
+++ /dev/null
@@ -1,28 +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.rubyrep;
-
-import brooklyn.entity.basic.SoftwareProcessDriver;
-
-/**
- * The driver interface for {@link RubyRepNode}.
- */
-public interface RubyRepDriver extends SoftwareProcessDriver {
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepNode.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepNode.java b/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepNode.java
deleted file mode 100644
index edfbda5..0000000
--- a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepNode.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.rubyrep;
-
-import org.apache.brooklyn.api.catalog.Catalog;
-import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
-import org.apache.brooklyn.api.event.AttributeSensor;
-import org.apache.brooklyn.core.util.flags.SetFromFlag;
-
-import brooklyn.config.ConfigKey;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.ConfigKeys;
-import brooklyn.entity.basic.SoftwareProcess;
-import brooklyn.entity.database.DatastoreMixins;
-import brooklyn.entity.database.DatastoreMixins.DatastoreCommon;
-import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
-import brooklyn.event.basic.BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey;
-import brooklyn.event.basic.Sensors;
-
-@Catalog(name = "RubyRep Node", description = "RubyRep is a database replication system", iconUrl = "classpath:///rubyrep-logo.jpeg")
-@ImplementedBy(RubyRepNodeImpl.class)
-public interface RubyRepNode extends SoftwareProcess {
-
-    @SetFromFlag("version")
-    ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "1.2.0");
-
-    @SetFromFlag("downloadUrl")
-    public static final BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new StringAttributeSensorAndConfigKey(
-            Attributes.DOWNLOAD_URL, "http://files.rubyforge.vm.bytemark.co.uk/rubyrep/rubyrep-${version}.zip");
-
-    @SetFromFlag("configurationScriptUrl")
-    ConfigKey<String> CONFIGURATION_SCRIPT_URL = ConfigKeys.newStringConfigKey(
-            "database.rubyrep.configScriptUrl",
-            "URL where RubyRep configuration can be found - disables other configuration options (except version)");
-
-    @SetFromFlag("templateUrl")
-    ConfigKey<String> TEMPLATE_CONFIGURATION_URL = ConfigKeys.newStringConfigKey(
-            "database.rubyrep.templateConfigurationUrl", "Template file (in freemarker format) for the rubyrep.conf file",
-            "classpath://brooklyn/entity/database/rubyrep/rubyrep.conf");
-
-    @SetFromFlag("tables")
-    ConfigKey<String> TABLE_REGEXP = ConfigKeys.newStringConfigKey(
-            "database.rubyrep.tableRegex", "Regular expression to select tables to sync using RubyRep", ".");
-
-    @SetFromFlag("replicationInterval")
-    ConfigKey<Integer> REPLICATION_INTERVAL = ConfigKeys.newIntegerConfigKey(
-            "database.rubyrep.replicationInterval", "Replication Interval", 30);
-
-    @SetFromFlag("startupTimeout")
-    ConfigKey<Integer> DATABASE_STARTUP_TIMEOUT = ConfigKeys.newIntegerConfigKey(
-            "database.rubyrep.startupTimeout", "Time to wait until databases have started up (in seconds)", 120);
-
-    // Left database
-
-    AttributeSensor<String> LEFT_DATASTORE_URL = Sensors.newSensorWithPrefix("left", DatastoreMixins.DATASTORE_URL);
-
-    @SetFromFlag("leftDatabase")
-    ConfigKey<? extends DatastoreCommon> LEFT_DATABASE = ConfigKeys.newConfigKey(DatastoreCommon.class,
-            "database.rubyrep.leftDatabase", "Brooklyn database entity to use as the left DBMS");
-
-    @SetFromFlag("leftDatabaseName")
-    ConfigKey<String> LEFT_DATABASE_NAME = ConfigKeys.newStringConfigKey(
-            "database.rubyrep.leftDatabaseName", "name of database to use for left db");
-
-    @SetFromFlag("leftUsername")
-    ConfigKey<String> LEFT_USERNAME = ConfigKeys.newStringConfigKey(
-            "database.rubyrep.leftUsername", "username to connect to left db");
-
-    @SetFromFlag("leftPassword")
-    ConfigKey<String> LEFT_PASSWORD = ConfigKeys.newStringConfigKey(
-            "database.rubyrep.leftPassword", "password to connect to left db");
-
-    // Right database
-
-    AttributeSensor<String> RIGHT_DATASTORE_URL = Sensors.newSensorWithPrefix("right", DatastoreMixins.DATASTORE_URL);
-
-    @SetFromFlag("rightDatabase")
-    ConfigKey<? extends DatastoreCommon> RIGHT_DATABASE = ConfigKeys.newConfigKey(DatastoreCommon.class,
-            "database.rubyrep.rightDatabase", "Brooklyn database entity to use as the right DBMS");
-
-    @SetFromFlag("rightDatabaseName")
-    ConfigKey<String> RIGHT_DATABASE_NAME = ConfigKeys.newStringConfigKey(
-            "database.rubyrep.rightDatabaseName", "name of database to use for right db");
-
-    @SetFromFlag("rightUsername")
-    ConfigKey<String> RIGHT_USERNAME = ConfigKeys.newStringConfigKey(
-            "database.rubyrep.rightUsername", "username to connect to right db");
-
-    @SetFromFlag("rightPassword")
-    ConfigKey<String> RIGHT_PASSWORD = ConfigKeys.newStringConfigKey(
-            "database.rubyrep.rightPassword", "password to connect to right db");
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepNodeImpl.java b/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepNodeImpl.java
deleted file mode 100644
index b12b439..0000000
--- a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepNodeImpl.java
+++ /dev/null
@@ -1,111 +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.rubyrep;
-
-import java.net.URI;
-
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.SoftwareProcessImpl;
-import brooklyn.entity.database.DatastoreMixins.DatastoreCommon;
-import brooklyn.event.basic.DependentConfiguration;
-import brooklyn.util.time.Duration;
-
-public class RubyRepNodeImpl extends SoftwareProcessImpl implements RubyRepNode {
-
-    @Override
-    protected void connectSensors() {
-        super.connectSensors();
-        connectServiceUpIsRunning();
-    }
-
-    @Override
-    public void disconnectSensors() {
-        disconnectServiceUpIsRunning();
-        super.disconnectSensors();
-    }
-
-    /**
-     * Set the database {@link DatastoreCommon#DATASTORE_URL urls} as attributes when they become available on the entities.
-     */
-    @Override
-    protected void preStart() {
-        super.preStart();
-
-        DatastoreCommon leftNode = getConfig(LEFT_DATABASE);
-        if (leftNode != null) {
-            setAttribute(LEFT_DATASTORE_URL, Entities.submit(this, DependentConfiguration.attributeWhenReady(leftNode, DatastoreCommon.DATASTORE_URL)).getUnchecked(getDatabaseStartupDelay()));
-        }
-
-        DatastoreCommon rightNode = getConfig(RIGHT_DATABASE);
-        if (rightNode != null) {
-            setAttribute(RIGHT_DATASTORE_URL, Entities.submit(this, DependentConfiguration.attributeWhenReady(rightNode, DatastoreCommon.DATASTORE_URL)).getUnchecked(getDatabaseStartupDelay()));
-        }
-    }
-
-    @Override
-    public Class<?> getDriverInterface() {
-        return RubyRepDriver.class;
-    }
-
-    public Duration getDatabaseStartupDelay() {
-        return Duration.seconds(getConfig(DATABASE_STARTUP_TIMEOUT));
-    }
-
-    // Accessors used in freemarker template processing
-
-    public int getReplicationInterval() {
-        return getConfig(REPLICATION_INTERVAL);
-    }
-    
-    public String getTableRegex() {
-        return getConfig(TABLE_REGEXP);
-    }
-    
-    public URI getLeftDatabaseUrl() {
-        return URI.create(getAttribute(LEFT_DATASTORE_URL));
-    }
-    
-    public String getLeftDatabaseName() {
-        return getConfig(LEFT_DATABASE_NAME);
-    }
-
-    public String getLeftUsername() {
-        return getConfig(LEFT_USERNAME);
-    }
-
-    public String getLeftPassword() {
-        return getConfig(LEFT_PASSWORD);
-    }
-
-    public URI getRightDatabaseUrl() {
-        return URI.create(getAttribute(RIGHT_DATASTORE_URL));
-    }
-
-    public String getRightDatabaseName() {
-        return getConfig(RIGHT_DATABASE_NAME);
-    }
-
-    public String getRightUsername() {
-        return getConfig(RIGHT_USERNAME);
-    }
-
-    public String getRightPassword() {
-        return getConfig(RIGHT_PASSWORD);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java b/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java
deleted file mode 100644
index 8415dd6..0000000
--- a/software/database/src/main/java/brooklyn/entity/database/rubyrep/RubyRepSshDriver.java
+++ /dev/null
@@ -1,126 +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.rubyrep;
-
-import static java.lang.String.format;
-
-import java.io.Reader;
-import java.net.URISyntaxException;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-import org.apache.brooklyn.api.entity.basic.EntityLocal;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
-import brooklyn.entity.basic.Attributes;
-import brooklyn.entity.basic.Entities;
-import org.apache.brooklyn.location.basic.SshMachineLocation;
-import brooklyn.util.collections.MutableMap;
-import brooklyn.util.os.Os;
-import brooklyn.util.ssh.BashCommands;
-import brooklyn.util.stream.Streams;
-
-import com.google.common.collect.ImmutableList;
-
-public class RubyRepSshDriver extends AbstractSoftwareProcessSshDriver implements RubyRepDriver {
-
-    public static final Logger log = LoggerFactory.getLogger(RubyRepSshDriver.class);
-
-    public RubyRepSshDriver(EntityLocal entity, SshMachineLocation machine) {
-        super(entity, machine);
-
-        entity.setAttribute(Attributes.LOG_FILE_LOCATION, getLogFileLocation());
-    }
-
-    protected String getLogFileLocation() {
-        return Os.mergePaths(getRunDir(), "log", "rubyrep.log");
-    }
-
-    @Override
-    public void preInstall() {
-        resolver = Entities.newDownloader(this);
-        setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(format("rubyrep-%s", getVersion()))));
-    }
-
-    @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_UNZIP)
-                .add("unzip " + saveAs)
-                .build();
-
-        newScript(INSTALLING)
-                .body.append(commands)
-                .failOnNonZeroResultCode()
-                .execute();
-    }
-
-    @Override
-    public void customize() {
-        newScript(CUSTOMIZING)
-                .body.append(format("cp -R %s %s", getExpandedInstallDir(), getRunDir()))
-                .failOnNonZeroResultCode()
-                .execute();
-        try {
-            customizeConfiguration();
-        } catch (Exception e) {
-            log.error("Failed to configure rubyrep, replication is unlikely to succeed", e);
-        }
-    }
-
-    protected void customizeConfiguration() throws ExecutionException, InterruptedException, URISyntaxException {
-        log.info("Copying creation script " + getEntity().toString());
-
-        // TODO check these semantics are what we really want?
-        String configScriptUrl = entity.getConfig(RubyRepNode.CONFIGURATION_SCRIPT_URL);
-        Reader configContents;
-        if (configScriptUrl != null) {
-            // If set accept as-is
-            configContents = Streams.reader(resource.getResourceFromUrl(configScriptUrl));
-        } else {
-            String configScriptContents = processTemplate(entity.getConfig(RubyRepNode.TEMPLATE_CONFIGURATION_URL));
-            configContents = Streams.newReaderWithContents(configScriptContents);
-        }
-
-        getMachine().copyTo(configContents, getRunDir() + "/rubyrep.conf");
-    }
-
-    @Override
-    public void launch() {
-        newScript(MutableMap.of("usePidFile", true), LAUNCHING)
-                .body.append(format("nohup rubyrep-%s/jruby/bin/jruby rubyrep-%s/bin/rubyrep replicate -c rubyrep.conf > ./console 2>&1 &", getVersion(), getVersion()))
-                .execute();
-    }
-
-    @Override
-    public boolean isRunning() {
-        return newScript(MutableMap.of("usePidFile", true), CHECK_RUNNING).execute() == 0;
-    }
-
-    @Override
-    public void stop() {
-        newScript(MutableMap.of("usePidFile", true), STOPPING).execute();
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/DatabaseNode.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/DatabaseNode.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/DatabaseNode.java
new file mode 100644
index 0000000..56eb9b7
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/DatabaseNode.java
@@ -0,0 +1,29 @@
+/*
+ * 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.apache.brooklyn.api.event.AttributeSensor;
+
+/** @deprecated since 0.7.0 use DatastoreMixins.DatastoreCommon */ @Deprecated 
+public interface DatabaseNode extends DatastoreMixins.DatastoreCommon {
+
+    /** @deprecated since 0.7.0 use DATASTORE_URL */ @Deprecated 
+    public static final AttributeSensor<String> DB_URL = DATASTORE_URL;
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/DatastoreMixins.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/DatastoreMixins.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/DatastoreMixins.java
new file mode 100644
index 0000000..fee8f02
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/DatastoreMixins.java
@@ -0,0 +1,104 @@
+/*
+ * 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 java.io.InputStream;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Effector;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.core.util.ResourceUtils;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.effector.Effectors;
+import brooklyn.event.basic.Sensors;
+import brooklyn.util.stream.KnownSizeInputStream;
+import brooklyn.util.text.Strings;
+
+public class DatastoreMixins {
+
+    private DatastoreMixins() {}
+    
+    
+    public static final AttributeSensor<String> DATASTORE_URL = HasDatastoreUrl.DATASTORE_URL;
+    
+    public static interface HasDatastoreUrl {
+        public static final AttributeSensor<String> DATASTORE_URL = Sensors.newStringSensor("datastore.url",
+            "Primary contact URL for a datastore (e.g. mysql://localhost:3306/)");
+    }
+
+    
+    public static final Effector<String> EXECUTE_SCRIPT = CanExecuteScript.EXECUTE_SCRIPT;
+    
+    public static interface CanExecuteScript {
+        public static final Effector<String> EXECUTE_SCRIPT = Effectors.effector(String.class, "executeScript")
+            .description("executes the given script contents")
+            .parameter(String.class, "commands")
+            .buildAbstract();
+    }
+
+    
+    public static final ConfigKey<String> CREATION_SCRIPT_CONTENTS = CanGiveCreationScript.CREATION_SCRIPT_CONTENTS; 
+    public static final ConfigKey<String> CREATION_SCRIPT_URL = CanGiveCreationScript.CREATION_SCRIPT_URL; 
+
+    public static interface CanGiveCreationScript {
+        @SetFromFlag("creationScriptContents")
+        public static final ConfigKey<String> CREATION_SCRIPT_CONTENTS = ConfigKeys.newStringConfigKey(
+                "datastore.creation.script.contents",
+                "Contents of creation script to initialize the datastore",
+                "");
+        
+        @SetFromFlag("creationScriptUrl")
+        public static final ConfigKey<String> CREATION_SCRIPT_URL = ConfigKeys.newStringConfigKey(
+                "datastore.creation.script.url",
+                "URL of creation script to use to initialize the datastore (ignored if creationScriptContents is specified)",
+                "");
+    }
+
+    /** returns the creation script contents, if it exists, or null if none is defined (error if it cannot be loaded) */
+    @Nullable public static InputStream getDatabaseCreationScript(Entity entity) {
+        String url = entity.getConfig(DatastoreMixins.CREATION_SCRIPT_URL);
+        if (!Strings.isBlank(url))
+            return new ResourceUtils(entity).getResourceFromUrl(url);
+        String contents = entity.getConfig(DatastoreMixins.CREATION_SCRIPT_CONTENTS);
+        if (!Strings.isBlank(contents))
+            return KnownSizeInputStream.of(contents);
+        return null;
+    }
+
+    /** returns the creation script contents, if it exists, or null if none is defined (error if it cannot be loaded) */
+    @Nullable public static String getDatabaseCreationScriptAsString(Entity entity) {
+        String url = entity.getConfig(DatastoreMixins.CREATION_SCRIPT_URL);
+        if (!Strings.isBlank(url))
+            return new ResourceUtils(entity).getResourceAsString(url);
+        String contents = entity.getConfig(DatastoreMixins.CREATION_SCRIPT_CONTENTS);
+        if (!Strings.isBlank(contents))
+            return contents;
+        return null;
+    }
+    
+    /** An entity with the most common datastore config, sensors, and effectors */ 
+    public interface DatastoreCommon extends Entity, DatastoreMixins.HasDatastoreUrl, DatastoreMixins.CanExecuteScript, DatastoreMixins.CanGiveCreationScript {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNode.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNode.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNode.java
new file mode 100644
index 0000000..264611e
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNode.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.database.crate;
+
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.SoftwareProcess;
+import org.apache.brooklyn.entity.database.DatastoreMixins.DatastoreCommon;
+import brooklyn.entity.java.UsesJava;
+import brooklyn.entity.java.UsesJavaMXBeans;
+import brooklyn.entity.java.UsesJmx;
+import brooklyn.event.basic.AttributeSensorAndConfigKey;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.event.basic.Sensors;
+
+import org.apache.brooklyn.location.basic.PortRanges;
+
+@ImplementedBy(CrateNodeImpl.class)
+public interface CrateNode extends SoftwareProcess, UsesJava,UsesJmx, UsesJavaMXBeans, DatastoreCommon {
+
+    @SetFromFlag("version")
+    ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION,
+            "0.45.7");
+
+    @SetFromFlag("downloadUrl")
+    AttributeSensorAndConfigKey<String, String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey(
+            Attributes.DOWNLOAD_URL,
+            "https://cdn.crate.io/downloads/releases/crate-${version}.tar.gz");
+
+    @SetFromFlag("serverConfig")
+    BasicAttributeSensorAndConfigKey<String> SERVER_CONFIG_URL = new BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey(
+            "crate.serverConfig", "A URL of a YAML file to use to configure the server",
+            "classpath://org/apache/brooklyn/entity/database/crate/crate.yaml");
+
+    @SetFromFlag("port")
+    public static final PortAttributeSensorAndConfigKey CRATE_PORT = new PortAttributeSensorAndConfigKey(
+            "crate.port", "The port for node-to-node communication", PortRanges.fromString("4300+"));
+
+    @SetFromFlag("httpPort")
+    public static final PortAttributeSensorAndConfigKey CRATE_HTTP_PORT = new PortAttributeSensorAndConfigKey(
+            "crate.httpPort", "The port for HTTP traffic", PortRanges.fromString("4200+"));
+
+    AttributeSensor<String> MANAGEMENT_URL = Sensors.newStringSensor(
+            "crate.managementUri", "The address at which the Crate server listens");
+
+    AttributeSensor<String> SERVER_NAME = Sensors.newStringSensor(
+            "crate.server.name", "The name of the server");
+
+    AttributeSensor<Boolean> SERVER_OK = Sensors.newBooleanSensor(
+            "crate.server.ok", "True if the server reports thus");
+
+    AttributeSensor<Integer> SERVER_STATUS = Sensors.newIntegerSensor(
+            "crate.server.status", "The status of the server");
+
+    AttributeSensor<String> SERVER_BUILD_TIMESTAMP = Sensors.newStringSensor(
+            "crate.server.buildTimestamp", "The timestamp of the server build");
+
+    AttributeSensor<String> SERVER_BUILD_HASH = Sensors.newStringSensor(
+            "crate.server.buildHash", "The build hash of the server");
+
+    AttributeSensor<Boolean> SERVER_IS_BUILD_SNAPSHOT = Sensors.newBooleanSensor(
+            "crate.server.isBuildSnapshot", "True if the server reports it is a snapshot build");
+
+    AttributeSensor<String> SERVER_LUCENE_VERSION = Sensors.newStringSensor(
+            "crate.server.luceneVersion", "The Lucene version of the server");
+
+    AttributeSensor<String> SERVER_ES_VERSION = Sensors.newStringSensor(
+            "crate.server.esVersion", "The ES version of the server");
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeDriver.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeDriver.java
new file mode 100644
index 0000000..708e0e8
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeDriver.java
@@ -0,0 +1,24 @@
+/*
+ * 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.crate;
+
+import brooklyn.entity.java.JavaSoftwareProcessDriver;
+
+public interface CrateNodeDriver extends JavaSoftwareProcessDriver {
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeImpl.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeImpl.java
new file mode 100644
index 0000000..df65398
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeImpl.java
@@ -0,0 +1,99 @@
+/*
+ * 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.crate;
+
+import brooklyn.config.render.RendererHints;
+import brooklyn.enricher.Enrichers;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.entity.java.JavaAppUtils;
+import brooklyn.event.feed.http.HttpFeed;
+import brooklyn.event.feed.http.HttpPollConfig;
+import brooklyn.event.feed.http.HttpValueFunctions;
+import brooklyn.event.feed.jmx.JmxFeed;
+import brooklyn.util.guava.Functionals;
+
+public class CrateNodeImpl extends SoftwareProcessImpl implements CrateNode{
+
+    private JmxFeed jmxFeed;
+    private HttpFeed httpFeed;
+
+    static {
+        JavaAppUtils.init();
+        RendererHints.register(MANAGEMENT_URL, RendererHints.namedActionWithUrl());
+    }
+
+    @Override
+    public Class getDriverInterface() {
+        return CrateNodeDriver.class;
+    }
+
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+        connectServiceUpIsRunning();
+        jmxFeed = JavaAppUtils.connectMXBeanSensors(this);
+        setAttribute(DATASTORE_URL, "crate://" + getAttribute(HOSTNAME) + ":" + getPort());
+        String url = "http://" + getAttribute(HOSTNAME) + ":" + getHttpPort();
+        setAttribute(MANAGEMENT_URL, url);
+
+        httpFeed = HttpFeed.builder()
+                .entity(this)
+                .baseUri(url)
+                .poll(new HttpPollConfig<String>(SERVER_NAME)
+                        .onSuccess(HttpValueFunctions.jsonContents("name", String.class)))
+                .poll(new HttpPollConfig<Integer>(SERVER_STATUS)
+                        .onSuccess(HttpValueFunctions.jsonContents("status", Integer.class)))
+                .poll(new HttpPollConfig<Boolean>(SERVER_OK)
+                        .onSuccess(HttpValueFunctions.jsonContents("ok", Boolean.class)))
+                .poll(new HttpPollConfig<String>(SERVER_BUILD_TIMESTAMP)
+                        .onSuccess(HttpValueFunctions.jsonContents(new String[]{"version", "build_timestamp"}, String.class)))
+                .poll(new HttpPollConfig<String>(SERVER_BUILD_HASH)
+                        .onSuccess(HttpValueFunctions.jsonContents(new String[]{"version", "build_hash"}, String.class)))
+                .poll(new HttpPollConfig<Boolean>(SERVER_IS_BUILD_SNAPSHOT)
+                        .onSuccess(HttpValueFunctions.jsonContents(new String[] {"version", "build_snapshot"}, Boolean.class)))
+                .poll(new HttpPollConfig<String>(SERVER_LUCENE_VERSION)
+                        .onSuccess(HttpValueFunctions.jsonContents(new String[] {"version", "lucene_version"}, String.class)))
+                .poll(new HttpPollConfig<String>(SERVER_ES_VERSION)
+                        .onSuccess(HttpValueFunctions.jsonContents(new String[] {"version", "es_version"}, String.class)))
+                .build();
+
+        addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
+                .from(SERVER_OK)
+                .computing(Functionals.ifNotEquals(true).value("Crate server reports it is not ok."))
+                .build());
+    }
+
+    @Override
+    protected void disconnectSensors() {
+        disconnectServiceUpIsRunning();
+        if (jmxFeed != null) jmxFeed.stop();
+        if (httpFeed != null) httpFeed.stop();
+        super.disconnectSensors();
+    }
+
+    public Integer getPort() {
+        return getAttribute(CRATE_PORT);
+    }
+
+    public Integer getHttpPort() {
+        return getAttribute(CRATE_HTTP_PORT);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeSshDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeSshDriver.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeSshDriver.java
new file mode 100644
index 0000000..2eafc81
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/crate/CrateNodeSshDriver.java
@@ -0,0 +1,118 @@
+/*
+ * 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.crate;
+
+import static java.lang.String.format;
+
+import java.util.List;
+
+import org.apache.brooklyn.api.entity.basic.EntityLocal;
+
+import com.google.common.collect.ImmutableList;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.java.JavaSoftwareProcessSshDriver;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Urls;
+import brooklyn.util.os.Os;
+import brooklyn.util.ssh.BashCommands;
+
+public class CrateNodeSshDriver extends JavaSoftwareProcessSshDriver {
+
+    public CrateNodeSshDriver(EntityLocal entity, SshMachineLocation machine) {
+        super(entity, machine);
+    }
+
+    @Override
+    public void preInstall() {
+        resolver = Entities.newDownloader(this);
+        setExpandedInstallDir(Os.mergePaths(getInstallDir(),
+                resolver.getUnpackedDirectoryName(format("crate-%s", getVersion()))));
+    }
+
+    @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("tar xvfz "+saveAs)
+                .build();
+
+        newScript(INSTALLING)
+                .failOnNonZeroResultCode()
+                .body.append(commands).execute();
+    }
+
+    @Override
+    public void customize() {
+        newScript(CUSTOMIZING)
+                .body.append("mkdir -p " + getDataLocation())
+                .execute();
+        copyTemplate(entity.getConfig(CrateNode.SERVER_CONFIG_URL), getConfigFileLocation());
+    }
+
+    @Override
+    public void launch() {
+        StringBuilder command = new StringBuilder(getExpandedInstallDir())
+                .append("/bin/crate ")
+                .append(" -d")
+                .append(" -p ").append(getPidFileLocation())
+                .append(" -Des.config=").append(getConfigFileLocation());
+        newScript(LAUNCHING)
+                .failOnNonZeroResultCode()
+                .body.append(command).execute();
+
+    }
+
+    @Override
+    public boolean isRunning() {
+        return newScript (MutableMap.of("usePidFile", getPidFileLocation()), CHECK_RUNNING)
+                .execute() == 0;
+    }
+
+    @Override
+    public void stop() {
+        // See https://crate.io/docs/stable/cli.html#signal-handling.
+        newScript(STOPPING)
+                .body.append("kill -USR2 `cat " + getPidFileLocation() + "`")
+                .execute();
+    }
+
+    protected String getConfigFileLocation() {
+        return Urls.mergePaths(getRunDir(), "config.yaml");
+    }
+
+    @Override
+    public String getLogFileLocation() {
+        return Urls.mergePaths(getRunDir(), "crate.log");
+    }
+
+    protected String getPidFileLocation () {
+        return Urls.mergePaths(getRunDir(), "pid.txt");
+    }
+
+    // public for use in template too.
+    public String getDataLocation() {
+        return Urls.mergePaths(getRunDir(), "data");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbDriver.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbDriver.java
new file mode 100644
index 0000000..5c841a7
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbDriver.java
@@ -0,0 +1,31 @@
+/*
+ * 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.mariadb;
+
+import org.apache.brooklyn.core.util.task.system.ProcessTaskWrapper;
+
+import brooklyn.entity.basic.SoftwareProcessDriver;
+
+/**
+ * The {@link SoftwareProcessDriver} for MariaDB.
+ */
+public interface MariaDbDriver extends SoftwareProcessDriver {
+    public String getStatusCmd();
+    public ProcessTaskWrapper<Integer> executeScriptAsync(String commands);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbNode.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbNode.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbNode.java
new file mode 100644
index 0000000..34b068e
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbNode.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.database.mariadb;
+
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+import org.apache.brooklyn.api.entity.trait.HasShortName;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.SoftwareProcess;
+import org.apache.brooklyn.entity.database.DatabaseNode;
+import org.apache.brooklyn.entity.database.DatastoreMixins.DatastoreCommon;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.event.basic.BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey;
+import brooklyn.event.basic.MapConfigKey;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
+import brooklyn.event.basic.Sensors;
+
+import org.apache.brooklyn.location.basic.PortRanges;
+
+@Catalog(name="MariaDB Node", description="MariaDB is an open source relational database management system (RDBMS)", iconUrl="classpath:///mariadb-logo-180x119.png")
+@ImplementedBy(MariaDbNodeImpl.class)
+public interface MariaDbNode extends SoftwareProcess, DatastoreCommon, HasShortName, DatabaseNode {
+
+    @SetFromFlag("version")
+    public static final ConfigKey<String> SUGGESTED_VERSION =
+        ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "5.5.40");
+
+    // https://downloads.mariadb.org/interstitial/mariadb-5.5.33a/kvm-bintar-hardy-amd64/mariadb-5.5.33a-linux-x86_64.tar.gz/from/http://mirrors.coreix.net/mariadb
+    // above redirects to download the artifactd from the URLs below.
+    // Use `curl -sL -w "%{http_code} %{url_effective}\n" "http://..." -o target.tar.gz` to find out redirect URL.
+    //     64-bit: http://mirrors.coreix.net/mariadb/mariadb-5.5.40/bintar-linux-x86_64/mariadb-5.5.40-linux-x86_64.tar.gz
+    //     32-bit: http://mirrors.coreix.net/mariadb/mariadb-5.5.40/bintar-linux-x86/mariadb-5.5.40-linux-i686.tar.gz
+
+    @SetFromFlag("downloadUrl")
+    public static final BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new StringAttributeSensorAndConfigKey(
+          Attributes.DOWNLOAD_URL, "${driver.mirrorUrl}/mariadb-${version}/${driver.downloadParentDir}/mariadb-${version}-${driver.osTag}.tar.gz");
+
+    /** download mirror, if desired */
+    @SetFromFlag("mirrorUrl")
+    public static final ConfigKey<String> MIRROR_URL = ConfigKeys.newStringConfigKey("mariadb.install.mirror.url", "URL of mirror",
+        "http://mirrors.coreix.net/mariadb/"
+     );
+
+    @SetFromFlag("port")
+    public static final PortAttributeSensorAndConfigKey MARIADB_PORT =
+        new PortAttributeSensorAndConfigKey("mariadb.port", "MariaDB port", PortRanges.fromString("3306, 13306+"));
+
+    @SetFromFlag("dataDir")
+    public static final ConfigKey<String> DATA_DIR = ConfigKeys.newStringConfigKey(
+        "mariadb.datadir", "Directory for writing data files", null);
+
+    @SetFromFlag("serverConf")
+    public static final MapConfigKey<Object> MARIADB_SERVER_CONF = new MapConfigKey<Object>(
+        Object.class, "mariadb.server.conf", "Configuration options for MariaDB server");
+
+    public static final ConfigKey<Object> MARIADB_SERVER_CONF_LOWER_CASE_TABLE_NAMES =
+        MARIADB_SERVER_CONF.subKey("lower_case_table_names", "See MariaDB (or MySQL!) guide. Set 1 to ignore case in table names (useful for OS portability)");
+
+    @SetFromFlag("password")
+    public static final StringAttributeSensorAndConfigKey PASSWORD = new StringAttributeSensorAndConfigKey(
+        "mariadb.password", "Database admin password (or randomly generated if not set)", null);
+
+    @SetFromFlag("socketUid")
+    public static final StringAttributeSensorAndConfigKey SOCKET_UID = new StringAttributeSensorAndConfigKey(
+        "mariadb.socketUid", "Socket uid, for use in file /tmp/mysql.sock.<uid>.3306 (or randomly generated if not set)", null);
+
+    /** @deprecated since 0.7.0 use DATASTORE_URL */ @Deprecated
+    public static final AttributeSensor<String> MARIADB_URL = DATASTORE_URL;
+
+    @SetFromFlag("configurationTemplateUrl")
+    static final BasicAttributeSensorAndConfigKey<String> TEMPLATE_CONFIGURATION_URL = new StringAttributeSensorAndConfigKey(
+        "mariadb.template.configuration.url", "Template file (in freemarker format) for the my.cnf file",
+        "classpath://org/apache/brooklyn/entity/database/mariadb/my.cnf");
+
+    public static final AttributeSensor<Double> QUERIES_PER_SECOND_FROM_MARIADB =
+        Sensors.newDoubleSensor("mariadb.queries.perSec.fromMariadb");
+
+    public String executeScript(String commands);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbNodeImpl.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbNodeImpl.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbNodeImpl.java
new file mode 100644
index 0000000..238ce49
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbNodeImpl.java
@@ -0,0 +1,139 @@
+/*
+ * 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.mariadb;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.SoftwareProcessImpl;
+import brooklyn.entity.effector.EffectorBody;
+import brooklyn.event.feed.ssh.SshFeed;
+import brooklyn.event.feed.ssh.SshPollConfig;
+import brooklyn.event.feed.ssh.SshPollValue;
+
+import org.apache.brooklyn.core.util.config.ConfigBag;
+import org.apache.brooklyn.location.basic.Locations;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+
+import com.google.common.base.Function;
+
+public class MariaDbNodeImpl extends SoftwareProcessImpl implements MariaDbNode {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MariaDbNodeImpl.class);
+
+    private SshFeed feed;
+
+    @Override
+    public Class<?> getDriverInterface() {
+        return MariaDbDriver.class;
+    }
+
+    @Override
+    public MariaDbDriver getDriver() {
+        return (MariaDbDriver) super.getDriver();
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        getMutableEntityType().addEffector(EXECUTE_SCRIPT, new EffectorBody<String>() {
+            @Override
+            public String call(ConfigBag parameters) {
+                return executeScript((String)parameters.getStringKey("commands"));
+            }
+        });
+    }
+    
+    @Override
+    protected void connectSensors() {
+        super.connectSensors();
+        setAttribute(DATASTORE_URL, String.format("mysql://%s:%s/", getAttribute(HOSTNAME), getAttribute(MARIADB_PORT)));
+
+        /*        
+         * TODO status gives us things like:
+         *   Uptime: 2427  Threads: 1  Questions: 581  Slow queries: 0  Opens: 53  Flush tables: 1  Open tables: 35  Queries per second avg: 0.239
+         * So can extract lots of sensors from that.
+         */
+        Maybe<SshMachineLocation> machine = Locations.findUniqueSshMachineLocation(getLocations());
+
+        if (machine.isPresent()) {
+            String cmd = getDriver().getStatusCmd();
+            feed = SshFeed.builder()
+                    .entity(this)
+                    .period(Duration.FIVE_SECONDS)
+                    .machine(machine.get())
+                    .poll(new SshPollConfig<Boolean>(SERVICE_UP)
+                            .command(cmd)
+                            .setOnSuccess(true)
+                            .setOnFailureOrException(false))
+                    .poll(new SshPollConfig<Double>(QUERIES_PER_SECOND_FROM_MARIADB)
+                            .command(cmd)
+                            .onSuccess(new Function<SshPollValue, Double>() {
+                                public Double apply(SshPollValue input) {
+                                    String q = Strings.getFirstWordAfter(input.getStdout(), "Queries per second avg:");
+                                    return (q == null) ? null : Double.parseDouble(q);
+                                }})
+                            .setOnFailureOrException(null) )
+                    .build();
+        } else {
+            LOG.warn("Location(s) {} not an ssh-machine location, so not polling for status; setting serviceUp immediately", getLocations());
+            setAttribute(SERVICE_UP, true);
+        }
+    }
+
+    @Override
+    protected void disconnectSensors() {
+        if (feed != null) feed.stop();
+        super.disconnectSensors();
+    }
+
+    public int getPort() {
+        return getAttribute(MARIADB_PORT);
+    }
+
+    public String getSocketUid() {
+        String result = getAttribute(MariaDbNode.SOCKET_UID);
+        if (Strings.isBlank(result))
+            setAttribute(MariaDbNode.SOCKET_UID, (result = Identifiers.makeRandomId(6)));
+        return result;
+    }
+
+    public String getPassword() {
+        String result = getAttribute(MariaDbNode.PASSWORD);
+        if (Strings.isBlank(result))
+            setAttribute(MariaDbNode.PASSWORD, (result = Identifiers.makeRandomId(6)));
+        return result;
+    }
+
+    @Override
+    public String getShortName() {
+        return "MariaDB";
+    }
+
+    @Override
+    public String executeScript(String commands) {
+        return getDriver().executeScriptAsync(commands).block().getStdout();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ac1a7c09/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbSshDriver.java
----------------------------------------------------------------------
diff --git a/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbSshDriver.java b/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbSshDriver.java
new file mode 100644
index 0000000..4f3a59b
--- /dev/null
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/mariadb/MariaDbSshDriver.java
@@ -0,0 +1,259 @@
+/*
+ * 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.mariadb;
+
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+import static brooklyn.util.ssh.BashCommands.commandsToDownloadUrlsAs;
+import static brooklyn.util.ssh.BashCommands.installPackage;
+import static brooklyn.util.ssh.BashCommands.ok;
+import static java.lang.String.format;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.Entities;
+import org.apache.brooklyn.entity.database.DatastoreMixins;
+import brooklyn.entity.software.SshEffectorTasks;
+
+import org.apache.brooklyn.api.location.OsDetails;
+import org.apache.brooklyn.core.util.task.DynamicTasks;
+import org.apache.brooklyn.core.util.task.system.ProcessTaskWrapper;
+import org.apache.brooklyn.location.basic.SshMachineLocation;
+
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.net.Urls;
+import brooklyn.util.os.Os;
+import brooklyn.util.ssh.BashCommands;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.CountdownTimer;
+import brooklyn.util.time.Duration;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * The SSH implementation of the {@link MariaDbDriver}.
+ */
+public class MariaDbSshDriver extends AbstractSoftwareProcessSshDriver implements MariaDbDriver {
+
+    public static final Logger log = LoggerFactory.getLogger(MariaDbSshDriver.class);
+
+    public MariaDbSshDriver(MariaDbNodeImpl entity, SshMachineLocation machine) {
+        super(entity, machine);
+
+        entity.setAttribute(Attributes.LOG_FILE_LOCATION, getLogFile());
+    }
+
+    public String getOsTag() {
+        OsDetails os = getLocation().getOsDetails();
+        // NOTE: cannot rely on OsDetails.isLinux() to return true for all linux flavours, so
+        // explicitly test for unsupported OSes, otherwise assume generic linux.
+        if (os == null) return "linux-i686";
+        if (os.isWindows() || os.isMac())
+            throw new UnsupportedOperationException("only support linux versions just now; OS details: " + os);
+        return (os.is64bit() ? "linux-x86_64" : "linux-i686");
+    }
+
+    public String getDownloadParentDir() {
+        // NOTE: cannot rely on OsDetails.isLinux() to return true for all linux flavours, so
+        // explicitly test for unsupported OSes, otherwise assume generic linux.
+        OsDetails os = getLocation().getOsDetails();
+        if (os == null) return "bintar-linux-x86";
+        if (os.isWindows() || os.isMac())
+            throw new UnsupportedOperationException("only support linux versions just now; OS details: " + os);
+        return (os.is64bit() ? "bintar-linux-x86_64" : "bintar-linux-x86");
+    }
+
+    public String getMirrorUrl() {
+        return entity.getConfig(MariaDbNode.MIRROR_URL);
+    }
+
+    public String getBaseDir() { return getExpandedInstallDir(); }
+
+    public String getDataDir() {
+        String result = entity.getConfig(MariaDbNode.DATA_DIR);
+        return (result == null) ? "." : result;
+    }
+
+    public String getLogFile() {
+        return Urls.mergePaths(getRunDir(), "console.log");
+    }
+
+    public String getConfigFile() {
+        return "my.cnf";
+    }
+
+    public String getInstallFilename() {
+        return String.format("mariadb-%s-%s.tar.gz", getVersion(), getOsTag());
+    }
+
+    @Override
+    public void preInstall() {
+        resolver = Entities.newDownloader(this, ImmutableMap.of("filename", getInstallFilename()));
+        setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(format("mariadb-%s-%s", getVersion(), getOsTag()))));
+    }
+
+    @Override
+    public void install() {
+        List<String> urls = resolver.getTargets();
+        String saveAs = resolver.getFilename();
+
+        List<String> commands = new LinkedList<String>();
+        commands.add(BashCommands.INSTALL_TAR);
+        commands.add(BashCommands.INSTALL_CURL);
+
+        commands.add("echo installing extra packages");
+        commands.add(installPackage(ImmutableMap.of("yum", "libgcc_s.so.1"), null));
+        commands.add(installPackage(ImmutableMap.of("yum", "libaio.so.1 libncurses.so.5", "apt", "libaio1 libaio-dev"), null));
+
+        // these deps are needed on some OS versions but others don't need them so ignore failures (ok(...))
+        commands.add(ok(installPackage(ImmutableMap.of("yum", "libaio", "apt", "ia32-libs"), null)));
+        commands.add("echo finished installing extra packages");
+
+        commands.addAll(commandsToDownloadUrlsAs(urls, saveAs));
+        commands.add(format("tar xfvz %s", saveAs));
+
+        newScript(INSTALLING).body.append(commands).execute();
+    }
+
+    public MariaDbNodeImpl getEntity() { return (MariaDbNodeImpl) super.getEntity(); }
+    public int getPort() { return getEntity().getPort(); }
+    public String getSocketUid() { return getEntity().getSocketUid(); }
+    public String getPassword() { return getEntity().getPassword(); }
+
+    @Override
+    public void customize() {
+        copyDatabaseConfigScript();
+
+        newScript(CUSTOMIZING)
+            .updateTaskAndFailOnNonZeroResultCode()
+            .body.append(
+                "chmod 600 "+getConfigFile(),
+                getBaseDir()+"/scripts/mysql_install_db "+
+                    "--basedir="+getBaseDir()+" --datadir="+getDataDir()+" "+
+                    "--defaults-file="+getConfigFile())
+            .execute();
+
+        // launch, then we will configure it
+        launch();
+
+        CountdownTimer timer = Duration.seconds(20).countdownTimer();
+        boolean hasCreationScript = copyDatabaseCreationScript();
+        timer.waitForExpiryUnchecked();
+
+        DynamicTasks.queue(
+            SshEffectorTasks.ssh(
+                "cd "+getRunDir(),
+                getBaseDir()+"/bin/mysqladmin --defaults-file="+getConfigFile()+" --password= password "+getPassword()
+            ).summary("setting password"));
+
+        if (hasCreationScript)
+            executeScriptFromInstalledFileAsync("creation-script.sql");
+
+        // not sure necessary to stop then subsequently launch, but seems safest
+        // (if skipping, use a flag in launch to indicate we've just launched it)
+        stop();
+    }
+
+    private void copyDatabaseConfigScript() {
+        newScript(CUSTOMIZING).execute();  //create the directory
+
+        String configScriptContents = processTemplate(entity.getAttribute(MariaDbNode.TEMPLATE_CONFIGURATION_URL));
+        Reader configContents = new StringReader(configScriptContents);
+
+        getMachine().copyTo(configContents, Urls.mergePaths(getRunDir(), getConfigFile()));
+    }
+
+    private boolean copyDatabaseCreationScript() {
+        InputStream creationScript = DatastoreMixins.getDatabaseCreationScript(entity);
+        if (creationScript==null) return false;
+        getMachine().copyTo(creationScript, getRunDir() + "/creation-script.sql");
+        return true;
+    }
+
+    public String getMariaDbServerOptionsString() {
+        Map<String, Object> options = entity.getConfig(MariaDbNode.MARIADB_SERVER_CONF);
+        StringBuilder result = new StringBuilder();
+        if (groovyTruth(options)) {
+            for (Map.Entry<String, Object> entry : options.entrySet()) {
+                result.append(entry.getKey());
+                String value = entry.getValue().toString();
+                if (!Strings.isEmpty(value)) {
+                    result.append(" = ").append(value);
+                }
+                result.append('\n');
+            }
+        }
+        return result.toString();
+    }
+
+    @Override
+    public void launch() {
+        newScript(MutableMap.of("usePidFile", true), LAUNCHING)
+            .updateTaskAndFailOnNonZeroResultCode()
+            .body.append(format("nohup %s/bin/mysqld --defaults-file=%s --user=`whoami` > %s 2>&1 < /dev/null &", getBaseDir(), getConfigFile(), getLogFile()))
+            .execute();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return newScript(MutableMap.of("usePidFile", false), CHECK_RUNNING)
+            .body.append(getStatusCmd())
+            .execute() == 0;
+    }
+
+    @Override
+    public void stop() {
+        newScript(MutableMap.of("usePidFile", true), STOPPING).execute();
+    }
+
+    @Override
+    public void kill() {
+        newScript(MutableMap.of("usePidFile", true), KILLING).execute();
+    }
+
+    @Override
+    public String getStatusCmd() {
+        return format("%s/bin/mysqladmin --defaults-file=%s status", getExpandedInstallDir(), Urls.mergePaths(getRunDir(), getConfigFile()));
+    }
+
+    public ProcessTaskWrapper<Integer> executeScriptAsync(String commands) {
+        String filename = "mariadb-commands-"+Identifiers.makeRandomId(8);
+        DynamicTasks.queue(SshEffectorTasks.put(Urls.mergePaths(getRunDir(), filename)).contents(commands).summary("copying datastore script to execute "+filename));
+        return executeScriptFromInstalledFileAsync(filename);
+    }
+
+    public ProcessTaskWrapper<Integer> executeScriptFromInstalledFileAsync(String filenameAlreadyInstalledAtServer) {
+        return DynamicTasks.queue(
+                SshEffectorTasks.ssh(
+                                "cd "+getRunDir(),
+                                getBaseDir()+"/bin/mysql --defaults-file="+getConfigFile()+" < "+filenameAlreadyInstalledAtServer)
+                        .summary("executing datastore script "+filenameAlreadyInstalledAtServer));
+    }
+
+}


Mime
View raw message