/**
* Copyright 2016-2017 Linagora, Université Joseph Fourier, Floralis
*
* The present code is developed in the scope of the joint LINAGORA -
* Université Joseph Fourier - Floralis research program and is designated
* as a "Result" pursuant to the terms and conditions of the LINAGORA
* - Université Joseph Fourier - Floralis research program. Each copyright
* holder of Results enumerated here above fully & independently holds complete
* ownership of the complete Intellectual Property rights applicable to the whole
* of said Results, and may freely exploit it in any manner which does not infringe
* the moral rights of the other copyright holders.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.roboconf.dm.scheduler.internal;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import net.roboconf.core.internal.tests.TestApplication;
import net.roboconf.core.model.beans.Application;
import net.roboconf.core.model.runtime.ScheduledJob;
import net.roboconf.core.utils.Utils;
import net.roboconf.dm.internal.test.TestManagerWrapper;
import net.roboconf.dm.management.ManagedApplication;
import net.roboconf.dm.management.Manager;
import net.roboconf.dm.management.events.IDmListener;
/**
* @author Vincent Zurczak - Linagora
*/
public class RoboconfSchedulerTest {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
private RoboconfScheduler scheduler;
private Manager manager;
@Before
public void prepare() throws Exception {
// Prepare the manager
this.manager = Mockito.spy( new Manager());
this.manager.configurationMngr().setWorkingDirectory( this.folder.newFolder());
this.scheduler = new RoboconfScheduler();
this.scheduler.manager = this.manager;
// Configure applications and commands
TestManagerWrapper wrapper = new TestManagerWrapper( this.manager );
Application app = new TestApplication().name( "app" );
app.setDirectory( this.folder.newFolder());
wrapper.addManagedApplication( new ManagedApplication( app ));
// Register commands
this.manager.commandsMngr().createOrUpdateCommand( app, "cmd", "" );
// Ignore all the manager's invocations until now
Mockito.reset( this.manager );
}
@After
public void clean() throws Exception {
this.scheduler.stop();
}
@Test
public void testStartAndStop() throws Exception {
Assert.assertNull( this.scheduler.dmListener );
Assert.assertNull( this.scheduler.scheduler );
Mockito.verifyZeroInteractions( this.manager );
this.scheduler.start();
Assert.assertNotNull( this.scheduler.dmListener );
Assert.assertNotNull( this.scheduler.scheduler );
Assert.assertTrue( this.scheduler.scheduler.isStarted());
Mockito.verify( this.manager, Mockito.times( 1 )).listenerAppears( this.scheduler.dmListener );
Mockito.verify( this.manager, Mockito.atLeast( 1 )).configurationMngr();
String dmPath = this.manager.configurationMngr().getWorkingDirectory().getAbsolutePath();
String schedulerPath = this.scheduler.getSchedulerDirectory().getAbsolutePath();
Assert.assertTrue( schedulerPath.startsWith( dmPath ));
this.scheduler.stop();
Mockito.verify( this.manager, Mockito.times( 1 )).listenerAppears( Mockito.any( IDmListener.class ));
Mockito.verify( this.manager, Mockito.times( 1 )).listenerDisappears( Mockito.any( IDmListener.class ));
Assert.assertNull( this.scheduler.dmListener );
Assert.assertNull( this.scheduler.scheduler );
}
@Test
public void testStop_noManager() throws Exception {
this.scheduler.manager = null;
this.scheduler.stop();
Assert.assertNull( this.scheduler.dmListener );
Assert.assertNull( this.scheduler.scheduler );
}
@Test
public void testSaveAndLoadJobs() throws Exception {
// Start the scheduler and halts triggers
this.scheduler.start();
this.scheduler.scheduler.standby();
File schedulerDirectory = this.scheduler.getSchedulerDirectory();
Set<JobKey> jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Create several jobs
final int max = 5;
String[] jobIds = new String[ max ];
for( int i=0; i<max; i++ ) {
jobIds[ i ] = this.scheduler.saveJob( null, "job " + i, "cmd", "0 0 0 ? 1 *", "app" );
}
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( max, jobKeys.size());
Assert.assertEquals( max, Utils.listAllFiles( schedulerDirectory ).size());
// Delete a job
this.scheduler.deleteJob( jobIds[ 3 ]);
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( max - 1, jobKeys.size());
Assert.assertEquals( max - 1, Utils.listAllFiles( schedulerDirectory ).size());
// Find job properties
ScheduledJob job = this.scheduler.findJobProperties( jobIds[ 1 ]);
Assert.assertNotNull( job );
Assert.assertEquals( "app", job.getAppName());
Assert.assertEquals( "cmd", job.getCmdName());
Assert.assertEquals( "0 0 0 ? 1 *", job.getCron());
Assert.assertEquals( "job 1", job.getJobName());
// Find the properties of a non-existing job
job = this.scheduler.findJobProperties( "job M" );
Assert.assertNull( job );
}
@Test
public void testSaveAndLoadJobs_invalidCron() throws Exception {
// Start the scheduler and halts triggers
this.scheduler.start();
this.scheduler.scheduler.standby();
File schedulerDirectory = this.scheduler.getSchedulerDirectory();
Assert.assertEquals( 0, this.scheduler.listJobs().size());
Set<JobKey> jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Create an invalid job
try {
this.scheduler.saveJob( null, "job", "cmd", "0 0 0 ? invalid *", "app" );
Assert.fail( "An I/O exception was expected." );
} catch( IOException e ) {
// nothing
}
// There should not be any job: in the scheduler...
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// ... and listed by Roboconf
Assert.assertEquals( 0, this.scheduler.listJobs().size());
}
@Test
public void testDeleteJob_inexisting() throws Exception {
// Start the scheduler and halts triggers
this.scheduler.start();
this.scheduler.scheduler.standby();
File schedulerDirectory = this.scheduler.getSchedulerDirectory();
Set<JobKey> jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Delete a job
this.scheduler.deleteJob( "job 3" );
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
}
@Test( expected = IllegalArgumentException.class )
public void testSaveJob_invalidApplicationName() throws Exception {
// Start the scheduler and halts triggers
this.scheduler.start();
this.scheduler.scheduler.standby();
// Create a job
this.scheduler.saveJob( null, "my job", "cmd", "0 0 0 ? 1 *", "app that does nto exist" );
}
@Test( expected = IllegalArgumentException.class )
public void testSaveJob_invalidCommandName() throws Exception {
// Start the scheduler and halts triggers
this.scheduler.start();
this.scheduler.scheduler.standby();
// Create a job
try {
Assert.assertNotNull( this.scheduler.saveJob( null, "my job", "cmd", "0 0 0 ? 1 *", "app" ));
} catch( Exception e ) {
Assert.fail( "No failure was expected here, the application was registered in the setup method." );
}
// This one will fail
this.scheduler.saveJob( null, "my job", "cmd that does not exist", "0 0 0 ? 1 *", "app" );
}
@Test
public void testSaveJobAndUpdateJob() throws Exception {
// Start the scheduler and halts triggers
this.scheduler.start();
this.scheduler.scheduler.standby();
File schedulerDirectory = this.scheduler.getSchedulerDirectory();
Set<JobKey> jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Create a job
String jobId = this.scheduler.saveJob( null, "my job", "cmd", "0 0 0 ? 1 *", "app" );
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 1, jobKeys.size());
Assert.assertEquals( 1, Utils.listAllFiles( schedulerDirectory ).size());
// Find job properties
ScheduledJob readJob = this.scheduler.findJobProperties( jobId );
Assert.assertNotNull( readJob );
Assert.assertEquals( "app", readJob.getAppName());
Assert.assertEquals( "cmd", readJob.getCmdName());
Assert.assertEquals( "0 0 0 ? 1 *", readJob.getCron());
Assert.assertEquals( "my job", readJob.getJobName());
// Rename the initial job and the application's name.
// We also need to make sure the application exists...
TestManagerWrapper wrapper = new TestManagerWrapper( this.manager );
Application app = new TestApplication().name( "new app" );
app.setDirectory( this.folder.newFolder());
wrapper.addManagedApplication( new ManagedApplication( app ));
// ... as well as the command.
this.manager.commandsMngr().createOrUpdateCommand( app, "cmd", "" );
// Update the job
jobId = this.scheduler.saveJob( jobId, "my new job", "cmd", "0 0 0 ? 1 *", app.getName());
// Find job properties
readJob = this.scheduler.findJobProperties( jobId );
Assert.assertNotNull( readJob );
Assert.assertEquals( app.getName(), readJob.getAppName());
Assert.assertEquals( "cmd", readJob.getCmdName());
Assert.assertEquals( "0 0 0 ? 1 *", readJob.getCron());
Assert.assertEquals( "my new job", readJob.getJobName());
// Delete the job
this.scheduler.deleteJob( jobId );
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
}
@Test
public void testDeleteJob_noAppNameInJobProperties() throws Exception {
this.scheduler.scheduler = Mockito.mock( Scheduler.class );
Properties props = new Properties();
props.put( RoboconfScheduler.JOB_NAME, "job" );
props.put( RoboconfScheduler.CMD_NAME, "cmd" );
props.put( RoboconfScheduler.CRON, "0 0 0 ? 1 *" );
try {
File f = this.scheduler.getJobFile( "job" );
Assert.assertTrue( f.getParentFile().mkdirs());
Utils.writePropertiesFile( props, f );
} catch( Exception e ) {
Assert.fail( "No exception was expected here." );
}
this.scheduler.deleteJob( "job" );
Mockito.verifyZeroInteractions( this.scheduler.scheduler );
}
@Test
public void testSaveJob_invalidProperties_noCron() throws Exception {
// Start the scheduler and halts triggers
this.scheduler.start();
this.scheduler.scheduler.standby();
File schedulerDirectory = this.scheduler.getSchedulerDirectory();
Set<JobKey> jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Save an invalid job (no cron)
this.scheduler.saveJob( null, "job", "cmd", null, "app" );
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Save an invalid job (no job name)
this.scheduler.saveJob( null, null, "cmd", "0 0 0 ? 1 *", "app" );
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Save an invalid job (no command name)
this.scheduler.saveJob( null, "job", null, "0 0 0 ? 1 *", "app" );
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Save an invalid job (no application name)
this.scheduler.saveJob( null, "job", "cmd", "0 0 0 ? 1 *", null );
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Save an invalid job (nothing at all)
this.scheduler.saveJob( null, null, null, null, null );
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
// Save an invalid job (an ID but nothing else)
this.scheduler.saveJob( "some-id", null, null, null, null );
jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( 0, Utils.listAllFiles( schedulerDirectory ).size());
}
@Test
public void testLoadAll_and_ListJobs() throws Exception {
Utils.createDirectory( this.scheduler.getSchedulerDirectory());
// Create several job FILES
final int max = 5;
for( int i=0; i<max; i++ ) {
Properties props = new Properties();
props.put( RoboconfScheduler.JOB_NAME, "same job name" );
props.put( RoboconfScheduler.APP_NAME, "app" );
props.put( RoboconfScheduler.CMD_NAME, "cmd" );
props.put( RoboconfScheduler.CRON, "0 0 0 ? 1 *" );
Utils.writePropertiesFile( props, this.scheduler.getJobFile( "my-id-" + i ));
}
// Start the scheduler and halts triggers
this.scheduler.start();
this.scheduler.scheduler.standby();
File schedulerDirectory = this.scheduler.getSchedulerDirectory();
Set<JobKey> jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( max, jobKeys.size());
Assert.assertEquals( max, Utils.listAllFiles( schedulerDirectory ).size());
// List the jobs
List<ScheduledJob> jobs = this.scheduler.listJobs();
Assert.assertEquals( max, jobs.size());
Set<String> jobIds = new HashSet<> ();
for( ScheduledJob job : jobs ) {
Assert.assertEquals( "same job name", job.getJobName());
Assert.assertEquals( "app", job.getAppName());
Assert.assertEquals( "cmd", job.getCmdName());
Assert.assertEquals( "0 0 0 ? 1 *", job.getCron());
jobIds.add( job.getJobId());
}
Assert.assertEquals( max, jobIds.size());
}
@Test( expected = IOException.class )
public void testSaveJob_exception() throws Exception {
this.scheduler.scheduler = Mockito.mock( Scheduler.class );
Mockito.when( this.scheduler.scheduler.scheduleJob(
Mockito.any( JobDetail.class ),
Mockito.any( Trigger.class ))).thenThrow( new SchedulerException( "For test" ));
this.scheduler.saveJob( null, "job", "cmd", "0 0 0 ? 1 *", "app" );
}
@Test( expected = IOException.class )
public void testDeleteJob_exception() throws Exception {
this.scheduler.scheduler = Mockito.mock( Scheduler.class );
Mockito
.when( this.scheduler.scheduler.unscheduleJob( Mockito.any( TriggerKey.class )))
.thenThrow( new SchedulerException( "For test" ));
Properties props = new Properties();
props.put( RoboconfScheduler.JOB_NAME, "job" );
props.put( RoboconfScheduler.APP_NAME, "app" );
props.put( RoboconfScheduler.CMD_NAME, "cmd" );
props.put( RoboconfScheduler.CRON, "0 0 0 ? 1 *" );
try {
File f = this.scheduler.getJobFile( "job-id" );
Assert.assertTrue( f.getParentFile().mkdirs());
Utils.writePropertiesFile( props, f );
} catch( Exception e ) {
Assert.fail( "No exception was expected here." );
}
this.scheduler.deleteJob( "job-id" );
}
@Test
public void testLoadAllJobs_invalidProperties() throws Exception {
Utils.createDirectory( this.scheduler.getSchedulerDirectory());
// Create several job FILES
final int max = 5;
for( int i=0; i<max; i++ ) {
Properties props = new Properties();
props.put( RoboconfScheduler.JOB_NAME, "same job name" );
props.put( RoboconfScheduler.APP_NAME, "app" );
props.put( RoboconfScheduler.CMD_NAME, "cmd" );
// One job will have invalid properties
if( i != 3 )
props.put( RoboconfScheduler.CRON, "0 0 0 ? 1 *" );
Utils.writePropertiesFile( props, this.scheduler.getJobFile( "job-id-" + i ));
}
// Start the scheduler and halts triggers
this.scheduler.start();
this.scheduler.scheduler.standby();
File schedulerDirectory = this.scheduler.getSchedulerDirectory();
Set<JobKey> jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( max - 1, jobKeys.size());
Assert.assertEquals( max, Utils.listAllFiles( schedulerDirectory ).size());
// List the jobs
List<ScheduledJob> jobs = this.scheduler.listJobs();
Assert.assertEquals( max, jobs.size());
Set<String> jobIds = new HashSet<> ();
for( ScheduledJob job : jobs ) {
Assert.assertEquals( "app", job.getAppName());
Assert.assertEquals( "cmd", job.getCmdName());
Assert.assertEquals( "same job name", job.getJobName());
jobIds.add( job.getJobId());
}
Assert.assertEquals( max, jobIds.size());
}
@Test
public void testLoadAllJobs_exception() throws Exception {
Utils.createDirectory( this.scheduler.getSchedulerDirectory());
// Create several job FILES
final int max = 5;
for( int i=0; i<max; i++ ) {
Properties props = new Properties();
props.put( RoboconfScheduler.JOB_NAME, "job " + i );
props.put( RoboconfScheduler.APP_NAME, "app" );
props.put( RoboconfScheduler.CMD_NAME, "cmd" );
props.put( RoboconfScheduler.CRON, "0 0 0 ? 1 *" );
Utils.writePropertiesFile( props, this.scheduler.getJobFile( "job-id-" + i ));
}
// Throw an exception when a job is scheduled
this.scheduler.scheduler = Mockito.mock( Scheduler.class );
Mockito.when( this.scheduler.scheduler.scheduleJob(
Mockito.any( JobDetail.class ),
Mockito.any( Trigger.class ))).thenThrow( new SchedulerException( "For test" ));
// Load the jobs...
this.scheduler.loadJobs();
File schedulerDirectory = this.scheduler.getSchedulerDirectory();
Set<JobKey> jobKeys = this.scheduler.scheduler.getJobKeys( GroupMatcher.anyJobGroup());
Assert.assertEquals( 0, jobKeys.size());
Assert.assertEquals( max, Utils.listAllFiles( schedulerDirectory ).size());
}
@Test
public void testListAllJobs_withEmptyProperties() throws Exception {
Utils.createDirectory( this.scheduler.getSchedulerDirectory());
Properties props = new Properties();
props.put( RoboconfScheduler.JOB_NAME, "job1" );
props.put( RoboconfScheduler.CMD_NAME, "cmd" );
props.put( RoboconfScheduler.CRON, "0 0 0 ? 1 *" );
Utils.writePropertiesFile( props, this.scheduler.getJobFile( "job1" ));
Utils.writePropertiesFile( new Properties(), this.scheduler.getJobFile( "job2" ));
List<ScheduledJob> jobs = this.scheduler.listJobs();
Assert.assertEquals( 1, jobs.size());
Assert.assertEquals( "job1", jobs.get( 0 ).getJobName());
}
@Test
public void testFindJobProperties_inexitingJob() throws Exception {
ScheduledJob job = this.scheduler.findJobProperties( "inexisting" );
Assert.assertNull( job );
}
}