/**
* 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 static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.quartz.JobBuilder.newJob;
import org.candlepin.auth.Access;
import org.candlepin.auth.ConsumerPrincipal;
import org.candlepin.auth.Principal;
import org.candlepin.common.exceptions.NotFoundException;
import org.candlepin.pinsetter.core.PinsetterJobListener;
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.tasks.HealEntireOrgJob;
import org.candlepin.pinsetter.tasks.RefreshPoolsJob;
import org.candlepin.test.DatabaseTestFixture;
import org.candlepin.test.TestUtil;
import org.candlepin.util.Util;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Test;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import javax.inject.Inject;
/**
* JobCuratorTest
*/
public class JobCuratorTest extends DatabaseTestFixture {
@Inject private JobCurator curator;
/**
*All the job status objects which have executed successfully and
*are clear for deletion should be swept away from the db.
*/
@Test
public void completedAndSelectedByDateCriteriaShouldBeDeleted() {
newJobStatus().startTime(new Date())
.finishTime(Util.tomorrow()).create();
this.curator.cleanUpOldCompletedJobs(Util.addDaysToDt(2));
assertEquals(0, this.curator.listAll().list().size());
}
/**
* Jobs which have not completed execution should stay in db
*/
@Test
public void notCompletedButSelectedByDateCriteriaShouldNotBeDeleted() {
newJobStatus().finishTime(Util.yesterday()).create();
this.curator.cleanUpOldCompletedJobs(Util.tomorrow());
assertEquals(1, this.curator.listAll().list().size());
}
/**
* Jobs which are completed but don't pass the selection criteria
* should stay in the db.
*/
@Test
public void completedButNotSelectedByDateCriteriaShouldNotBeDeleted() {
newJobStatus().startTime(Util.yesterday()).finishTime(new Date())
.create();
this.curator.cleanUpOldCompletedJobs(Util.yesterday());
assertEquals(1, this.curator.listAll().list().size());
}
/**
* Jobs which neither completed nor pass selection criteria
* should stay in db.
*/
@Test
public void notCompletedAndNotSelectedByDateCriteriaShouldNotBeDeleted() {
newJobStatus().startTime(Util.yesterday()).create();
this.curator.cleanUpOldCompletedJobs(Util.tomorrow());
assertEquals(1, this.curator.listAll().list().size());
}
@Test
public void failedJobs() {
newJobStatus().startTime(Util.yesterday()).finishTime(null)
.result("wrong pool").state(JobState.FAILED).create();
this.curator.cleanupAllOldJobs(new Date());
assertEquals(0, this.curator.listAll().list().size());
}
@Test
public void deleteJobNoStatusReturn() {
newJobStatus().result("Taylor Swift").state(JobState.CANCELED).create();
List<JobStatus> statuses = this.curator.listAll().list();
assertEquals(1, statuses.size());
this.curator.deleteJobNoStatusReturn(statuses.get(0).getId());
assertEquals(0, this.curator.listAll().list().size());
}
@Test
public void findByPrincipalName() {
JobStatus job = newJobStatus().principalName("donald").owner("ducks").create();
List<JobStatus> jobs = this.curator.findByPrincipalName("donald").list();
assertNotNull(jobs);
assertEquals("donald", job.getPrincipalName());
assertEquals(job, jobs.get(0));
}
@Test
public void correlationIdOnStatus() {
JobStatus job = newJobStatus().principalName("donald").owner("ducks").create();
assertEquals("test-csid", job.getCorrelationId());
}
@Test(expected = NotFoundException.class)
public void cancelNonExistentJob() {
curator.cancel("dont_exist");
}
@Test
public void cancel() {
String jobid = newJobStatus().owner("ducks")
.startTime(Util.yesterday()).create().getId();
JobStatus job = curator.cancel(jobid);
assertNotNull(job);
assertEquals(jobid, job.getId());
assertEquals(JobStatus.JobState.CANCELED, job.getState());
}
@Test
public void updateWithLargeResult() {
String longstr = RandomStringUtils.randomAlphanumeric(300);
JobExecutionContext ctx = mock(JobExecutionContext.class);
when(ctx.getFireTime()).thenReturn(new Date());
when(ctx.getJobRunTime()).thenReturn(1000L);
when(ctx.getResult()).thenReturn(longstr);
JobStatus status = newJobStatus().owner("terps").create();
status.update(ctx);
curator.merge(status);
}
@Test
public void findWaitingJobsTest() {
JobStatus waitingJob1 = newJobStatus().state(JobStatus.JobState.WAITING)
.startTime(Util.yesterday()).create();
JobStatus waitingJob2 = newJobStatus().state(JobStatus.JobState.WAITING)
.startTime(Util.yesterday()).create();
JobStatus createdJob = newJobStatus().state(JobStatus.JobState.CREATED)
.startTime(Util.yesterday()).create();
JobStatus finishedJob = newJobStatus().state(JobStatus.JobState.FINISHED)
.startTime(Util.yesterday()).create();
List<JobStatus> waitingList = curator.findWaitingJobs().list();
assertTrue(waitingList.contains(waitingJob1));
assertTrue(waitingList.contains(waitingJob2));
assertFalse(waitingList.contains(createdJob));
assertFalse(waitingList.contains(finishedJob));
}
@Test
public void findNumRunningByOwnerAndClass() {
newJobStatus().state(JobStatus.JobState.WAITING)
.owner("some_owner").create();
newJobStatus().state(JobStatus.JobState.WAITING)
.owner("my_owner").create();
newJobStatus().state(JobStatus.JobState.RUNNING)
.owner("my_owner").create();
newJobStatus().state(JobStatus.JobState.CREATED)
.owner("some_owner").create();
newJobStatus().state(JobStatus.JobState.FINISHED)
.owner("my_owner").create();
newJobStatus().state(JobStatus.JobState.RUNNING)
.owner("some_owner").create();
newJobStatus().state(JobStatus.JobState.RUNNING)
.owner("other_owner").create();
newJobStatus().state(JobStatus.JobState.RUNNING)
.jobClass(RefreshPoolsJob.class)
.owner("my_owner").create();
newJobStatus().state(JobStatus.JobState.RUNNING)
.jobClass(HealEntireOrgJob.class)
.owner("my_owner").create();
long result = curator.findNumRunningByClassAndTarget("my_owner", RefreshPoolsJob.class);
assertEquals(1, result);
}
@Test
public void getLatestByClassAndOwner() {
long offset = System.currentTimeMillis() - 7000;
newJobStatus(new Date(offset + 1000)).state(JobStatus.JobState.WAITING)
.owner("my_owner")
.jobClass(HealEntireOrgJob.class)
.create();
newJobStatus(new Date(offset + 2000)).state(JobStatus.JobState.RUNNING)
.owner("my_owner")
.jobClass(HealEntireOrgJob.class)
.create();
newJobStatus(new Date(offset + 3000)).state(JobStatus.JobState.RUNNING)
.owner("my_owner")
.jobClass(HealEntireOrgJob.class)
.create();
JobStatus expected = newJobStatus(new Date(offset + 4000)).state(JobStatus.JobState.CREATED)
.jobClass(HealEntireOrgJob.class)
.owner("my_owner")
.create();
// Would be chosen if the job class was correct
newJobStatus(new Date(offset + 5000)).state(JobStatus.JobState.WAITING)
.owner("my_owner")
.jobClass(RefreshPoolsJob.class)
.create();
// Would be chosen if the owner was correct
newJobStatus(new Date(offset + 6000)).state(JobStatus.JobState.WAITING)
.owner("some_owner")
.jobClass(HealEntireOrgJob.class)
.create();
// Would be chosen if the jobstate wasn't done
newJobStatus(new Date(offset + 7000)).state(JobStatus.JobState.FINISHED)
.jobClass(HealEntireOrgJob.class)
.owner("my_owner")
.create();
JobStatus result = curator.getByClassAndTarget("my_owner", HealEntireOrgJob.class);
assertEquals(expected, result);
}
@Test
public void cancelOrphanedJobs() throws InterruptedException {
JobStatus status1 = newJobStatus().state(JobStatus.JobState.WAITING).id("1").create();
JobStatus status2 = newJobStatus().state(JobStatus.JobState.WAITING).id("2").create();
JobStatus status3 = newJobStatus().state(JobStatus.JobState.RUNNING).id("3").create();
JobStatus status4 = newJobStatus().state(JobStatus.JobState.CREATED).id("4").create();
JobStatus status5 = newJobStatus().state(JobStatus.JobState.RUNNING).id("5").create();
List<String> activeIds = new LinkedList<String>();
activeIds.add(status1.getId());
activeIds.add(status3.getId());
activeIds.add(status4.getId());
int updated = curator.cancelOrphanedJobs(activeIds, 0L);
assertEquals(2, updated);
curator.refresh(status1);
curator.refresh(status2);
curator.refresh(status3);
curator.refresh(status4);
curator.refresh(status5);
assertEquals(JobStatus.JobState.WAITING, status1.getState());
assertEquals(JobStatus.JobState.CANCELED, status2.getState());
assertEquals(JobStatus.JobState.RUNNING, status3.getState());
assertEquals(JobStatus.JobState.CREATED, status4.getState());
assertEquals(JobStatus.JobState.CANCELED, status5.getState());
}
@Test
public void findByPrincipalNameRestrictsUserToAccessableOrgs() {
JobStatus job = newJobStatus().principalName("donald").owner("ducks").create();
newJobStatus().principalName("donald").owner("marley").create();
setupPrincipal("goofy", new Owner("ducks"), Access.READ_ONLY);
List<JobStatus> jobs = this.curator.findByPrincipalName("donald").list();
assertNotNull(jobs);
assertEquals(1, jobs.size());
assertEquals("donald", job.getPrincipalName());
assertEquals(job, jobs.get(0));
}
@Test
public void findByPrincipalNameRestrictsConsumerToOwnJobs() {
Owner owner = new Owner("ducks");
Consumer consumer = TestUtil.createConsumer(owner);
JobStatus job = newJobStatus().principalName(consumer.getUuid()).owner(owner.getKey()).create();
newJobStatus().principalName("donald").owner(owner.getKey()).create();
setupPrincipal(new ConsumerPrincipal(consumer));
assertTrue(this.curator.findByPrincipalName("donald").list().isEmpty());
List<JobStatus> jobs = this.curator.findByPrincipalName(consumer.getUuid()).list();
assertNotNull(jobs);
assertEquals(1, jobs.size());
assertEquals(consumer.getUuid(), job.getPrincipalName());
assertEquals(job, jobs.get(0));
}
@Test
public void findByOwnerReturnsAllWhenRequestedBySuperAdmin() {
newJobStatus().principalName("p1").owner("owner1").create();
newJobStatus().principalName("p2").owner("owner1").create();
newJobStatus().principalName("p3").owner("owner2").create();
setupAdminPrincipal("bob");
List<JobStatus> jobs = this.curator.findByOwnerKey("owner1").list();
assertNotNull(jobs);
assertEquals(2, jobs.size());
for (JobStatus job : jobs) {
assertEquals("owner1", job.getOwnerId());
}
assertEquals(1, this.curator.findByOwnerKey("owner2").list().size());
}
@Test
public void findByUserFiltersByOwnerAccessWhenRequestedByBasicUser() {
newJobStatus().principalName("p1").owner("owner1").create();
newJobStatus().principalName("p2").owner("owner1").create();
newJobStatus().principalName("p3").owner("owner2").create();
setupPrincipal("goofy", new Owner("owner1"), Access.READ_ONLY);
List<JobStatus> jobs = this.curator.findByOwnerKey("owner1").list();
assertNotNull(jobs);
assertEquals(2, jobs.size());
for (JobStatus job : jobs) {
assertEquals("owner1", job.getOwnerId());
}
assertTrue(this.curator.findByOwnerKey("owner2").list().isEmpty());
}
@Test
public void findByUserFiltersByOwnerAccessAndUUIDWhenRequestedByConsumer() {
Owner owner = new Owner("testowner");
Consumer consumer = TestUtil.createConsumer(owner);
JobStatus job = newJobStatus().principalName(consumer.getUuid()).owner(owner.getKey()).create();
newJobStatus().principalName("p1").owner("owner1").create();
newJobStatus().principalName("p2").owner("owner1").create();
newJobStatus().principalName("p3").owner("owner2").create();
setupPrincipal(new ConsumerPrincipal(consumer));
assertTrue(this.curator.findByOwnerKey("owner1").list().isEmpty());
assertTrue(this.curator.findByOwnerKey("owner2").list().isEmpty());
List<JobStatus> found = this.curator.findByOwnerKey("testowner").list();
assertEquals(1, found.size());
assertEquals(job, found.get(0));
}
@Test
public void findByConsumerUuidRestrictsByOwnerWhenRequestedByBasicUser() {
newJobStatus().principalName("p1").consumer("c1", "owner1").create();
newJobStatus().principalName("p4").owner("owner2").create();
JobStatus job = newJobStatus().principalName("p3").consumer("c2", "owner1").create();
// Technically this case should not happen since a consumer
// can belong to one org, but adding just to make sure that
// it gets filtered.
newJobStatus().principalName("p2").consumer("c2", "owner2").create();
setupPrincipal("goofy", new Owner("owner1"), Access.READ_ONLY);
List<JobStatus> found = curator.findByConsumerUuid("c2").list();
assertEquals(1, found.size());
assertEquals(job, found.get(0));
}
@Test
public void findByConsuemrUuidRestrictsByConsumerUuidEnforcesOwnerMatchWhenRequestedByConsumer() {
Owner owner = new Owner("testowner");
Consumer consumer = TestUtil.createConsumer(owner);
JobStatus job = newJobStatus().principalName(consumer.getUuid())
.consumer(consumer.getUuid(), consumer.getOwner().getKey()).create();
// Technically this case should not happen since a consumer
// can belong to one org, but adding just to make sure that
// it gets filtered.
newJobStatus().principalName(consumer.getUuid()).consumer(consumer.getUuid(), "owner1").create();
newJobStatus().principalName("p2").consumer("c2", "owner1").create();
newJobStatus().principalName("p3").owner("owner2").create();
setupPrincipal(new ConsumerPrincipal(consumer));
List<JobStatus> jobs = curator.findByConsumerUuid(consumer.getUuid()).list();
assertEquals(1, jobs.size());
assertEquals(job, jobs.get(0));
}
private JobStatusBuilder newJobStatus() {
return new JobStatusBuilder();
}
private JobStatusBuilder newJobStatus(Date creationDate) {
return new JobStatusBuilder(creationDate);
}
private class JobStatusBuilder{
private String id;
private Date creationDate;
private Date startDt;
private Date endDt;
private String result;
private JobState state;
private String targetValue;
private String principalName;
private String contextOwner;
private JobStatus.TargetType targetType;
private JobDataMap map;
private Class<? extends Job> jobClass = Job.class;
public JobStatusBuilder(Date creationDate) {
this.creationDate = creationDate;
id("id" + Math.random());
map = new JobDataMap();
contextOwner = "an-owner-key";
targetValue = contextOwner;
targetType = JobStatus.TargetType.OWNER;
}
public JobStatusBuilder() {
this(null);
}
public JobStatusBuilder id(String id) {
this.id = id;
return this;
}
public JobStatusBuilder startTime(Date dt) {
this.startDt = dt;
return this;
}
public JobStatusBuilder finishTime(Date dt) {
this.endDt = dt;
return this;
}
public JobStatusBuilder result(String result) {
this.result = result;
return this;
}
public JobStatusBuilder state(JobState state) {
this.state = state;
return this;
}
public JobStatusBuilder owner(String key) {
this.contextOwner = key;
this.targetValue = key;
this.targetType = JobStatus.TargetType.OWNER;
return this;
}
public JobStatusBuilder consumer(String consumerUuid, String consumerOwnerKey) {
this.contextOwner = consumerOwnerKey;
this.targetValue = consumerUuid;
this.targetType = JobStatus.TargetType.CONSUMER;
return this;
}
public JobStatusBuilder principalName(String name) {
this.principalName = name;
return this;
}
public JobStatusBuilder jobClass(Class<? extends Job> clazz) {
this.jobClass = clazz;
return this;
}
@SuppressWarnings("serial")
public JobStatus create() {
//sigh - all of this pain to construct a JobDetail
//which does not have setters!
Principal p = mock(Principal.class);
when(p.getPrincipalName()).thenReturn(principalName);
map.put(PinsetterJobListener.PRINCIPAL_KEY, p);
map.put(JobStatus.TARGET_TYPE, targetType);
map.put(JobStatus.TARGET_ID, targetValue);
map.put(JobStatus.OWNER_ID, contextOwner);
map.put(JobStatus.CORRELATION_ID, "test-csid");
JobStatus status = new JobStatus(
newJob(jobClass).withIdentity(id, PinsetterKernel.SINGLE_JOB_GROUP)
.usingJobData(map).build());
JobExecutionContext context = mock(JobExecutionContext.class);
when(context.getFireTime()).thenReturn(startDt);
long time = -1;
if (endDt != null && startDt != null) {
time = endDt.getTime() - startDt.getTime();
}
when(context.getJobRunTime()).thenReturn(time);
when(context.getResult()).thenReturn(result);
status.update(context);
if (state != null) {
status.setState(state);
}
status.setCreated(this.creationDate);
return curator.create(status);
}
}
}