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 a binary format, using the "--format=c" 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 PostgreSQLBinaryBackuper extends
051: AbstractPostgreSQLBackuper {
052: // Logger
053: static Trace logger = Trace
054: .getLogger(PostgreSQLBinaryBackuper.class.getName());
055:
056: /**
057: * The dump format for this (family of) backuper.
058: *
059: * @see org.continuent.sequoia.controller.backup.Backuper#getDumpFormat()
060: */
061: public String getDumpFormat() {
062: return "PostgreSQL Binary Dump";
063: }
064:
065: /**
066: * @see org.continuent.sequoia.controller.backup.Backuper#backup(DatabaseBackend,
067: * String, String, String, String, ArrayList)
068: */
069: public Date backup(DatabaseBackend backend, String login,
070: String password, String dumpName, String path,
071: ArrayList tables) throws BackupException {
072: // Parse the URL for the connection information
073: String url = backend.getURL();
074: PostgreSQLUrlInfo info = new AbstractPostgreSQLBackuper.PostgreSQLUrlInfo(
075: url);
076:
077: if (logger.isDebugEnabled())
078: logger.debug("Backing up database '" + info.getDbName()
079: + "' on host '" + info.getHost() + ":"
080: + info.getPort() + "'");
081:
082: try {
083: // Create the path, if it does not already exist
084: File pathDir = new File(path);
085: if (!pathDir.exists()) {
086: pathDir.mkdirs();
087: pathDir.mkdir();
088: }
089: String fullPath = getDumpPhysicalPath(path, dumpName);
090:
091: boolean succeeded = false;
092: String dumpOptions = "";
093: if (pgDumpFlags != null) {
094: if (pgDumpFlags.indexOf("-F") >= 0
095: || pgDumpFlags.indexOf("--format=") >= 0) {
096: logger
097: .error("Invalid option in pgDumpFlags \""
098: + pgDumpFlags
099: + "\". You are not allowed to set the format of the dump!");
100: } else {
101: dumpOptions = " " + pgDumpFlags + " ";
102: }
103: }
104: if (useAuthentication) {
105: // TODO: Check if this works--it should be disabled?
106: if (logger.isDebugEnabled())
107: logger
108: .debug("Performing backup using authentication");
109: int timeout = -1;
110: if (dumpTimeout != null) {
111: try {
112: timeout = Integer.parseInt(dumpTimeout);
113: } catch (NumberFormatException e) {
114: logger
115: .error("\""
116: + dumpTimeout
117: + "\" is not a valid dump-timeout value!");
118: timeout = -1;
119: }
120: }
121: String[] expectFeed = makeExpectDialogueWithAuthentication(
122: "pg_dump", info, " --format=c -f " + fullPath
123: + dumpOptions, login, password, timeout);
124:
125: String[] cmd = makeExpectCommandReadingStdin();
126:
127: succeeded = safelyExecNativeCommand(cmd, expectFeed, 0);
128: } else {
129: if (pgDumpFlags != null) {
130: if (pgDumpFlags.indexOf("-U") >= 0
131: || pgDumpFlags.indexOf("-W") >= 0) {
132: logger
133: .error("Invalid option in pgDumpFlags \""
134: + pgDumpFlags
135: + "\". Set \"authentication=true\" if you want to use authentication!");
136: } else {
137: dumpOptions = " " + pgDumpFlags + " ";
138: }
139: }
140:
141: String cmd = makeCommand("pg_dump", info,
142: " --format=c -f " + fullPath + dumpOptions,
143: login);
144: succeeded = safelyExecNativeCommand(cmd, null, 0);
145: }
146:
147: if (!succeeded) {
148: throw new BackupException(
149: "pg_dump execution did not complete successfully!");
150: }
151:
152: } catch (Exception e) {
153: String msg = "Error while performing backup";
154: logger.error(msg, e);
155: throw new BackupException(msg, e);
156: }
157:
158: return new Date(System.currentTimeMillis());
159: }
160:
161: /**
162: * @see org.continuent.sequoia.controller.backup.Backuper#restore(DatabaseBackend,
163: * String, String, String, String, ArrayList)
164: */
165: public void restore(DatabaseBackend backend, String login,
166: String password, String dumpName, String path,
167: ArrayList tables) throws BackupException {
168: // Parse the URL for the connection information
169: String url = backend.getURL();
170: PostgreSQLUrlInfo info = new AbstractPostgreSQLBackuper.PostgreSQLUrlInfo(
171: url);
172:
173: if (logger.isDebugEnabled())
174: logger.debug("Restoring database '" + info.getDbName()
175: + "' on host '" + info.getHost() + ":"
176: + info.getPort() + "'");
177:
178: // Check to see if the given path + dumpName exists
179: String fullPath = getDumpPhysicalPath(path, dumpName);
180: File dump = new File(fullPath);
181: if (!dump.exists())
182: throw new BackupException("Backup '" + fullPath
183: + "' does not exist!");
184:
185: try {
186: if (useAuthentication) {
187: if (logger.isInfoEnabled())
188: logger
189: .info("Performing database operations using authentication");
190:
191: // Drop the database if it already exists
192: if (logger.isDebugEnabled())
193: logger.debug("Dropping database '"
194: + info.getDbName() + "'");
195:
196: String[] expectFeed = makeExpectDialogueWithAuthentication(
197: "dropdb", info, "", login, password, 60); // 1 minute timeout
198:
199: String[] cmd = makeExpectCommandReadingStdin();
200:
201: if (!safelyExecNativeCommand(cmd, expectFeed, 0)) {
202: logger
203: .warn("Unable to drop database prior to restore");
204: }
205:
206: // Re-create the database, use the specified encoding if provided
207: if (logger.isDebugEnabled())
208: logger.debug("Re-creating '" + info.getDbName()
209: + "'");
210:
211: expectFeed = makeExpectDialogueWithAuthentication(
212: "createdb", info,
213: encoding != null ? "--encoding=" + encoding
214: + " " : "", login, password, 60); // 1 minute timeout
215:
216: cmd = makeExpectCommandReadingStdin();
217:
218: if (!safelyExecNativeCommand(cmd, expectFeed, 0)) {
219: throw new BackupException(
220: "createdb execution did not complete successfully!");
221: }
222:
223: // Run a pre-restore script, if specified
224: if (preRestoreScript != null) {
225: if (logger.isDebugEnabled())
226: logger.debug("Running pre-restore script '"
227: + preRestoreScript + "' on '"
228: + info.getDbName() + "'");
229:
230: expectFeed = makeExpectDialogueWithAuthentication(
231: getPsqlCommand(), info, "--pset pager -f "
232: + preRestoreScript, login,
233: password, 300); // 5
234: // minutes
235: // timeout
236:
237: cmd = makeExpectCommandReadingStdin();
238:
239: if (!safelyExecNativeCommand(expectFeed, cmd, 0)) {
240: throw new BackupException(
241: getPsqlCommand()
242: + " execution did not complete successfully!");
243: }
244: }
245:
246: // Use the pg_restore command to rebuild the database
247: if (logger.isDebugEnabled())
248: logger.debug("Rebuilding '" + info.getDbName()
249: + "' from dump '" + dumpName + "'");
250:
251: int timeout = -1;
252: if (restoreTimeout != null) {
253: try {
254: timeout = Integer.parseInt(restoreTimeout);
255: } catch (NumberFormatException e) {
256: logger
257: .error("\""
258: + restoreTimeout
259: + "\" is not a valid restore-timeout value!");
260: timeout = -1;
261: }
262: }
263:
264: expectFeed = makeExpectDialogueWithAuthentication(
265: "pg_restore", info, "--format=c -d "
266: + info.getDbName() + " " + fullPath,
267: login, password, timeout); // 30 minutes timeout
268:
269: cmd = makeExpectCommandReadingStdin();
270:
271: if (!safelyExecNativeCommand(cmd, expectFeed, 0)) {
272: throw new BackupException(
273: "pg_restore execution did not complete successfully!");
274: }
275:
276: // Run a post-restore script, if specified
277: if (postRestoreScript != null) {
278: if (logger.isDebugEnabled())
279: logger.debug("Running post-restore script '"
280: + postRestoreScript + "' on '"
281: + info.getDbName() + "'");
282:
283: expectFeed = makeExpectDialogueWithAuthentication(
284: getPsqlCommand(), info, "--pset pager -f "
285: + postRestoreScript, login,
286: password, 300); // 5
287: // minutes
288: // timeout
289:
290: cmd = makeExpectCommandReadingStdin();
291:
292: if (!safelyExecNativeCommand(expectFeed, cmd, 0)) {
293: throw new BackupException(
294: getPsqlCommand()
295: + " execution did not complete successfully!");
296: }
297: }
298: } else
299: // No authentication
300: {
301: // Drop the database if it already exists
302: if (logger.isDebugEnabled())
303: logger.debug("Dropping database '"
304: + info.getDbName() + "'");
305:
306: String dropCmd = makeCommand("dropdb", info, "", login);
307: if (!safelyExecNativeCommand(dropCmd, null, 0)) {
308: // Errors can happen there, e.g. if the database does not exist yet.
309: // Just log them, and carry-on...
310: logger
311: .warn("Unable to drop database prior to restore");
312: }
313:
314: // Re-create the database, use the specified encoding if provided
315: if (logger.isDebugEnabled())
316: logger.debug("Re-creating '" + info.getDbName()
317: + "'");
318:
319: String createCmd = makeCommand("createdb", info,
320: encoding != null ? "--encoding=" + encoding
321: + " " : "", login);
322: if (!safelyExecNativeCommand(createCmd, null, 0)) {
323: throw new BackupException(
324: "createdb execution did not complete successfully!");
325: }
326:
327: // Run a pre-restore script, if specified
328: if (preRestoreScript != null) {
329: if (logger.isDebugEnabled())
330: logger.debug("Running pre-restore script '"
331: + preRestoreScript + "' on '"
332: + info.getDbName() + "'");
333:
334: String preRestoreCmd = makeCommand(
335: getPsqlCommand(), info, "--pset pager -f "
336: + preRestoreScript, login);
337: if (!safelyExecNativeCommand(preRestoreCmd, null, 0)) {
338: printErrors();
339: throw new BackupException(
340: getPsqlCommand()
341: + " execution did not complete successfully!");
342: }
343: }
344:
345: // Use the pg_restore command to rebuild the database
346: if (logger.isDebugEnabled())
347: logger.debug("Rebuilding '" + info.getDbName()
348: + "' from dump '" + dumpName + "'");
349:
350: String replayCmd = makeCommand("pg_restore", info,
351: "--format=c -d " + info.getDbName() + " "
352: + fullPath, login);
353: if (!safelyExecNativeCommand(replayCmd, null, 0)) {
354: throw new BackupException(
355: "pg_restore execution did not complete successfully!");
356: }
357:
358: // Run a post-restore script, if specified
359: if (postRestoreScript != null) {
360: if (logger.isDebugEnabled())
361: logger.debug("Running post-restore script '"
362: + postRestoreScript + "' on '"
363: + info.getDbName() + "'");
364:
365: String postRestoreCmd = makeCommand(
366: getPsqlCommand(), info, "--pset pager -f "
367: + postRestoreScript, login);
368: if (!safelyExecNativeCommand(postRestoreCmd, null,
369: 0)) {
370: throw new BackupException(
371: getPsqlCommand()
372: + " execution did not complete successfully!");
373: }
374: }
375: }
376: } catch (Exception e) {
377: String msg = "Error while performing backup";
378: logger.error(msg, e);
379: throw new BackupException(msg, e);
380: }
381: }
382:
383: /**
384: * @see org.continuent.sequoia.controller.backup.Backuper#fetchDump(org.continuent.sequoia.controller.backup.DumpTransferInfo,
385: * java.lang.String, java.lang.String)
386: */
387: public void fetchDump(DumpTransferInfo dumpTransferInfo,
388: String path, String dumpName) throws BackupException,
389: IOException {
390: BackupManager.fetchDumpFile(dumpTransferInfo, path, dumpName);
391: }
392: }
|