/*
* The MIT License
*
* Copyright (c) 2009-2010, Vincent Sellier, Manufacture Fran�aise des Pneumatiques Michelin, Romain Seguy
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jvnet.hudson.plugins.backup.utils;
import hudson.model.Computer;
import hudson.model.Hudson;
import hudson.security.ACL;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.regex.Matcher;
import org.acegisecurity.context.SecurityContextHolder;
import org.apache.commons.io.filefilter.DelegateFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.codehaus.plexus.util.DirectoryScanner;
import org.jvnet.hudson.plugins.backup.BackupConfig;
import org.jvnet.hudson.plugins.backup.BackupException;
import org.jvnet.hudson.plugins.backup.utils.filename.SimpleFileFilter;
/**
* This is the backup task, run in background and log to a file
*
* @author vsellier
*/
public class BackupTask extends BackupPluginTask {
public final static String JOBS_NAME = "jobs";
public final static String WORKSPACE_NAME = "workspace";
public final static String FINGERPRINTS_NAME = "fingerprints";
public final static String BUILDS_NAME = "builds";
public final static String ARCHIVE_NAME = "archive";
public final static String[] DEFAULT_EXCLUSIONS = { "backup.log" };
/**
* delay between two verification of no running processes
*/
private final static Integer DELAY = 5000;
public BackupTask(BackupConfig configuration, String hudsonWorkDir, String backupFileName, String logFilePath) {
super(configuration, hudsonWorkDir, backupFileName, logFilePath);
}
public void run() {
assert (logFilePath != null);
assert (configuration.getFileNameTemplate() != null);
SecurityContextHolder.getContext().setAuthentication(ACL.SYSTEM);
startDate = new Date();
try {
logger = new BackupLogger(logFilePath, configuration.isVerbose());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Unable to open log file for writing: {0}", logFilePath);
return;
}
logger.info("Backup started at " + getTimestamp(startDate));
// Have to include shutdown time in backup time ?
waitNoJobsInQueue();
// file filter specific to what's inside jobs' worskpace
IOFileFilter jobsExclusionFileFilter = null;
// Creating global exclusions
List<String> exclusions = new ArrayList<String>();
exclusions.addAll(Arrays.asList(DEFAULT_EXCLUSIONS));
exclusions.addAll(configuration.getCustomExclusions());
if (! configuration.getKeepWorkspaces()) {
exclusions.add(WORKSPACE_NAME);
}
else {
jobsExclusionFileFilter = createJobsExclusionFileFilter(
hudsonWorkDir,
configuration.getJobIncludes(),
configuration.getJobExcludes(),
configuration.getCaseSensitive());
}
if (! configuration.getKeepFingerprints()) {
exclusions.add(FINGERPRINTS_NAME);
}
if (! configuration.getKeepBuilds()) {
exclusions.add(BUILDS_NAME);
}
if (! configuration.getKeepArchives()) {
exclusions.add(ARCHIVE_NAME);
}
IOFileFilter filter = createFileFilter(exclusions, jobsExclusionFileFilter);
try {
BackupEngine backupEngine = new BackupEngine(logger,
hudsonWorkDir, backupFileName, configuration.getArchiveType().getArchiver(), filter);
backupEngine.doBackup();
} catch (BackupException e) {
e.printStackTrace(logger.getWriter());
} finally {
cancelNoJobs();
endDate = new Date();
logger.info("Backup end at " + getTimestamp(endDate));
BigDecimal delay = new BigDecimal(endDate.getTime()
- startDate.getTime());
delay = delay.setScale(2, BigDecimal.ROUND_HALF_UP);
delay = delay.divide(new BigDecimal("1000"));
logger.info("[" + delay.toPlainString() + "s]");
finished = true;
logger.close();
}
}
public static IOFileFilter createFileFilter(List<String> exclusions, IOFileFilter jobsExclusionFileFilter) {
if(jobsExclusionFileFilter == null) {
return FileFilterUtils.notFileFilter(new NameFileFilter(exclusions.toArray(new String[]{})));
}
else {
return FileFilterUtils.andFileFilter(
FileFilterUtils.notFileFilter(new NameFileFilter(exclusions.toArray(new String[]{}))),
jobsExclusionFileFilter);
}
}
/**
* Returns a file filter filtering files/dirs to NOT include from jobs' workspace
* (this means the returned file filter is already a negation).
*/
public static IOFileFilter createJobsExclusionFileFilter(
String hudsonWorkDir,
String jobIncludes,
String jobExcludes,
boolean caseSensitive) {
// directory scanning will be done from the HUDSON_HOME/jobs directory
DirectoryScanner directoryScanner = new DirectoryScanner();
directoryScanner.setBasedir(new File(hudsonWorkDir, JOBS_NAME));
directoryScanner.setCaseSensitive(caseSensitive);
// for each specified inclusion, we need to prefix it with "*/workspace"
// to match the workspace directory of each job
if(jobIncludes != null && jobIncludes.length() > 0) {
// jobIncludes looks like file1,fil2,dir1/**,dir2/*
// we need to prefix the first element in the list with "*/workspace"
jobIncludes = jobIncludes.replaceAll(Matcher.quoteReplacement("\\"), "/");
jobIncludes = "*/" + WORKSPACE_NAME + '/' + jobIncludes;
// we do the same for all other elements in the list
jobIncludes = jobIncludes.replaceAll(",", ",*/" + WORKSPACE_NAME + '/');
directoryScanner.setIncludes(jobIncludes.split(","));
}
else {
directoryScanner.setIncludes(null);
}
// the same is done for exclusions
if(jobExcludes != null && jobExcludes.length() > 0) {
jobExcludes = jobExcludes.replaceAll(Matcher.quoteReplacement("\\"), "/");
jobExcludes = "*/" + WORKSPACE_NAME + '/' + jobExcludes;
jobExcludes = jobExcludes.replaceAll(",", ",*/" + WORKSPACE_NAME + '/');
directoryScanner.setExcludes(jobExcludes.split(","));
}
else {
directoryScanner.setExcludes(null);
}
directoryScanner.scan();
FileFilter ff = new SimpleFileFilter(
directoryScanner.getBasedir(),
directoryScanner.getExcludedDirectories(),
directoryScanner.getExcludedFiles());
return FileFilterUtils.notFileFilter(new DelegateFileFilter(ff));
}
private void waitNoJobsInQueue() {
logger.info("Setting hudson in shutdown mode to avoid files corruptions.");
try {
Hudson.getInstance().doQuietDown();
} catch(NoSuchMethodError e) {
// nothing to do, this "hack" is there for the plugin to work with
// Hudson 1.312
} catch (Exception e) {
logger.error("Erreur putting hudson in shutdown mode.");
e.printStackTrace(logger.getWriter());
}
logger.info("Waiting all jobs end...");
Computer computer[] = Hudson.getInstance().getComputers();
int running = 1;
// TODO BACKUP-PLUGIN add timeout here
while (running > 0) {
running = 0;
for (int i = 0; i < computer.length; i++) {
running += computer[i].countBusy();
}
logger.info("Number of running jobs detected : " + running);
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
}
}
logger.info("All jobs finished.");
}
private void cancelNoJobs() {
logger.info("Cancel hudson shutdown mode");
try {
Hudson.getInstance().doCancelQuietDown();
} catch(NoSuchMethodError e) {
// nothing to do, this "hack" is there for the plugin to work with
// Hudson 1.312
} catch (Exception e) {
logger.error("Erreur cancelling hudson shutdown mode.");
e.printStackTrace(logger.getWriter());
}
}
}