| /* |
| * 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 java.lang.String.format; |
| import static org.apache.brooklyn.util.ssh.BashCommands.alternativesGroup; |
| import static org.apache.brooklyn.util.ssh.BashCommands.chainGroup; |
| import static org.apache.brooklyn.util.ssh.BashCommands.dontRequireTtyForSudo; |
| import static org.apache.brooklyn.util.ssh.BashCommands.executeCommandThenAsUserTeeOutputToFile; |
| import static org.apache.brooklyn.util.ssh.BashCommands.fail; |
| import static org.apache.brooklyn.util.ssh.BashCommands.ifExecutableElse0; |
| import static org.apache.brooklyn.util.ssh.BashCommands.ifExecutableElse1; |
| import static org.apache.brooklyn.util.ssh.BashCommands.installPackage; |
| import static org.apache.brooklyn.util.ssh.BashCommands.sudo; |
| import static org.apache.brooklyn.util.ssh.BashCommands.sudoAsUser; |
| import static org.apache.brooklyn.util.ssh.BashCommands.warn; |
| import static org.apache.brooklyn.util.ssh.BashCommands.commandToDownloadUrlAs; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.security.InvalidParameterException; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import javax.annotation.Nullable; |
| |
| import org.apache.brooklyn.api.location.OsDetails; |
| import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; |
| import org.apache.brooklyn.core.entity.Attributes; |
| import org.apache.brooklyn.core.sensor.BasicAttributeSensorAndConfigKey; |
| import org.apache.brooklyn.entity.database.DatastoreMixins; |
| import org.apache.brooklyn.entity.software.base.AbstractSoftwareProcessSshDriver; |
| import org.apache.brooklyn.entity.software.base.SoftwareProcess; |
| import org.apache.brooklyn.location.ssh.SshMachineLocation; |
| import org.apache.brooklyn.util.collections.MutableList; |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.core.task.DynamicTasks; |
| import org.apache.brooklyn.util.core.task.ssh.SshTasks; |
| import org.apache.brooklyn.util.core.task.ssh.SshTasks.OnFailingTask; |
| import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.net.Urls; |
| import org.apache.brooklyn.util.os.Os; |
| import org.apache.brooklyn.util.stream.Streams; |
| import org.apache.brooklyn.util.text.Identifiers; |
| import org.apache.brooklyn.util.text.StringEscapes; |
| import org.apache.brooklyn.util.text.StringFunctions; |
| import org.apache.brooklyn.util.text.Strings; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| 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.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| 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.sensors().set(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 |
| // TODO if command above fails then the following `getUnchecked` blocks forever |
| 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.contains("red hat")) 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( |
| sudo(commandToDownloadUrlAs( |
| format("http://yum.postgresql.org/%s/redhat/rhel-%s-%s/pgdg-%s%s-%s.noarch.rpm", majorMinorVersion, osMajorVersion, arch, osName, shortVersion, version), |
| "pgdg.rpm")), |
| sudo("rpm -Uvh pgdg.rpm") |
| ); |
| } |
| |
| private String getAptRepository() { |
| return chainGroup( |
| sudo("apt-key adv --fetch-keys http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc"), |
| "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>() { |
| @Override |
| 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(Boolean.TRUE.equals(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())); |
| |
| String createRolesAdditionalCommand = ""; |
| |
| if (entity.getConfig(PostgreSqlNode.ROLES) != null && !entity.getConfig(PostgreSqlNode.ROLES).isEmpty()) { |
| String createRolesQuery = buildCreateRolesQuery(); |
| createRolesAdditionalCommand = |
| sudoAsUser("postgres", getInstallDir() + "/bin/psql -p " + entity.getAttribute(PostgreSqlNode.POSTGRESQL_PORT) + |
| " --command="+ createRolesQuery); |
| } |
| |
| 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), |
| createRolesAdditionalCommand, |
| callPgctl("stop", true)) |
| .failOnNonZeroResultCode().execute(); |
| } |
| |
| @VisibleForTesting |
| protected String buildCreateRolesQuery() { |
| Map<String, Map<String, ?>> roles = entity.getConfig(PostgreSqlNode.ROLES); |
| StringBuilder builder = new StringBuilder("\""); |
| |
| for (Map.Entry<String, ? extends Map<String, ?>> entry : roles.entrySet()) { |
| String roleName = entry.getKey(); |
| Map<String, ?> roleConfig = entry.getValue(); |
| if (roleConfig == null) roleConfig = ImmutableMap.of(); |
| |
| if (Strings.isBlank(roleName)) { |
| throw new NullPointerException("Role name must not be blank, but got "+roles); |
| } |
| validateInput(roleName, "role name '"+roleName+"'"); |
| |
| builder.append(String.format("CREATE ROLE %s", roleName)); |
| |
| |
| if (roleConfig != null && roleConfig.containsKey(PostgreSqlNode.ROLE_PROPERTIES_KEY)) { |
| Object rawProps = roleConfig.get("properties"); |
| String props = validateInput((String) rawProps, "role '"+roleName+"' property "+rawProps);; |
| builder.append(String.format(" WITH %s; ", props)); |
| } else { |
| builder.append("; "); |
| } |
| |
| if (roleConfig.containsKey(PostgreSqlNode.ROLE_PRIVILEGES_KEY)) { |
| List<String> privileges = toListOfStrings(roleConfig.get(PostgreSqlNode.ROLE_PRIVILEGES_KEY)); |
| for (Object rawPrivilege : privileges) { |
| String privilege = validateInput((String) rawPrivilege, "role '"+roleName+"' privilege "+rawPrivilege); |
| builder.append(String.format("GRANT %s TO %s; ", privilege, roleName)); |
| } |
| } |
| |
| Set<String> otherConfig = Sets.difference(roleConfig.keySet(), |
| ImmutableSet.of(PostgreSqlNode.ROLE_PROPERTIES_KEY, PostgreSqlNode.ROLE_PRIVILEGES_KEY)); |
| if (!otherConfig.isEmpty()) { |
| throw new IllegalArgumentException("Invalid configuration for role "+roleName+", got "+roles); |
| } |
| } |
| |
| builder.append("\""); |
| |
| return builder.toString(); |
| } |
| |
| private String validateInput(String input, String errContext) { |
| String regex = "[A-Za-z_,\\s]+"; |
| Pattern pattern = Pattern.compile(regex); |
| Matcher matcher = pattern.matcher(input); |
| |
| if (Strings.isBlank(input)) { |
| throw new NullPointerException("Must be non-blank for "+errContext); |
| } |
| if (matcher.find() && matcher.start() == 0 && matcher.end() == input.length()) { |
| return input; |
| } |
| |
| throw new InvalidParameterException("Query input seems to be insecure. Make sure you pass a valid value for "+errContext); |
| } |
| |
| // TODO Duplicate of JcloudsLocation.toListOfStrings |
| private static List<String> toListOfStrings(Object v) { |
| List<String> result = Lists.newArrayList(); |
| if (v instanceof Iterable) { |
| for (Object o : (Iterable<?>)v) { |
| result.add(o.toString()); |
| } |
| } else if (v instanceof Object[]) { |
| for (int i = 0; i < ((Object[])v).length; i++) { |
| result.add(((Object[])v)[i].toString()); |
| } |
| } else if (v instanceof String) { |
| result.add((String) v); |
| } else { |
| throw new IllegalArgumentException("Invalid type for List<String>: "+v+" of type "+v.getClass()); |
| } |
| return result; |
| } |
| |
| private String getConfigOrDefault(BasicAttributeSensorAndConfigKey<String> key, String def) { |
| String val = entity.getConfig(key); |
| if (Strings.isEmpty(val)) { |
| val = entity.sensors().get(key); |
| if (Strings.isEmpty(val)) { |
| val = def; |
| log.debug(entity + " has no config specified for " + key + "; using default `" + def + "`"); |
| entity.sensors().set(key, val); |
| } |
| } |
| return val; |
| } |
| |
| 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); |
| } |
| |
| @Override |
| 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) + " -v ON_ERROR_STOP=1 --file " + filenameAlreadyInstalledAtServer)) |
| .requiringExitCodeZero() |
| .summary("executing datastore script "+filenameAlreadyInstalledAtServer)); |
| } |
| |
| } |