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 creates
035: * dumps in the tar format, using the "--format=t" switch on pg_dump.
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 PostgreSQLTarBackuper extends AbstractPostgreSQLBackuper {
051: // Logger
052: static Trace logger = Trace.getLogger(PostgreSQLTarBackuper.class
053: .getName());
054:
055: /**
056: * The dump format for this (family of) backuper.
057: */
058: public static final String DUMP_FORMAT = "PostgreSQL Tar Dump";
059:
060: /**
061: * @see org.continuent.sequoia.controller.backup.Backuper#getDumpFormat()
062: */
063: public String getDumpFormat() {
064: return DUMP_FORMAT;
065: }
066:
067: /**
068: * @see org.continuent.sequoia.controller.backup.Backuper#backup(DatabaseBackend,
069: * String, String, String, String, ArrayList)
070: */
071: public Date backup(DatabaseBackend backend, String login,
072: String password, String dumpName, String path,
073: ArrayList tables) throws BackupException {
074: // Parse the URL for the connection information
075: String url = backend.getURL();
076: PostgreSQLUrlInfo info = new AbstractPostgreSQLBackuper.PostgreSQLUrlInfo(
077: url);
078:
079: if (logger.isDebugEnabled())
080: logger.debug("Backing up database '" + info.getDbName()
081: + "' on host '" + info.getHost() + ":"
082: + info.getPort() + "'");
083:
084: try {
085: // Create the path, if it does not already exist
086: File pathDir = new File(path);
087: if (!pathDir.exists()) {
088: pathDir.mkdirs();
089: pathDir.mkdir();
090: }
091: String fullPath = getDumpPhysicalPath(path, dumpName)
092: + ".tar";
093:
094: int exitValue = -1;
095: String dumpOptions = "";
096: if (pgDumpFlags != null) {
097: if (pgDumpFlags.indexOf("-F") >= 0
098: || pgDumpFlags.indexOf("--format=") >= 0) {
099: logger
100: .error("Invalid option in pgDumpFlags \""
101: + pgDumpFlags
102: + "\". You are not allowed to set the format of the dump!");
103: } else {
104: dumpOptions = " " + pgDumpFlags + " ";
105: }
106: }
107: if (useAuthentication) {
108: if (logger.isDebugEnabled())
109: logger
110: .debug("Performing backup using authentication");
111: int timeout = -1;
112: if (dumpTimeout != null) {
113: try {
114: timeout = Integer.parseInt(dumpTimeout);
115: } catch (NumberFormatException e) {
116: logger
117: .error("\""
118: + dumpTimeout
119: + "\" is not a valid dump-timeout value!");
120: timeout = -1;
121: }
122: }
123: String[] expectFeed = makeExpectDialogueWithAuthentication(
124: "pg_dump", info, " --format=t -f " + fullPath
125: + dumpOptions, login, password, timeout);
126:
127: String[] cmd = makeExpectCommandReadingStdin();
128:
129: exitValue = executeNativeCommand(expectFeed, cmd);
130: } else {
131: if (pgDumpFlags != null) {
132: if (pgDumpFlags.indexOf("-U") >= 0
133: || pgDumpFlags.indexOf("-W") >= 0) {
134: logger
135: .error("Invalid option in pgDumpFlags \""
136: + pgDumpFlags
137: + "\". Set \"authentication=true\" if you want to use authentication!");
138: } else {
139: dumpOptions = " " + pgDumpFlags + " ";
140: }
141: }
142:
143: String cmd = makeCommand("pg_dump", info,
144: " --format=t -f " + fullPath + dumpOptions,
145: 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) + ".tar";
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 pg_restore 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: "pg_restore", info, "--format=t -d "
271: + info.getDbName() + " " + fullPath,
272: login, password, timeout); // 30 minutes timeout
273:
274: cmd = makeExpectCommandReadingStdin();
275:
276: if (executeNativeCommand(expectFeed, cmd) != 0) {
277: printErrors();
278: throw new BackupException(
279: "pg_restore execution did not complete successfully!");
280: }
281:
282: // Run a post-restore script, if specified
283: if (postRestoreScript != null) {
284: if (logger.isDebugEnabled())
285: logger.debug("Running post-restore script '"
286: + postRestoreScript + "' on '"
287: + info.getDbName() + "'");
288:
289: expectFeed = makeExpectDialogueWithAuthentication(
290: "psql", info, "--pset pager -f "
291: + postRestoreScript, login,
292: password, 300); // 5
293: // minutes
294: // timeout
295:
296: cmd = makeExpectCommandReadingStdin();
297:
298: if (executeNativeCommand(expectFeed, cmd) != 0) {
299: printErrors();
300: throw new BackupException(
301: "psql execution did not complete successfully!");
302: }
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("pg_restore", info,
357: "--format=t -d " + info.getDbName() + " "
358: + fullPath, login);
359: if (executeNativeCommand(replayCmd) != 0) {
360: printErrors();
361: throw new BackupException(
362: "pg_restore execution did not complete successfully!");
363: }
364:
365: // Run a post-restore script, if specified
366: if (postRestoreScript != null) {
367: if (logger.isDebugEnabled())
368: logger.debug("Running post-restore script '"
369: + postRestoreScript + "' on '"
370: + info.getDbName() + "'");
371:
372: String postRestoreCmd = makeCommand("psql", info,
373: "--pset pager -f " + postRestoreScript,
374: login);
375: if (executeNativeCommand(postRestoreCmd) != 0) {
376: printErrors();
377: throw new BackupException(
378: "psql execution did not complete successfully!");
379: }
380: }
381:
382: }
383: } catch (Exception e) {
384: String msg = "Error while performing backup";
385: logger.error(msg, e);
386: throw new BackupException(msg, e);
387: }
388: }
389:
390: /**
391: * @see org.continuent.sequoia.controller.backup.Backuper#fetchDump(org.continuent.sequoia.controller.backup.DumpTransferInfo,
392: * java.lang.String, java.lang.String)
393: */
394: public void fetchDump(DumpTransferInfo dumpTransferInfo,
395: String path, String dumpName) throws BackupException,
396: IOException {
397: BackupManager.fetchDumpFile(dumpTransferInfo, path, dumpName
398: + ".tar");
399: }
400:
401: /**
402: * @see org.continuent.sequoia.controller.backup.Backuper#deleteDump(java.lang.String,
403: * java.lang.String)
404: */
405: public void deleteDump(String path, String dumpName)
406: throws BackupException {
407: super .deleteDump(path, dumpName + ".tar");
408: }
409:
410: }
|