All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.duracloud.snapshot.service.impl.SnapshotJobManagerImpl Maven / Gradle / Ivy

The newest version!
/*
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 *     http://duracloud.org/license/
 */
package org.duracloud.snapshot.service.impl;

import java.io.File;
import java.text.MessageFormat;
import java.util.Date;

import org.apache.commons.io.FileUtils;
import org.duracloud.client.ContentStore;
import org.duracloud.common.retry.Retriable;
import org.duracloud.common.retry.Retrier;
import org.duracloud.snapshot.SnapshotConstants;
import org.duracloud.snapshot.SnapshotException;
import org.duracloud.snapshot.SnapshotNotFoundException;
import org.duracloud.snapshot.common.SnapshotServiceConstants;
import org.duracloud.snapshot.db.ContentDirUtils;
import org.duracloud.snapshot.db.model.BaseEntity;
import org.duracloud.snapshot.db.model.DuracloudEndPointConfig;
import org.duracloud.snapshot.db.model.Restoration;
import org.duracloud.snapshot.db.model.Snapshot;
import org.duracloud.snapshot.db.repo.RestoreRepo;
import org.duracloud.snapshot.db.repo.SnapshotRepo;
import org.duracloud.snapshot.dto.RestoreStatus;
import org.duracloud.snapshot.dto.SnapshotStatus;
import org.duracloud.snapshot.dto.task.CompleteCancelSnapshotTaskParameters;
import org.duracloud.snapshot.service.AlreadyInitializedException;
import org.duracloud.snapshot.service.EventLog;
import org.duracloud.snapshot.service.RestorationNotFoundException;
import org.duracloud.snapshot.service.SnapshotJobManager;
import org.duracloud.snapshot.service.SnapshotJobManagerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.launch.JobExecutionNotRunningException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.NoSuchJobExecutionException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.batch.core.step.NoSuchStepException;
import org.springframework.batch.core.step.StepLocator;
import org.springframework.batch.core.step.tasklet.StoppableTasklet;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * The default implementation of the SnapshotJobManager interface.
 * Uses spring-batch componentry.
 *
 * @author Daniel Bernstein Date: Feb 11, 2014
 */
@Component
public class SnapshotJobManagerImpl implements SnapshotJobManager {

    private static final Logger log = LoggerFactory.getLogger(SnapshotJobManagerImpl.class);
    private JobLauncher jobLauncher;
    private JobRepository jobRepository;
    private SnapshotRepo snapshotRepo;
    private RestoreRepo restoreRepo;
    private SnapshotJobManagerConfig config;
    private BatchJobBuilderManager builderManager;
    private StoreClientHelper storeClientHelper;
    private EventLog eventLog;

    @Autowired
    public SnapshotJobManagerImpl(SnapshotRepo snapshotRepo,
                                  RestoreRepo restoreRepo,
                                  JobLauncher jobLauncher,
                                  JobRepository jobRepository,
                                  BatchJobBuilderManager manager,
                                  StoreClientHelper storeClientHelper,
                                  EventLog eventLog) {
        super();
        this.restoreRepo = restoreRepo;
        this.snapshotRepo = snapshotRepo;
        this.builderManager = manager;
        this.jobLauncher = jobLauncher;
        this.jobRepository = jobRepository;
        this.storeClientHelper = storeClientHelper;
        this.eventLog = eventLog;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.duracloud.snapshot.rest.SnapshotJobManager#initialize(org.duracloud
     * .snapshot.rest.InitParams)
     */
    @Override
    public void init(SnapshotJobManagerConfig config) throws AlreadyInitializedException {
        init(config, true);
    }

