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: *
021: */package org.continuent.sequoia.controller.backup.backupers;
022:
023: import java.io.File;
024: import java.io.FilenameFilter;
025: import java.io.IOException;
026: import java.util.ArrayList;
027: import java.util.Date;
028:
029: import org.continuent.sequoia.common.exceptions.BackupException;
030: import org.continuent.sequoia.common.log.Trace;
031: import org.continuent.sequoia.controller.backend.DatabaseBackend;
032: import org.continuent.sequoia.controller.backup.BackupManager;
033: import org.continuent.sequoia.controller.backup.DumpTransferInfo;
034:
035: /**
036: * This class defines a Backuper for PostgreSQL databases. This backuper makes
037: * dumps in a plain-text format.
038: * *****************************************************************************
039: * This class backups PostgreSQL database usig pg_dump. backup command from
040: * console executes the pg_dump with split, and example pg_dump dbName -h
041: * 127.0.0.1 -p 5432 -U postgres | split -a 4 -b 10m - /tmp/dumpFile which does
042: * spliting ONE single dump file to splitted files. Size of the splitted files
043: * are determined by virtual xml file in options for Backuper, and example
044: * <Backuper backuperName="splitter"
045: * className="org.continuent.sequoia.controller.backup.backupers.PostgreSQLSplitPlainTextBackuper"
046: * options="zip=true,splitSize=10m"/> default size is 1000m Restore command
047: * restores the DB from splitted files, and example cat /tmp/dumpFile* | psql
048: * dbName -h 127.0.0.1 -p 5432 -U postgres restore checks the existing of dump
049: * file for first suffix dumpFileaaaa (suffix is set to 4) Contributer also
050: * added two methods and one varibale to AbstractPostgreSQLBackuper class
051: * protected String[] makeSplitCommand(String command, PostgreSQLUrlInfo info,
052: * String options, String login) protected String[]
053: * makeSplitCommandWithAuthentication(String command, PostgreSQLUrlInfo info,
054: * String options, String login, String password, boolean isPsql) static
055: * protected String SPLIT_SIZE = "1000m"; Contributor for this class: Gurkan
056: * Ozfidan
057: * *****************************************************************************
058: * <p>
059: * Supported URLs are:
060: * <ul>
061: * <li>jdbc:postgresql://host:port/dbname?param1=foo,param2=bar</li>
062: * <li>jdbc:postgresql://host/dbname?param1=foo,param2=bar</li>
063: * <li>jdbc:postgresql:dbname?param1=foo,param2=bar</li>
064: * </ul>
065: *
066: * @author <a href="mailto:emmanuel.cecchet@emicnetworks.com">Emmanuel Cecchet</a>
067: * @author <a href="mailto:dhansen@h2st.com">Dylan Hansen</a>
068: * @author <a href="mailto:mathieu.peltier@emicnetworks.com">Mathieu Peltier</a>
069: * @author <a href="mailto:olivier.fambon@emicnetworks.com">Olivier Fambon</a>
070: * @version 1.0
071: */
072: public class PostgreSQLSplitPlainTextBackuper extends
073: AbstractPostgreSQLBackuper {
074: // Logger
075: static Trace logger = Trace
076: .getLogger(PostgreSQLSplitPlainTextBackuper.class.getName());
077:
078: /**
079: * The dump format for this (family of) backuper.
080: */
081: public static final String DUMP_FORMAT = "PostgreSQL Split Plain Text Dump";
082:
083: /**
084: * @see org.continuent.sequoia.controller.backup.Backuper#getDumpFormat()
085: */
086: public String getDumpFormat() {
087: return DUMP_FORMAT;
088: }
089:
090: /**
091: * Backups the DB using pg_dump/split commands; It creates splitted dump files
092: * instead of one single(huge) dump file
093: *
094: * @see org.continuent.sequoia.controller.backup.Backuper#backup(DatabaseBackend,
095: * String, String, String, String, ArrayList)
096: */
097: public Date backup(DatabaseBackend backend, String login,
098: String password, String dumpName, String path,
099: ArrayList tables) throws BackupException {
100: // Parse the URL for the connection information
101: String url = backend.getURL();
102: PostgreSQLUrlInfo info = new AbstractPostgreSQLBackuper.PostgreSQLUrlInfo(
103: url);
104:
105: if (logger.isDebugEnabled())
106: logger.debug("Backing up database '" + info.getDbName()
107: + "' on host '" + info.getHost() + ":"
108: + info.getPort() + "'");
109:
110: try {
111:
112: // Create the path, if it does not already exist
113: File pathDir = new File(path);
114: if (!pathDir.exists()) {
115: pathDir.mkdirs();
116: pathDir.mkdir();
117: }
118:
119: String fullPath = getDumpPhysicalPath(path, dumpName);
120:
121: int exitValue = -1;
122: if (useAuthentication) {
123: if (logger.isDebugEnabled())
124: logger
125: .debug("Performing backup using authentication");
126:
127: String[] gCmd = makeSplitCommandWithAuthentication(
128: "pg_dump", info, " | split -a 4 -b 10m - "
129: + fullPath, login, password, false);
130:
131: exitValue = executeNativeCommand(gCmd);
132: } else {
133: String[] gCmdArray = makeSplitCommand("pg_dump", info,
134: " | split -a 4 -b " + splitSize + " - "
135: + fullPath, login);
136:
137: exitValue = executeNativeCommand(gCmdArray);
138: }
139:
140: if (exitValue != 0) {
141: printErrors();
142: throw new BackupException(
143: "pg_dump execution did not complete successfully!");
144: }
145:
146: } catch (Exception e) {
147: String msg = "Error while performing backup";
148: logger.error(msg, e);
149: throw new BackupException(msg, e);
150: }
151:
152: return new Date(System.currentTimeMillis());
153: }
154:
155: /**
156: * Restores the DB using cat/psql commands; It restores the DB using splitted
157: * dump files.
158: *
159: * @see org.continuent.sequoia.controller.backup.Backuper#restore(DatabaseBackend,
160: * String, String, String, String, ArrayList)
161: */
162: public void restore(DatabaseBackend backend, String login,
163: String password, String dumpName, String path,
164: ArrayList tables) throws BackupException {
165: // Parse the URL for the connection information
166: String url = backend.getURL();
167: PostgreSQLUrlInfo info = new AbstractPostgreSQLBackuper.PostgreSQLUrlInfo(
168: url);
169:
170: if (logger.isDebugEnabled())
171: logger.debug("Restoring database '" + info.getDbName()
172: + "' on host '" + info.getHost() + ":"
173: + info.getPort() + "'");
174:
175: // Check to see if the given path + dumpName exists
176: String fullPath = getDumpPhysicalPath(path, dumpName);
177:
178: File dump = new File(fullPath + "aaaa");// checking only first split file,
179: // suffix length is 4
180: if (!dump.exists())
181: throw new BackupException("Backup '" + fullPath
182: + "' does not exist!");
183:
184: try {
185: if (useAuthentication) {
186: if (logger.isInfoEnabled())
187: logger
188: .info("Performing database operations using authentication");
189:
190: // Drop the database if it already exists
191: if (logger.isDebugEnabled())
192: logger.debug("Dropping database '"
193: + info.getDbName() + "'");
194:
195: String[] dropCmd = makeCommandWithAuthentication(
196: "dropdb", info, "", login, password, false);
197: if (executeNativeCommand(dropCmd) != 0) {
198: printErrors();
199: throw new BackupException(
200: "dropdb execution did not complete successfully!");
201: }
202:
203: // Re-create the database, use the specified encoding if provided
204: if (logger.isDebugEnabled())
205: logger.debug("Re-creating '" + info.getDbName()
206: + "'");
207:
208: String[] createCmd = makeCommandWithAuthentication(
209: "createdb", info,
210: encoding != null ? "--encoding=" + encoding
211: + " " : "", login, password, false);
212: if (executeNativeCommand(createCmd) != 0) {
213: printErrors();
214: throw new BackupException(
215: "createdb execution did not complete successfully!");
216: }
217:
218: // Run a pre-restore script, if specified
219: if (preRestoreScript != null) {
220: if (logger.isDebugEnabled())
221: logger.debug("Running pre-restore script '"
222: + preRestoreScript + "' on '"
223: + info.getDbName() + "'");
224:
225: String[] preRestoreCmd = makeCommandWithAuthentication(
226: "psql", info, " -f " + preRestoreScript,
227: login, password, true);
228:
229: if (executeNativeCommand(preRestoreCmd) != 0) {
230: printErrors();
231: throw new BackupException(
232: "psql execution did not complete successfully!");
233: }
234: }
235:
236: // Use the psql command to rebuild the database
237: if (logger.isDebugEnabled())
238: logger.debug("Rebuilding '" + info.getDbName()
239: + "' from dump '" + dumpName + "'");
240:
241: String[] replayCmd = makeSplitCommandWithAuthentication(
242: "cat" + " " + fullPath + "* | psql", info, "",
243: login, password, false);
244:
245: if (executeNativeCommand(replayCmd) != 0) {
246: printErrors();
247: throw new BackupException(
248: "pg_restore execution did not complete successfully!");
249: }
250:
251: // Run a post-restore script, if specified
252: if (postRestoreScript != null) {
253: if (logger.isDebugEnabled())
254: logger.debug("Running post-restore script '"
255: + postRestoreScript + "' on '"
256: + info.getDbName() + "'");
257:
258: String[] postRestoreCmd = makeCommandWithAuthentication(
259: "psql", info, " -f " + postRestoreScript,
260: login, password, true);
261:
262: if (executeNativeCommand(postRestoreCmd) != 0) {
263: printErrors();
264: throw new BackupException(
265: "psql execution did not complete successfully!");
266: }
267: }
268: } else
269: // No authentication
270: {
271: // Drop the database if it already exists
272: if (logger.isDebugEnabled())
273: logger.debug("Dropping database '"
274: + info.getDbName() + "'");
275:
276: String dropCmd = makeCommand("dropdb", info, "", login);
277: if (executeNativeCommand(dropCmd) != 0) {
278: printErrors();
279: throw new BackupException(
280: "dropdb execution did not complete successfully!");
281: }
282:
283: // Re-create the database, use the specified encoding if provided
284: if (logger.isDebugEnabled())
285: logger.debug("Re-creating '" + info.getDbName()
286: + "'");
287:
288: String createCmd = makeCommand("createdb", info,
289: encoding != null ? "--encoding=" + encoding
290: + " " : "", login);
291: if (executeNativeCommand(createCmd) != 0) {
292: printErrors();
293: throw new BackupException(
294: "createdb execution did not complete successfully!");
295: }
296:
297: // Run a pre-restore script, if specified
298: if (preRestoreScript != null) {
299: if (logger.isDebugEnabled())
300: logger.debug("Running pre-restore script '"
301: + preRestoreScript + "' on '"
302: + info.getDbName() + "'");
303:
304: String preRestoreCmd = makeCommand("psql", info,
305: " -f " + preRestoreScript, login);
306: if (executeNativeCommand(preRestoreCmd) != 0) {
307: printErrors();
308: throw new BackupException(
309: "psql execution did not complete successfully!");
310: }
311: }
312:
313: // Use the psql command to rebuild the database
314: if (logger.isDebugEnabled())
315: logger.debug("Rebuilding '" + info.getDbName()
316: + "' from dump '" + dumpName + "'");
317:
318: String[] cmdArray = makeSplitCommand("cat" + " "
319: + fullPath + "* | psql", info, "", login);
320:
321: if (executeNativeCommand(cmdArray) != 0) {
322: printErrors();
323: throw new BackupException(
324: "psql execution did not complete successfully!");
325: }
326:
327: // Run a post-restore script, if specified
328: if (postRestoreScript != null) {
329: if (logger.isDebugEnabled())
330: logger.debug("Running post-restore script '"
331: + postRestoreScript + "' on '"
332: + info.getDbName() + "'");
333:
334: String postRestoreCmd = makeCommand("psql", info,
335: " -f " + postRestoreScript, login);
336: if (executeNativeCommand(postRestoreCmd) != 0) {
337: printErrors();
338: throw new BackupException(
339: "psql execution did not complete successfully!");
340: }
341: }
342:
343: }
344: } catch (Exception e) {
345: String msg = "Error while performing backup";
346: logger.error(msg, e);
347: throw new BackupException(msg, e);
348: }
349: }
350:
351: /**
352: * @see org.continuent.sequoia.controller.backup.Backuper#fetchDump(org.continuent.sequoia.controller.backup.DumpTransferInfo,
353: * java.lang.String, java.lang.String)
354: */
355: public void fetchDump(DumpTransferInfo dumpTransferInfo,
356: String path, String dumpName) throws BackupException,
357: IOException {
358: BackupManager.fetchDumpFile(dumpTransferInfo, path, dumpName
359: + ".sql");
360: }
361:
362: /**
363: * @see org.continuent.sequoia.controller.backup.Backuper#deleteDump(java.lang.String,
364: * java.lang.String)
365: */
366: public void deleteDump(String path, String dumpName)
367: throws BackupException {
368: File dir = new File(path);
369:
370: // Only list files that start with the given dump name
371: String[] files = dir.list(new DumpNameFilter(dumpName));
372:
373: for (int i = 0; i < files.length; i++) {
374: File toRemove = new File(
375: getDumpPhysicalPath(path, files[i]));
376: if (logger.isDebugEnabled())
377: logger.debug("Deleting dump " + toRemove);
378: toRemove.delete();
379: }
380: }
381:
382: /**
383: * Private class that filters files based on beginning of file name. This
384: * should be equal to the dump file(s) we want to delete.
385: *
386: * @author Dylan Hansen
387: */
388: class DumpNameFilter implements FilenameFilter {
389: private String dumpName;
390:
391: /**
392: * Creates a new instance of DumpNameFilter
393: *
394: * @param dumpName Dump name for set of file(s)
395: * @author Dylan Hansen
396: */
397: public DumpNameFilter(String dumpName) {
398: this .dumpName = dumpName;
399: }
400:
401: /**
402: * Does this file match the filter?
403: *
404: * @param dir Directory of file, not used
405: * @param theDump Name of current dump we're checking
406: * @author Dylan Hansen
407: */
408: public boolean accept(File dir, String theDump) {
409: return (theDump.startsWith(dumpName) && theDump.length() == dumpName
410: .length() + 4);
411: }
412: }
413:
414: }
|