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)); + } + +}