    protected void init(SnapshotJobManagerConfig config,
                        boolean attemptRestart) throws AlreadyInitializedException {
        if (isInitialized()) {
            throw new AlreadyInitializedException("Already initialized!");
        }

        this.config = config;

        log.info("initialized successfully.");

        if (attemptRestart) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        restartIncompleteJobs();
                    } catch (Exception e) {
                        log.error(
                            "failed to restart all incomplete jobs:" + e.getMessage(), e);
                    }
                }
            }).start();
        }
    }

    /**
     *
     */
    private void restartIncompleteJobs() throws Exception {
        log.info("checking for incomplete snapshot jobs.");

        resumeByStatus(SnapshotStatus.TRANSFERRING_FROM_DURACLOUD);
        resumeByStatus(SnapshotStatus.INITIALIZED);

        log.info("checking for incomplete restore jobs.");
        for (Restoration restore : this.restoreRepo.findRunning()) {
            try {
                resumeJob(SnapshotServiceConstants.RESTORE_JOB_NAME, restore);
            } catch (SnapshotException ex) {
                log.error("unable to resume restore " + restore, ex);
            }
        }
    }

    private void resumeByStatus(final SnapshotStatus status) {
        for (Snapshot snapshot : this.snapshotRepo.findByStatusOrderBySnapshotDateAsc(status)) {
            try {
                resumeJob(SnapshotServiceConstants.SNAPSHOT_JOB_NAME, snapshot);
            } catch (SnapshotException ex) {
                log.error("unable to resume snapshot " + snapshot, ex);
            }
        }
    }

    private void resumeJob(String jobName, Object entity) throws SnapshotException {

        BatchJobBuilder builder = this.builderManager.getBuilder(entity);
        Job job = builder.buildJob(entity, config);
        JobParameters params = builder.buildIdentifyingJobParameters(entity);
        JobExecution jobExecution = this.jobRepository.getLastJobExecution(jobName, params);

        if (jobExecution != null) {
            if (!jobExecution.getStatus().isRunning()) {
                return;
            } else {
                log.debug("found job execution in running state for {} (job execution = {})", entity, jobExecution);
                jobExecution.setStatus(BatchStatus.STOPPED);
                jobExecution.setExitStatus(ExitStatus.STOPPED);
                jobExecution.setEndTime(new Date());
                jobRepository.update(jobExecution);
                log.info("updated job execution in running state to stopped: {} (job execution = {})",
                         entity,
                         jobExecution);
            }
        }

        try {
            JobExecution execution = jobLauncher.run(job, params);
            log.info("restarted job execution = {} for {}:  newly executed job execution id = {}",
                     execution,
                     entity,
                     execution.getId());
        } catch (JobExecutionAlreadyRunningException | JobInstanceAlreadyCompleteException | JobRestartException
            | JobParametersInvalidException e) {
            log.error(MessageFormat.format("failed to resume stopped job: jobExecution={0}, entity={1}: {2}",
                                           jobExecution,
                                           entity,
                                           e.getMessage()),
                      e);
        }

    }

    /* (non-Javadoc)
     * @see org.duracloud.snapshot.service.SnapshotJobManager#isInitialized()
     */
    @Override
    public boolean isInitialized() {
        return this.config != null;
    }

    private Snapshot getSnapshot(String snapshotId) throws SnapshotNotFoundException {
        Snapshot snapshot = this.snapshotRepo.findByName(snapshotId);
        if (snapshot == null) {
            throw new SnapshotNotFoundException(snapshotId);
        }

        return snapshot;
    }

    @SuppressWarnings("unchecked")
    private BatchStatus executeJob(Object entity) throws SnapshotException {

        log.debug("executing job for {}", entity);
        try {
            @SuppressWarnings("rawtypes")
            BatchJobBuilder builder = this.builderManager.getBuilder(entity);
            Job job = builder.buildJob(entity, config);
            JobParameters params = builder.buildJobParameters(entity);
            JobExecution execution = jobLauncher.run(job, params);
            BatchStatus status = execution.getStatus();
            log.info("executed  {} using parameters {}: jobexecution={}, execution status={}",
                     job,
                     params,
                     execution,
                     status);
            return status;
        } catch (Exception e) {
            String message = "Error running job based on " + entity + ": " + e.getMessage();
            log.error(message, e);
            throw new SnapshotException(e.getMessage(), e);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.duracloud.snapshot.manager.SnapshotJobManager#executeRestoration(java
     * .lang.String)
     */
    @Override
    public BatchStatus executeRestoration(String restorationId) throws SnapshotException {
        return executeJob(getRestoration(restorationId));
    }

    /**
     * @param restorationId
     * @return
     */
    private Restoration getRestoration(String restorationId) throws RestorationNotFoundException {
        Restoration restoration = this.restoreRepo.findByRestorationId(restorationId);
        if (restoration == null) {
            throw new RestorationNotFoundException(restorationId);
        }
        return restoration;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.duracloud.snapshot.manager.SnapshotJobManager#executeSnapshot(java.
     * lang.String)
     */
    @Override
    public BatchStatus executeSnapshot(String snapshotId) throws SnapshotException {
        checkInitialized();
        return executeJob(getSnapshot(snapshotId));
    }

    private boolean stop(JobExecution jobExecution, Job job)
        throws NoSuchJobExecutionException, JobExecutionNotRunningException {

        BatchStatus status = jobExecution.getStatus();
        if (!(status == BatchStatus.STARTED || status == BatchStatus.STARTING)) {
            throw new JobExecutionNotRunningException(
                "JobExecution must be running so that it can be stopped: " + jobExecution);
        }
        jobExecution.setStatus(BatchStatus.STOPPING);
        jobRepository.update(jobExecution);
        //get the current stepExecution
        for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
            if (stepExecution.getStatus().isRunning()) {
                try {
                    //have the step execution that's running -> need to 'stop' it
                    Step step = ((StepLocator) job).getStep(stepExecution.getStepName());
                    if (step instanceof TaskletStep) {
                        Tasklet tasklet = ((TaskletStep) step).getTasklet();
                        if (tasklet instanceof StoppableTasklet) {
                            StepSynchronizationManager.register(stepExecution);
                            ((StoppableTasklet) tasklet).stop();
                            StepSynchronizationManager.release();
                        }
                    }
                } catch (NoSuchStepException e) {
                    log.warn("Step not found", e);
                }
            }
        }
        return true;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.duracloud.snapshot.service.SnapshotJobManager#cancelSnapshot(java.
     * lang.String)
     */
    @Override
    @Transactional
    public void cancelSnapshot(String snapshotId) throws SnapshotException {
        checkInitialized();
        final Snapshot snapshot = getSnapshot(snapshotId);
        stop(snapshot);
        String snapshotDir = ContentDirUtils.getDestinationPath(snapshotId, this.config.getContentRootDir());
        deleteDirectory(snapshotDir);

        new Thread(new Runnable() {
            @Override
            public void run() {
                final DuracloudEndPointConfig source = snapshot.getSource();
                final String spaceId = source.getSpaceId();
                final ContentStore contentStore =
                    storeClientHelper.create(source,
                                             config.getDuracloudUsername(),
                                             config.getDuracloudPassword());
                try {
                    String result = new Retrier().execute(new Retriable() {
                        /* (non-Javadoc)
                         * @see org.duracloud.common.retry.Retriable#retry()
                         */
                        @Override
                        public Object retry() throws Exception {
                            CompleteCancelSnapshotTaskParameters params =
                                new CompleteCancelSnapshotTaskParameters();
                            params.setSpaceId(spaceId);
                            return contentStore.performTask(SnapshotConstants.COMPLETE_SNAPSHOT_CANCEL_TASK_NAME,
                                                            params.serialize());
                        }
                    });

                    log.info("snapshot cancellation is complete: {}", result);

                    snapshot.setStatus(SnapshotStatus.CANCELLED);
                    snapshot.setStatusText("");
                    eventLog.logSnapshotUpdate(snapshot);
                } catch (Exception ex) {
                    log.error("failed to complete cancellation on the durastore side for space {}:  {}",
                              spaceId,
                              ex.getMessage(),
                              ex);
                }

            }
        }).start();
    }

    @Transactional
    @Override
    public Restoration stopRestore(String restoreId) throws SnapshotException {
        return stopRestoreInternal(restoreId);
    }

    @Transactional
    public void cancelRestore(String restoreId) throws SnapshotException {
        checkInitialized();
        final Restoration restore = stopRestoreInternal(restoreId);
        String restoreDir = ContentDirUtils.getSourcePath(restoreId, this.config.getContentRootDir());
        deleteDirectory(restoreDir);
        final DuracloudEndPointConfig destination = restore.getDestination();

        new Thread(new Runnable() {
            @Override
            public void run() {
                final String spaceId = destination.getSpaceId();
                @SuppressWarnings("unused") final ContentStore contentStore =
                    storeClientHelper.create(destination,
                                             config.getDuracloudUsername(),
                                             config.getDuracloudPassword());
                try {
                    String result = new Retrier().execute(new Retriable() {
                        @Override
                        public Object retry() throws Exception {
                            contentStore.deleteSpace(spaceId);
                            return null;
                        }
                    });

                    log.info("restore cancellation is complete: {}", result);

                    restore.setStatus(RestoreStatus.CANCELLED);
                    restore.setStatusText("");
                    eventLog.logRestoreUpdate(restore);
                } catch (Exception ex) {
                    log.error("failed to delete restore space {} as part of cleanup:  {}",
                              spaceId,
                              ex.getMessage(),
                              ex);
                }

            }
        }).start();
    }

    /**
     * @param restoreId
     * @return
     * @throws RestorationNotFoundException
     */
    private Restoration stopRestoreInternal(String restoreId) throws SnapshotException {
        final Restoration restore = getRestoration(restoreId);
        stop(restore);
        return restore;
    }

    /**
     * @param entity
     * @throws SnapshotException
     */
    private void stop(final BaseEntity entity) throws SnapshotException {
        JobExecution execution = getJobExecution(entity);
        if (execution == null) {
            log.info("no job executions associated with {}", entity);
            return;
        }
        Job job = this.builderManager.getBuilder(entity)
                                     .buildJob(entity, this.config);
        try {
            stop(execution, job);
        } catch (NoSuchJobExecutionException e1) {
            log.warn("job execution not found: " + e1.getMessage());
        } catch (JobExecutionNotRunningException e1) {
            log.warn("job execution not running: " + e1.getMessage());
        }
    }

    /**
     * @param path
     */
    private void deleteDirectory(String path) {
        log.info("deleting restore dir: {}", path);
        File dir = new File(path);
        if (!dir.exists()) {
            log.info("nothing to delete: {} does not exist.", path);
        } else {
            boolean success = FileUtils.deleteQuietly(dir);
            log.info("deleted dir {}: success={}", path, success);
        }
    }

    private void checkInitialized() throws SnapshotException {
        if (!isInitialized()) {
            throw new SnapshotException("The application must be initialized " + "before it can be invoked!", null);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.duracloud.snapshot.rest.SnapshotJobManager#getJobExecution(java.lang
     * .String)
     */
    @Override
    public BatchStatus getStatus(String snapshotId) throws SnapshotNotFoundException, SnapshotException {

        checkInitialized();
        JobExecution ex = getJobExecution(this.snapshotRepo.findByName(snapshotId));
        if (ex == null) {
            return BatchStatus.UNKNOWN;
        } else {
            return ex.getStatus();
        }
    }

    private JobExecution getJobExecution(BaseEntity entity) {
        BatchJobBuilder builder = this.builderManager.getBuilder(entity);
        JobParameters params = builder.buildIdentifyingJobParameters(entity);
        String jobName = builder.getJobName();
        JobExecution ex = this.jobRepository.getLastJobExecution(jobName, params);
        return ex;
    }

    @Override
    public SnapshotJobManagerConfig getConfig() {
        return this.config;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy