001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2005 Emic Networks
004: * Contact: sequoia@continuent.org
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: * Initial developer(s): Emmanuel Cecchet.
019: * Contributor(s): Dylan Hansen, Mathieu Peltier, Olivier Fambon.
020: */package org.continuent.sequoia.controller.backup.backupers;
021:
022: import java.io.File;
023: import java.io.IOException;
024: import java.util.ArrayList;
025: import java.util.Date;
026:
027: import org.continuent.sequoia.common.exceptions.BackupException;
028: import org.continuent.sequoia.common.log.Trace;
029: import org.continuent.sequoia.controller.backend.DatabaseBackend;
030: import org.continuent.sequoia.controller.backup.BackupManager;
031: import org.continuent.sequoia.controller.backup.DumpTransferInfo;
032:
033: /**
034: * This class defines a Backuper for PostgreSQL databases. This backuper makes
035: * dumps in a plain-text format with a .sql extension.
036: * <p>
037: * Supported URLs are:
038: * <ul>
039: * <li>jdbc:postgresql://host:port/dbname?param1=foo,param2=bar</li>
040: * <li>jdbc:postgresql://host/dbname?param1=foo,param2=bar</li>
041: * <li>jdbc:postgresql:dbname?param1=foo,param2=bar</li>
042: * </ul>
043: *
044: * @author <a href="mailto:emmanuel.cecchet@emicnetworks.com">Emmanuel Cecchet</a>
045: * @author <a href="mailto:dhansen@h2st.com">Dylan Hansen</a>
046: * @author <a href="mailto:mathieu.peltier@emicnetworks.com">Mathieu Peltier</a>
047: * @author <a href="mailto:olivier.fambon@emicnetworks.com">Olivier Fambon</a>
048: * @version 1.0
049: */
050: public class PostgreSQLPlainTextBackuper extends
051: AbstractPostgreSQLBackuper {
052: // Logger
053: static Trace logger = Trace
054: .getLogger(PostgreSQLPlainTextBackuper.class.getName());
055:
056: /**
057: * The dump format for this (family of) backuper.
058: */
059: public static final String DUMP_FORMAT = "PostgreSQL Plain Text Dump";
060:
061: /**
062: * @see org.continuent.sequoia.controller.backup.Backuper#getDumpFormat()
063: */
064: public String getDumpFormat() {
065: return DUMP_FORMAT;
066: }
067:
068: /**
069: * @see org.continuent.sequoia.controller.backup.Backuper#backup(DatabaseBackend,
070: * String, String, String, String, ArrayList)
071: */
072: public Date backup(DatabaseBackend backend, String login,
073: String password, String dumpName, String path,
074: ArrayList tables) throws BackupException {
075: // Parse the URL for the connection information
076: String url = backend.getURL();
077: PostgreSQLUrlInfo info = new AbstractPostgreSQLBackuper.PostgreSQLUrlInfo(
078: url);
079:
080: if (logger.isDebugEnabled())
081: logger.debug("Backing up database '" + info.getDbName()
082: + "' on host '" + info.getHost() + ":"
083: + info.getPort() + "'");
084:
085: try {
086: // Create the path, if it does not already exist
087: File pathDir = new File(path);
088: if (!pathDir.exists()) {
089: pathDir.mkdirs();
090: pathDir.mkdir();
091: }
092: String fullPath = getDumpPhysicalPath(path, dumpName)
093: + ".sql";
094:
095: int exitValue = -1;
096: String dumpOptions = "";
097: if (pgDumpFlags != null) {
098: if (pgDumpFlags.indexOf("-F") >= 0
099: || pgDumpFlags.indexOf("--format=") >= 0) {
100: logger
101: .error("Invalid option in pgDumpFlags \""
102: + pgDumpFlags
103: + "\". You are not allowed to set the format of the dump!");
104: } else {
105: dumpOptions = " " + pgDumpFlags + " ";
106: }
107: }
108: if (useAuthentication) {
109: if (logger.isDebugEnabled())
110: logger
111: .debug("Performing backup using authentication");
112: int timeout = -1;
113: if (dumpTimeout != null) {
114: try {
115: timeout = Integer.parseInt(dumpTimeout);
116: } catch (NumberFormatException e) {
117: logger
118: .error("\""
119: + dumpTimeout
120: + "\" is not a valid dump-timeout value!");
121: timeout = -1;
122: }
123: }
124: String[] expectFeed = makeExpectDialogueWithAuthentication(
125: "pg_dump", info, " -f " + fullPath
126: + dumpOptions, login, password, timeout);
127:
128: String[] cmd = makeExpectCommandReadingStdin();
129:
130: exitValue = executeNativeCommand(expectFeed, cmd);
131: } else {
132: if (pgDumpFlags != null) {
133: if (pgDumpFlags.indexOf("-U") >= 0
134: || pgDumpFlags.indexOf("-W") >= 0) {
135: logger
136: .error("Invalid option in pgDumpFlags \""
137: + pgDumpFlags
138: + "\". Set \"authentication=true\" if you want to use authentication!");
139: } else {
140: dumpOptions = " " + pgDumpFlags + " ";
141: }
142: }
143:
144: String cmd = makeCommand("pg_dump", info, "-f "
145: + fullPath + dumpOptions, login);
146: exitValue = executeNativeCommand(cmd);
147: }
148:
149: if (exitValue != 0) {
150: printErrors();
151: throw new BackupException(
152: "pg_dump execution did not complete successfully!");
153: }
154:
155: } catch (Exception e) {
156: String msg = "Error while performing backup";
157: logger.error(msg, e);
158: throw new BackupException(msg, e);
159: }
160:
161: return new Date(System.currentTimeMillis());
162: }
163:
164: /**
165: * @see org.continuent.sequoia.controller.backup.Backuper#restore(DatabaseBackend,
166: * String, String, String, String, ArrayList)
167: */
168: public void restore(DatabaseBackend backend, String login,
169: String password, String dumpName, String path,
170: ArrayList tables) throws BackupException {
171: // Parse the URL for the connection information
172: String url = backend.getURL();
173: PostgreSQLUrlInfo info = new AbstractPostgreSQLBackuper.PostgreSQLUrlInfo(
174: url);
175:
176: if (logger.isDebugEnabled())
177: logger.debug("Restoring database '" + info.getDbName()
178: + "' on host '" + info.getHost() + ":"
179: + info.getPort() + "'");
180:
181: // Check to see if the given path + dumpName exists
182: String fullPath = getDumpPhysicalPath(path, dumpName) + ".sql";
183: File dump = new File(fullPath);
184: if (!dump.exists())
185: throw new BackupException("Backup '" + fullPath
186: + "' does not exist!");
187:
188: try {
189: if (useAuthentication) {
190: if (logger.isInfoEnabled())
191: logger
192: .info("Performing database operations using authentication");
193:
194: // Drop the database if it already exists
195: if (logger.isDebugEnabled())
196: logger.debug("Dropping database '"
197: + info.getDbName() + "'");
198:
199: String[] expectFeed = makeExpectDialogueWithAuthentication(
200: "dropdb", info, "", login, password, 60); // 1 minute timeout
201:
202: String[] cmd = makeExpectCommandReadingStdin();
203:
204: if (executeNativeCommand(expectFeed, cmd) != 0) {
205: printErrors();
206: throw new BackupException(
207: "dropdb execution did not complete successfully!");
208: }
209:
210: // Re-create the database, use the specified encoding if provided
211: if (logger.isDebugEnabled())
212: logger.debug("Re-creating '" + info.getDbName()
213: + "'");
214:
215: expectFeed = makeExpectDialogueWithAuthentication(
216: "createdb", info,
217: encoding != null ? "--encoding=" + encoding
218: + " " : "", login, password, 60); // 1 minute timeout
219:
220: cmd = makeExpectCommandReadingStdin();
221:
222: if (executeNativeCommand(expectFeed, cmd) != 0) {
223: printErrors();
224: throw new BackupException(
225: "createdb execution did not complete successfully!");
226: }
227:
228: // Run a pre-restore script, if specified
229: if (preRestoreScript != null) {
230: if (logger.isDebugEnabled())
231: logger.debug("Running pre-restore script '"
232: + preRestoreScript + "' on '"
233: + info.getDbName() + "'");
234:
235: expectFeed = makeExpectDialogueWithAuthentication(
236: "psql", info, "--pset pager -f "
237: + preRestoreScript, login,
238: password, 300); // 5
239: // minutes
240: // timeout
241:
242: cmd = makeExpectCommandReadingStdin();
243:
244: if (executeNativeCommand(expectFeed, cmd) != 0) {
245: printErrors();
246: throw new BackupException(
247: "psql execution did not complete successfully!");
248: }
249: }
250:
251: // Use the psql command to rebuild the database
252: if (logger.isDebugEnabled())
253: logger.debug("Rebuilding '" + info.getDbName()
254: + "' from dump '" + dumpName + "'");
255:
256: int timeout = -1;
257: if (restoreTimeout != null) {
258: try {
259: timeout = Integer.parseInt(restoreTimeout);
260: } catch (NumberFormatException e) {
261: logger
262: .error("\""
263: + restoreTimeout
264: + "\" is not a valid restore-timeout value!");
265: timeout = -1;
266: }
267: }
268:
269: expectFeed = makeExpectDialogueWithAuthentication(
270: "psql", info, "--pset pager -f " + fullPath,
271: login, password, timeout); // 30
272: // minutes
273: // timeout
274:
275: cmd = makeExpectCommandReadingStdin();
276:
277: if (executeNativeCommand(expectFeed, cmd) != 0) {
278: printErrors();
279: throw new BackupException(
280: "psql execution did not complete successfully!");
281: }
282:
283: // Run a post-restore script, if specified
284: if (postRestoreScript != null) {
285: if (logger.isDebugEnabled())
286: logger.debug("Running post-restore script '"
287: + postRestoreScript + "' on '"
288: + info.getDbName() + "'");
289:
290: expectFeed = makeExpectDialogueWithAuthentication(
291: "psql", info, "--pset pager -f "
292: + postRestoreScript, login,
293: password, 300); // 5
294: // minutes
295: // timeout
296:
297: cmd = makeExpectCommandReadingStdin();
298:
299: if (executeNativeCommand(expectFeed, cmd) != 0) {
300: printErrors();
301: throw new BackupException(
302: "psql execution did not complete successfully!");
303: }
304: }
305: } else
306: // No authentication
307: {
308: // Drop the database if it already exists
309: if (logger.isDebugEnabled())
310: logger.debug("Dropping database '"
311: + info.getDbName() + "'");
312:
313: String dropCmd = makeCommand("dropdb", info, "", login);
314: if (executeNativeCommand(dropCmd) != 0) {
315: printErrors();
316: throw new BackupException(
317: "dropdb execution did not complete successfully!");
318: }
319:
320: // Re-create the database, use the specified encoding if provided
321: if (logger.isDebugEnabled())
322: logger.debug("Re-creating '" + info.getDbName()
323: + "'");
324:
325: String createCmd = makeCommand("createdb", info,
326: encoding != null ? "--encoding=" + encoding
327: + " " : "", login);
328: if (executeNativeCommand(createCmd) != 0) {
329: printErrors();
330: throw new BackupException(
331: "createdb execution did not complete successfully!");
332: }
333:
334: // Run a pre-restore script, if specified
335: if (preRestoreScript != null) {
336: if (logger.isDebugEnabled())
337: logger.debug("Running pre-restore script '"
338: + preRestoreScript + "' on '"
339: + info.getDbName() + "'");
340:
341: String preRestoreCmd = makeCommand("psql", info,
342: "--pset pager -f " + preRestoreScript,
343: login);
344: if (executeNativeCommand(preRestoreCmd) != 0) {
345: printErrors();
346: throw new BackupException(
347: "psql execution did not complete successfully!");
348: }
349: }
350:
351: // Use the psql command to rebuild the database
352: if (logger.isDebugEnabled())
353: logger.debug("Rebuilding '" + info.getDbName()
354: + "' from dump '" + dumpName + "'");
355:
356: String replayCmd = makeCommand("psql", info,
357: "--pset pager -f " + fullPath, login);
358: if (executeNativeCommand(replayCmd) != 0) {
359: printErrors();
360: throw new BackupException(
361: "psql execution did not complete successfully!");
362: }
363:
364: // Run a post-restore script, if specified
365: if (postRestoreScript != null) {
366: if (logger.isDebugEnabled())
367: logger.debug("Running post-restore script '"
368: + postRestoreScript + "' on '"
369: + info.getDbName() + "'");
370:
371: String postRestoreCmd = makeCommand("psql", info,
372: "--pset pager -f " + postRestoreScript,
373: login);
374: if (executeNativeCommand(postRestoreCmd) != 0) {
375: printErrors();
376: throw new BackupException(
377: "psql execution did not complete successfully!");
378: }
379: }
380: }
381: } catch (Exception e) {
382: String msg = "Error while performing backup";
383: logger.error(msg, e);
384: throw new BackupException(msg, e);
385: }
386: }
387:
388: /**
389: * @see org.continuent.sequoia.controller.backup.Backuper#fetchDump(org.continuent.sequoia.controller.backup.DumpTransferInfo,
390: * java.lang.String, java.lang.String)
391: */
392: public void fetchDump(DumpTransferInfo dumpTransferInfo,
393: String path, String dumpName) throws BackupException,
394: IOException {
395: BackupManager.fetchDumpFile(dumpTransferInfo, path, dumpName
396: + ".sql");
397: }
398:
399: /**
400: * @see org.continuent.sequoia.controller.backup.Backuper#deleteDump(java.lang.String,
401: * java.lang.String)
402: */
403: public void deleteDump(String path, String dumpName)
404: throws BackupException {
405: super .deleteDump(path, dumpName + ".sql");
406: }
407: }
|