You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/08/18 13:06:22 UTC
[24/24] incubator-brooklyn git commit: This closes #832
This closes #832
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/f092e183
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/f092e183
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/f092e183
Branch: refs/heads/master
Commit: f092e183f9fc0ebc060d615a0efdea78357d9f87
Parents: bf2cfcd 3fff641
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Aug 18 12:04:55 2015 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Aug 18 12:04:55 2015 +0100
----------------------------------------------------------------------
.../database/postgresql/PostgreSqlNode.java | 23 ++++++-
.../postgresql/PostgreSqlSshDriver.java | 64 +++++++++++++++++---
2 files changed, 77 insertions(+), 10 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f092e183/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNode.java
----------------------------------------------------------------------
diff --cc software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNode.java
index 7d195f7,0000000..70ac0c7
mode 100644,000000..100644
--- a/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNode.java
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlNode.java
@@@ -1,95 -1,0 +1,116 @@@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.postgresql;
+
+import org.apache.brooklyn.api.catalog.Catalog;
+import org.apache.brooklyn.api.entity.Effector;
+import org.apache.brooklyn.api.entity.proxying.ImplementedBy;
+import org.apache.brooklyn.api.entity.trait.HasShortName;
+import org.apache.brooklyn.core.util.flags.SetFromFlag;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.SoftwareProcess;
+import org.apache.brooklyn.entity.database.DatabaseNode;
+import org.apache.brooklyn.entity.database.DatastoreMixins;
+import org.apache.brooklyn.entity.database.DatastoreMixins.DatastoreCommon;
+import brooklyn.entity.effector.Effectors;
++import brooklyn.event.basic.BasicAttributeSensorAndConfigKey;
+import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
-
+import org.apache.brooklyn.location.basic.PortRanges;
+
+/**
+ * PostgreSQL database node entity.
+ * <p>
+ * <ul>
+ * <li>You may need to increase shared memory settings in the kernel depending on the setting of
+ * the {@link #SHARED_MEMORY_BUFFER} key. The minimumm value is <em>128kB</em>. See the PostgreSQL
+ * <a href="http://www.postgresql.org/docs/9.1/static/kernel-resources.html">documentation</a>.
+ * <li>You will also need to enable passwordless sudo.
+ * </ul>
+ */
+@Catalog(name="PostgreSQL Node", description="PostgreSQL is an object-relational database management system (ORDBMS)", iconUrl="classpath:///postgresql-logo-200px.png")
+@ImplementedBy(PostgreSqlNodeImpl.class)
+public interface PostgreSqlNode extends SoftwareProcess, HasShortName, DatastoreCommon, DatabaseNode {
+
+ @SetFromFlag("version")
+ ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "9.3-1");//"9.1-4");
+
+ @SetFromFlag("configFileUrl")
+ ConfigKey<String> CONFIGURATION_FILE_URL = ConfigKeys.newStringConfigKey(
+ "postgresql.config.file.url", "URL where PostgreSQL configuration file can be found; "
+ + "if not supplied the blueprint uses the default and customises it");
+
+ @SetFromFlag("authConfigFileUrl")
+ ConfigKey<String> AUTHENTICATION_CONFIGURATION_FILE_URL = ConfigKeys.newStringConfigKey(
+ "postgresql.authConfig.file.url", "URL where PostgreSQL host-based authentication configuration file can be found; "
+ + "if not supplied the blueprint uses the default and customises it");
+
+ @SetFromFlag("port")
+ PortAttributeSensorAndConfigKey POSTGRESQL_PORT = new PortAttributeSensorAndConfigKey(
+ "postgresql.port", "PostgreSQL port", PortRanges.fromString("5432+"));
+
+ @SetFromFlag("sharedMemory")
+ ConfigKey<String> SHARED_MEMORY = ConfigKeys.newStringConfigKey(
+ "postgresql.sharedMemory", "Size of shared memory buffer (must specify as kB, MB or GB, minimum 128kB)", "4MB");
+
+ @SetFromFlag("maxConnections")
+ ConfigKey<Integer> MAX_CONNECTIONS = ConfigKeys.newIntegerConfigKey(
+ "postgresql.maxConnections", "Maximum number of connections to the database", 100);
+
+ @SetFromFlag("disconnectOnStop")
+ ConfigKey<Boolean> DISCONNECT_ON_STOP = ConfigKeys.newBooleanConfigKey(
+ "postgresql.disconnect.on.stop", "If true, PostgreSQL will immediately disconnet (pg_ctl -m immediate stop) all current connections when the node is stopped", true);
+
+ @SetFromFlag("pollPeriod")
+ ConfigKey<Long> POLL_PERIOD = ConfigKeys.newLongConfigKey(
+ "postgresql.sensorpoll", "Poll period (in milliseconds)", 1000L);
++
++ @SetFromFlag("initializeDB")
++ ConfigKey<Boolean> INITIALIZE_DB = ConfigKeys.newBooleanConfigKey(
++ "postgresql.initialize", "If true, PostgreSQL will create a new user and database", false);
++
++ @SetFromFlag("username")
++ BasicAttributeSensorAndConfigKey<String> USERNAME = new BasicAttributeSensorAndConfigKey<>(
++ String.class, "postgresql.username", "Username of the database user");
++
++ String DEFAULT_USERNAME = "postgresqluser";
++
++ @SetFromFlag("password")
++ BasicAttributeSensorAndConfigKey<String> PASSWORD = new BasicAttributeSensorAndConfigKey<>(
++ String.class, "postgresql.password",
++ "Password for the database user, auto-generated if not set");
++
++ @SetFromFlag("database")
++ BasicAttributeSensorAndConfigKey<String> DATABASE = new BasicAttributeSensorAndConfigKey<>(
++ String.class, "postgresql.database", "Database to be used");
++
++ String DEFAULT_DB_NAME = "db";
+
+ Effector<String> EXECUTE_SCRIPT = Effectors.effector(DatastoreMixins.EXECUTE_SCRIPT)
+ .description("Executes the given script contents using psql")
+ .buildAbstract();
+
+ Integer getPostgreSqlPort();
+ String getSharedMemory();
+ Integer getMaxConnections();
+
+ String executeScript(String commands);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/f092e183/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
----------------------------------------------------------------------
diff --cc software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
index d66ed76,0000000..54b88a2
mode 100644,000000..100644
--- a/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
+++ b/software/database/src/main/java/org/apache/brooklyn/entity/database/postgresql/PostgreSqlSshDriver.java
@@@ -1,425 -1,0 +1,471 @@@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.entity.database.postgresql;
+
+import 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.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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver;
+import brooklyn.entity.basic.Attributes;
+import brooklyn.entity.basic.SoftwareProcess;
+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.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.event.basic.BasicAttributeSensorAndConfigKey;
+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.StringEscapes;
+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();
-
++ if(entity.getConfig(PostgreSqlNode.INITIALIZE_DB)){
++ initializeNewDatabase();
++ }
+ // 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.
+ }
+
++ private void initializeNewDatabase() {
++ String createUserCommand = String.format(
++ "\"CREATE USER %s WITH PASSWORD '%s'; \"",
++ StringEscapes.escapeSql(getUsername()),
++ StringEscapes.escapeSql(getUserPassword())
++ );
++ String createDatabaseCommand = String.format(
++ "\"CREATE DATABASE %s OWNER %s\"",
++ StringEscapes.escapeSql(getDatabaseName()),
++ StringEscapes.escapeSql(getUsername()));
++ newScript("initializing user and database")
++ .body.append(
++ "cd " + getInstallDir(),
++ callPgctl("start", true),
++ sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) +
++ " --command="+ createUserCommand),
++ sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) +
++ " --command="+ createDatabaseCommand),
++ callPgctl("stop", true))
++ .failOnNonZeroResultCode().execute();
++ }
++
++ private String getConfigOrDefault(BasicAttributeSensorAndConfigKey<String> key, String def) {
++ String config = entity.getConfig(key);
++ if(Strings.isEmpty(config)) {
++ config = def;
++ log.debug(entity + " has no config specified for " + key + "; using default `" + def + "`");
++ entity.setAttribute(key, config);
++ }
++ return config;
++ }
++
++ protected String getDatabaseName() {
++ return getConfigOrDefault(PostgreSqlNode.DATABASE, PostgreSqlNode.DEFAULT_DB_NAME);
++ }
++
++ protected String getUsername(){
++ return getConfigOrDefault(PostgreSqlNode.USERNAME, PostgreSqlNode.DEFAULT_USERNAME);
++ }
++
++ protected String getUserPassword() {
++ return getConfigOrDefault(PostgreSqlNode.PASSWORD, Strings.makeRandomId(8));
++ }
++
+ 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));
+ }
+
+}