/** * Copyright (c) 2009 - 2012 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package org.candlepin.model; import org.candlepin.common.config.Configuration; import org.candlepin.common.exceptions.NotFoundException; import org.candlepin.config.ConfigProperties; import org.candlepin.pinsetter.core.PinsetterKernel; import org.candlepin.pinsetter.core.model.JobStatus; import org.candlepin.pinsetter.core.model.JobStatus.JobState; import org.candlepin.pinsetter.core.model.JobStatus.TargetType; import org.candlepin.pinsetter.tasks.KingpinJob; import com.google.inject.Inject; import com.google.inject.persist.Transactional; import org.hibernate.Query; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Set; /** * */ public class JobCurator extends AbstractHibernateCurator<JobStatus> { private Configuration config; @Inject public JobCurator(Configuration config) { super(JobStatus.class); this.config = config; } @Transactional public JobStatus cancel(String jobId) { this.cancelNoReturn(jobId); JobStatus result = this.find(jobId); if (result != null) { this.refresh(result); } return result; } @Transactional public void cancelNoReturn(String jobId) { String hql = "update JobStatus j " + "set j.state = :canceled " + "where j.id = :jobid"; Query query = this.currentSession().createQuery(hql) .setParameter("jobid", jobId) .setInteger("canceled", JobState.CANCELED.ordinal()); int updated = query.executeUpdate(); if (updated == 0) { throw new NotFoundException("job not found"); } } public int deleteJobNoStatusReturn(String jobId) { return this.currentSession().createQuery( "delete from JobStatus where id = :jobid") .setParameter("jobid", jobId) .executeUpdate(); } public int cleanupAllOldJobs(Date deadline) { return this.currentSession().createQuery( "delete from JobStatus where updated <= :date") .setTimestamp("date", deadline) .executeUpdate(); } public int cleanUpOldCompletedJobs(Date deadLineDt) { return this.currentSession().createQuery( "delete from JobStatus where updated <= :date and " + "(state = :completed or state = :canceled)") .setTimestamp("date", deadLineDt) .setInteger("completed", JobState.FINISHED.ordinal()) .setInteger("canceled", JobState.CANCELED.ordinal()) .executeUpdate(); } @SuppressWarnings("unchecked") private CandlepinQuery<JobStatus> findByTarget(TargetType type, String tgtid) { DetachedCriteria criteria = this.createSecureDetachedCriteria() .add(Restrictions.eq("targetId", tgtid)) .add(Restrictions.eq("targetType", type)); return this.cpQueryFactory.<JobStatus>buildQuery(this.currentSession(), criteria); } public CandlepinQuery<JobStatus> findByOwnerKey(String ownerKey) { return this.findByTarget(JobStatus.TargetType.OWNER, ownerKey); } public CandlepinQuery<JobStatus> findByConsumerUuid(String uuid) { return this.findByTarget(JobStatus.TargetType.CONSUMER, uuid); } @SuppressWarnings("unchecked") public CandlepinQuery<JobStatus> findByPrincipalName(String principalName) { DetachedCriteria criteria = this.createSecureDetachedCriteria() .add(Restrictions.eq("principalName", principalName)); return this.cpQueryFactory.<JobStatus>buildQuery(this.currentSession(), criteria); } /** * This implementation allows us to avoid looping through all canceled jobs. * Finds all jobs marked as CANCELED which have an ID in the input list * so we can remove the scheduled job. * * @param activeJobs Names of jobs that are currently active * @return JobStatus list to have quartz job canceled */ @SuppressWarnings("unchecked") public CandlepinQuery<JobStatus> findCanceledJobs(Set<String> activeJobs) { if (activeJobs == null || activeJobs.isEmpty()) { return this.cpQueryFactory.<JobStatus>buildQuery(); } DetachedCriteria criteria = DetachedCriteria.forClass(JobStatus.class) .add(Restrictions.eq("state", JobState.CANCELED)) .add(Restrictions.in("id", activeJobs)); return this.cpQueryFactory.<JobStatus>buildQuery(this.currentSession(), criteria); } @SuppressWarnings("unchecked") public CandlepinQuery<JobStatus> findWaitingJobs() { // Perhaps unique jobClass/target combinations, However we're already in a weird state if // that makes a difference DetachedCriteria criteria = DetachedCriteria.forClass(JobStatus.class) .add(Restrictions.eq("state", JobState.WAITING)); return this.cpQueryFactory.<JobStatus>buildQuery(this.currentSession(), criteria); } public long findNumRunningByClassAndTarget(String target, Class<? extends KingpinJob> jobClass) { return (Long) this.currentSession().createCriteria(JobStatus.class) .add(Restrictions.ge("updated", getBlockingCutoff())) .add(Restrictions.eq("state", JobState.RUNNING)) .add(Restrictions.eq("targetId", target)) .add(Restrictions.eq("jobClass", jobClass)) .setProjection(Projections.count("id")) .uniqueResult(); } public JobStatus getByClassAndTarget(String target, Class<? extends KingpinJob> jobClass) { // FIXME: // This is not guaranteed to find the intended target if more than one job in the DB // matches the input criteria return (JobStatus) this.currentSession().createCriteria(JobStatus.class) .addOrder(Order.desc("created")) .add(Restrictions.ge("updated", getBlockingCutoff())) .add(Restrictions.ne("state", JobState.FINISHED)) .add(Restrictions.ne("state", JobState.FAILED)) .add(Restrictions.ne("state", JobState.CANCELED)) .add(Restrictions.eq("targetId", target)) .add(Restrictions.eq("jobClass", jobClass)) .setMaxResults(1) .uniqueResult(); } /* * Cancel jobs that should have a quartz job (but don't), * and have not been updated within the last 2 minutes. */ public int cancelOrphanedJobs(List<String> activeIds) { return cancelOrphanedJobs(activeIds, 1000L * 60L * 2L); //2 minutes } public int cancelOrphanedJobs(List<String> activeIds, Long millis) { Date before = new Date(new Date().getTime() - millis); String hql = "update JobStatus j " + "set j.state = :canceled " + "where j.jobGroup = :async and " + "j.state != :canceled and " + "j.state != :finished and " + "j.state != :failed and " + "j.updated <= :date"; // Must trim out activeIds if the list is empty, otherwise the // statement will fail. if (!activeIds.isEmpty()) { hql += " and j.id not in (:activeIds)"; } Query query = this.currentSession().createQuery(hql) .setTimestamp("date", before) .setParameter("async", PinsetterKernel.SINGLE_JOB_GROUP) .setInteger("finished", JobState.FINISHED.ordinal()) .setInteger("failed", JobState.FAILED.ordinal()) .setInteger("canceled", JobState.CANCELED.ordinal()); if (!activeIds.isEmpty()) { query.setParameterList("activeIds", activeIds); } return query.executeUpdate(); } private Date getBlockingCutoff() { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.SECOND, -1 * config.getInt( ConfigProperties.PINSETTER_ASYNC_JOB_TIMEOUT)); return calendar.getTime(); } }