/* * Copyright 2016 ThoughtWorks, Inc. * * 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 com.thoughtworks.go.server.service; import com.thoughtworks.go.config.CaseInsensitiveString; import com.thoughtworks.go.config.PipelineConfig; import com.thoughtworks.go.config.TimerConfig; import com.thoughtworks.go.listener.ConfigChangedListener; import com.thoughtworks.go.listener.EntityConfigChangedListener; import com.thoughtworks.go.serverhealth.HealthStateScope; import com.thoughtworks.go.serverhealth.HealthStateType; import com.thoughtworks.go.serverhealth.ServerHealthService; import com.thoughtworks.go.serverhealth.ServerHealthState; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.quartz.*; import java.util.Date; import java.util.List; import static com.thoughtworks.go.helper.PipelineConfigMother.pipelineConfig; import static com.thoughtworks.go.helper.PipelineConfigMother.pipelineConfigWithTimer; import static java.util.Arrays.asList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; public class TimerSchedulerTest { private SchedulerFactory schedulerFactory; private Scheduler scheduler; @Before public void setup() throws Exception { schedulerFactory = mock(SchedulerFactory.class); scheduler = mock(Scheduler.class); } @After public void teardown() throws Exception { Mockito.verifyNoMoreInteractions(schedulerFactory, scheduler); } @Test public void shouldRegisterJobsWithSchedulerForEachPipelineWithTimerOnInit() throws Exception { when(schedulerFactory.getScheduler()).thenReturn(scheduler); GoConfigService goConfigService = mock(GoConfigService.class); List<PipelineConfig> pipelineConfigs = asList( pipelineConfigWithTimer("uat", "0 15 10 ? * MON-FRI"), pipelineConfig("dist")); when(goConfigService.getAllPipelineConfigs()).thenReturn(pipelineConfigs); TimerScheduler timerScheduler = new TimerScheduler(schedulerFactory, goConfigService, null, null); timerScheduler.initialize(); JobDetail expectedJob = new JobDetail("uat", "CruiseTimers", TimerScheduler.SchedulePipelineQuartzJob.class); Trigger expectedTrigger = new CronTrigger("uat", "CruiseTimers", "0 15 10 ? * MON-FRI"); verify(scheduler).scheduleJob(expectedJob, expectedTrigger); verify(schedulerFactory).getScheduler(); verify(scheduler).start(); } @Test public void shouldUpdateServerHealthStatusIfSchedulerInitializationFails() throws Exception { when(schedulerFactory.getScheduler()).thenThrow(new SchedulerException("Should fail creation for this test")); GoConfigService goConfigService = mock(GoConfigService.class); ServerHealthService serverHealthService = mock(ServerHealthService.class); TimerScheduler timerScheduler = new TimerScheduler(schedulerFactory, goConfigService, null, serverHealthService); timerScheduler.initialize(); verify(schedulerFactory).getScheduler(); verify(serverHealthService).update( ServerHealthState.error("Failed to initialize timer", "Cannot schedule pipelines using the timer", HealthStateType.general(HealthStateScope.GLOBAL))); } @Test public void shouldUpdateServerHealthStatusWhenCronSpecCantBeParsed() throws Exception { when(schedulerFactory.getScheduler()).thenReturn(scheduler); GoConfigService goConfigService = mock(GoConfigService.class); when(goConfigService.getAllPipelineConfigs()).thenReturn(asList(pipelineConfigWithTimer("uat", "bad cron spec!!!"))); ServerHealthService serverHealthService = mock(ServerHealthService.class); TimerScheduler timerScheduler = new TimerScheduler(schedulerFactory, goConfigService, null, serverHealthService); timerScheduler.initialize(); verify(schedulerFactory).getScheduler(); verify(scheduler).start(); verify(serverHealthService).update( ServerHealthState.error("Bad timer specification for timer in Pipeline: uat", "Cannot schedule pipeline using the timer", HealthStateType.general(HealthStateScope.forPipeline("uat")))); } @Test public void shouldScheduleOtherPipelinesEvenIfOneHasAnInvalidCronSpec() throws Exception { when(schedulerFactory.getScheduler()).thenReturn(scheduler); GoConfigService goConfigService = mock(GoConfigService.class); List<PipelineConfig> pipelineConfigs = asList( pipelineConfigWithTimer("uat", "---- bad cron spec!"), pipelineConfigWithTimer("dist", "0 15 10 ? * MON-FRI")); when(goConfigService.getAllPipelineConfigs()).thenReturn(pipelineConfigs); TimerScheduler timerScheduler = new TimerScheduler(schedulerFactory, goConfigService, null, mock(ServerHealthService.class)); timerScheduler.initialize(); JobDetail expectedJob = new JobDetail("dist", "CruiseTimers", TimerScheduler.SchedulePipelineQuartzJob.class); Trigger expectedTrigger = new CronTrigger("dist", "CruiseTimers", "0 15 10 ? * MON-FRI"); verify(scheduler).scheduleJob(expectedJob, expectedTrigger); verify(schedulerFactory).getScheduler(); verify(scheduler).start(); } @Test public void shouldUpdateServerHealthStatusWhenPipelineCantBeAddedToTheQuartzScheduler() throws Exception { Scheduler scheduler = mock(Scheduler.class); when(scheduler.scheduleJob(any(JobDetail.class), any(Trigger.class))).thenThrow( new SchedulerException("scheduling failed!")); SchedulerFactory schedulerFactory = mock(SchedulerFactory.class); when(schedulerFactory.getScheduler()).thenReturn(scheduler); GoConfigService goConfigService = mock(GoConfigService.class); when(goConfigService.getAllPipelineConfigs()).thenReturn(asList(pipelineConfigWithTimer("uat", "* * * * * ?"))); ServerHealthService serverHealthService = mock(ServerHealthService.class); TimerScheduler timerScheduler = new TimerScheduler(schedulerFactory, goConfigService, null, serverHealthService); timerScheduler.initialize(); verify(serverHealthService).update( ServerHealthState.error("Could not register pipeline 'uat' with timer", "", HealthStateType.general(HealthStateScope.forPipeline("uat")))); } @Test public void shouldRegisterAsACruiseConfigChangeListener() throws Exception { when(schedulerFactory.getScheduler()).thenReturn(scheduler); GoConfigService goConfigService = mock(GoConfigService.class); TimerScheduler timerScheduler = new TimerScheduler(schedulerFactory, goConfigService, null, null); timerScheduler.initialize(); verify(goConfigService).register(timerScheduler); verify(schedulerFactory).getScheduler(); verify(scheduler).start(); } @Test public void shouldRescheduleTimerTriggerPipelineWhenItsConfigChanges() throws SchedulerException { GoConfigService goConfigService = mock(GoConfigService.class); when(schedulerFactory.getScheduler()).thenReturn(scheduler); String pipelineName = "timer-based-pipeline"; when(scheduler.getJobDetail(pipelineName, TimerScheduler.QUARTZ_GROUP)).thenReturn(mock(JobDetail.class)); TimerScheduler timerScheduler = new TimerScheduler(schedulerFactory, goConfigService, null, null); ArgumentCaptor<ConfigChangedListener> captor = ArgumentCaptor.forClass(ConfigChangedListener.class); doNothing().when(goConfigService).register(captor.capture()); timerScheduler.initialize(); List<ConfigChangedListener> listeners = captor.getAllValues(); assertThat(listeners.get(1) instanceof EntityConfigChangedListener, is(true)); EntityConfigChangedListener<PipelineConfig> pipelineConfigChangeListener= (EntityConfigChangedListener<PipelineConfig>) listeners.get(1); PipelineConfig pipelineConfig = mock(PipelineConfig.class); when(pipelineConfig.name()).thenReturn(new CaseInsensitiveString(pipelineName)); when(pipelineConfig.getTimer()).thenReturn(new TimerConfig("* * * * * ?", true)); ArgumentCaptor<JobDetail> jobDetailArgumentCaptor = ArgumentCaptor.forClass(JobDetail.class); ArgumentCaptor<CronTrigger> triggerArgumentCaptor = ArgumentCaptor.forClass(CronTrigger.class); when(scheduler.scheduleJob(jobDetailArgumentCaptor.capture(), triggerArgumentCaptor.capture())).thenReturn(new Date()); pipelineConfigChangeListener.onEntityConfigChange(pipelineConfig); assertThat(jobDetailArgumentCaptor.getValue().getName(), is(pipelineName)); assertThat(triggerArgumentCaptor.getValue().getCronExpression(), is("* * * * * ?")); verify(schedulerFactory).getScheduler(); verify(scheduler).start(); verify(scheduler).getJobDetail(pipelineName, TimerScheduler.QUARTZ_GROUP); verify(scheduler).unscheduleJob(pipelineName, TimerScheduler.QUARTZ_GROUP); verify(scheduler).deleteJob(pipelineName, TimerScheduler.QUARTZ_GROUP); verify(scheduler).scheduleJob(jobDetailArgumentCaptor.getValue(), triggerArgumentCaptor.getValue()); } }