blob: cad36b962ac254c867b27a1da8882fc9e7108e27 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cloudstack.vm.schedule;
import com.cloud.event.ActionEventUtils;
import com.cloud.user.User;
import com.cloud.uservm.UserVm;
import com.cloud.utils.component.ComponentContext;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.command.user.vm.RebootVMCmd;
import org.apache.cloudstack.api.command.user.vm.StartVMCmd;
import org.apache.cloudstack.api.command.user.vm.StopVMCmd;
import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.vm.schedule.dao.VMScheduleDao;
import org.apache.cloudstack.vm.schedule.dao.VMScheduledJobDao;
import org.apache.commons.lang.time.DateUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import javax.persistence.EntityExistsException;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class VMSchedulerImplTest {
@Spy
@InjectMocks
private VMSchedulerImpl vmScheduler = new VMSchedulerImpl();
@Mock
private UserVmManager userVmManager;
@Mock
private VMScheduleDao vmScheduleDao;
@Mock
private VMScheduledJobDao vmScheduledJobDao;
@Mock
private EnumMap<VMSchedule.Action, String> actionEventMap;
@Mock
private AsyncJobManager asyncJobManager;
@Mock
private AsyncJobDispatcher asyncJobDispatcher;
private AutoCloseable closeable;
private MockedStatic<ActionEventUtils> actionEventUtilsMocked;
@Before
public void setUp() throws Exception {
closeable = MockitoAnnotations.openMocks(this);
actionEventUtilsMocked = Mockito.mockStatic(ActionEventUtils.class);
Mockito.when(ActionEventUtils.onScheduledActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(),
Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyBoolean(),
Mockito.anyLong()))
.thenReturn(1L);
Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(),
Mockito.anyString(), Mockito.anyBoolean(),
Mockito.anyString(),
Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong())).thenReturn(1L);
}
@After
public void tearDown() throws Exception {
actionEventUtilsMocked.close();
closeable.close();
}
@Test
public void testProcessJobRunning() {
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.STOP);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.FORCE_STOP);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.REBOOT);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Running, VMSchedule.Action.FORCE_REBOOT);
executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State.Stopped, VMSchedule.Action.START);
}
private void executeProcessJobWithVMStateAndActionNonSkipped(VirtualMachine.State state, VMSchedule.Action action) {
VMScheduledJob vmScheduledJob = Mockito.mock(VMScheduledJob.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Long expectedValue = 1L;
prepareMocksForProcessJob(vm, vmScheduledJob, state, action, expectedValue);
Long jobId = vmScheduler.processJob(vmScheduledJob, vm);
actionEventUtilsMocked.verify(() -> ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), null,
actionEventMap.get(action), true,
String.format("Executing action (%s) for VM Id:%s", vmScheduledJob.getAction(), vm.getUuid()),
vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), 0));
Assert.assertEquals(expectedValue, jobId);
}
private void prepareMocksForProcessJob(VirtualMachine vm, VMScheduledJob vmScheduledJob,
VirtualMachine.State vmState, VMSchedule.Action action,
Long executeJobReturnValue) {
Mockito.when(vm.getState()).thenReturn(vmState);
Mockito.when(vmScheduledJob.getAction()).thenReturn(action);
if (executeJobReturnValue != null) {
Mockito.doReturn(executeJobReturnValue).when(vmScheduler).executeStartVMJob(
Mockito.any(VirtualMachine.class), Mockito.anyLong());
Mockito.doReturn(executeJobReturnValue).when(vmScheduler).executeStopVMJob(
Mockito.any(VirtualMachine.class), Mockito.anyBoolean(), Mockito.anyLong());
Mockito.doReturn(executeJobReturnValue).when(vmScheduler).executeRebootVMJob(
Mockito.any(VirtualMachine.class), Mockito.anyBoolean(), Mockito.anyLong());
}
}
@Test
public void testProcessJobInvalidAction() {
VMScheduledJob vmScheduledJob = Mockito.mock(VMScheduledJob.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
prepareMocksForProcessJob(vm, vmScheduledJob, VirtualMachine.State.Running, VMSchedule.Action.START, null);
Long jobId = vmScheduler.processJob(vmScheduledJob, vm);
Assert.assertNull(jobId);
}
@Test
public void testProcessJobVMInInvalidState() {
VMScheduledJob vmScheduledJob = Mockito.mock(VMScheduledJob.class);
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
prepareMocksForProcessJob(vm, vmScheduledJob, VirtualMachine.State.Unknown, VMSchedule.Action.START, null);
Long jobId = vmScheduler.processJob(vmScheduledJob, vm);
Assert.assertNull(jobId);
}
@Test
public void testScheduleNextJobScheduleScheduleExists() {
Date now = DateUtils.setSeconds(new Date(), 0);
Date startDate = DateUtils.addDays(now, 1);
Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(startDate, 1), Calendar.MINUTE);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(startDate);
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Mockito.when(vmScheduledJobDao.persist(Mockito.any())).thenThrow(EntityExistsException.class);
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleFutureSchedule() {
Date now = DateUtils.setSeconds(new Date(), 0);
Date startDate = DateUtils.addDays(now, 1);
Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(startDate, 1), Calendar.MINUTE);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(startDate);
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleFutureScheduleWithTimeZoneChecks() throws Exception {
// Ensure that Date vmSchedulerImpl.scheduleNextJob(VMScheduleVO vmSchedule) generates
// the correct scheduled time on basis of schedule(cron), start date
// and the timezone of the user. The system running the test can have any timezone.
String cron = "30 5 * * *";
Date now = DateUtils.setSeconds(new Date(), 0);
Date startDate = DateUtils.addDays(now, 1);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn(cron);
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("EST").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(startDate);
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
// The timezone of the user is EST. The cron expression is 30 5 * * *.
// The start date is 1 day from now. The expected scheduled time is 5:30 AM EST.
// The actual scheduled time is 10:30 AM UTC.
// The actual scheduled time is 5:30 AM EST.
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(startDate.toInstant(), vmSchedule.getTimeZoneId());
zonedDateTime = zonedDateTime.withHour(5).withMinute(30).withSecond(0).withNano(0);
Date expectedScheduledTime = Date.from(zonedDateTime.toInstant());
if (expectedScheduledTime.before(startDate)) {
expectedScheduledTime = DateUtils.addDays(expectedScheduledTime, 1);
}
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleCurrentSchedule() {
Date now = DateUtils.setSeconds(new Date(), 0);
Date expectedScheduledTime = DateUtils.round(DateUtils.addMinutes(now, 1), Calendar.MINUTE);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn("* * * * *");
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("UTC").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(now, -1));
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleCurrentScheduleWithTimeZoneChecks() throws Exception {
// Ensure that Date vmSchedulerImpl.scheduleNextJob(VMScheduleVO vmSchedule) generates
// the correct scheduled time on basis of schedule(cron), start date
// and the timezone of the user. The system running the test can have any timezone.
String cron = "30 5 * * *";
Date now = DateUtils.setSeconds(new Date(), 0);
UserVm vm = Mockito.mock(UserVm.class);
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Mockito.when(vmSchedule.getSchedule()).thenReturn(cron);
Mockito.when(vmSchedule.getTimeZoneId()).thenReturn(TimeZone.getTimeZone("EST").toZoneId());
Mockito.when(vmSchedule.getStartDate()).thenReturn(DateUtils.addDays(now, -1));
Mockito.when(userVmManager.getUserVm(Mockito.anyLong())).thenReturn(vm);
// The timezone of the user is EST. The cron expression is 30 5 * * *.
// The start date is 1 day ago. The expected scheduled time is 5:30 AM EST.
// The actual scheduled time is 10:30 AM UTC.
// The actual scheduled time is 5:30 AM EST.
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(now.toInstant(), vmSchedule.getTimeZoneId());
zonedDateTime = zonedDateTime.withHour(5).withMinute(30).withSecond(0).withNano(0);
Date expectedScheduledTime = Date.from(zonedDateTime.toInstant());
if (expectedScheduledTime.before(now)) {
expectedScheduledTime = DateUtils.addDays(expectedScheduledTime, 1);
}
Date actualScheduledTime = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertEquals(expectedScheduledTime, actualScheduledTime);
}
@Test
public void testScheduleNextJobScheduleExpired() {
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEndDate()).thenReturn(DateUtils.addMinutes(new Date(), -5));
Mockito.when(vmSchedule.getEnabled()).thenReturn(true);
Date actualDate = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertNull(actualDate);
}
@Test
public void testScheduleNextJobScheduleDisabled() {
VMScheduleVO vmSchedule = Mockito.mock(VMScheduleVO.class);
Mockito.when(vmSchedule.getEnabled()).thenReturn(false);
Date actualDate = vmScheduler.scheduleNextJob(vmSchedule, new Date());
Assert.assertNull(actualDate);
}
@Test
public void testExecuteJobs() {
/*
Test VMSchedulerImpl.executeJobs() method with a map of VMScheduledJob objects
covering all the possible scenarios
1. When the job is executed successfully
2. When the job is skipped (processJob returns null)
*/
VMScheduledJobVO job1 = Mockito.mock(VMScheduledJobVO.class);
VMScheduledJobVO job2 = Mockito.mock(VMScheduledJobVO.class);
UserVm vm1 = Mockito.mock(UserVm.class);
UserVm vm2 = Mockito.mock(UserVm.class);
Mockito.when(job2.getVmId()).thenReturn(2L);
Mockito.when(userVmManager.getUserVm(2L)).thenReturn(vm2);
Mockito.doReturn(null).when(vmScheduler).processJob(job2, vm2);
Mockito.when(vmScheduledJobDao.acquireInLockTable(job1.getId())).thenReturn(job1);
Mockito.when(vmScheduledJobDao.acquireInLockTable(job2.getId())).thenReturn(job2);
Map<Long, VMScheduledJob> jobs = new HashMap<>();
jobs.put(1L, job1);
jobs.put(2L, job2);
ReflectionTestUtils.setField(vmScheduler, "currentTimestamp", new Date());
vmScheduler.executeJobs(jobs);
Mockito.verify(vmScheduler, Mockito.times(2)).processJob(Mockito.any(), Mockito.any());
Mockito.verify(vmScheduledJobDao, Mockito.times(2)).acquireInLockTable(Mockito.anyLong());
}
@Test
public void testExecuteStopVMJob() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
when(ComponentContext.inject(StopVMCmd.class)).thenReturn(Mockito.mock(StopVMCmd.class));
long jobId = vmScheduler.executeStopVMJob(vm, false, 1L);
Assert.assertEquals(1L, jobId);
}
}
@Test
public void testExecuteRebootVMJob() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
when(ComponentContext.inject(RebootVMCmd.class)).thenReturn(Mockito.mock(RebootVMCmd.class));
long jobId = vmScheduler.executeRebootVMJob(vm, false, 1L);
Assert.assertEquals(1L, jobId);
}
}
@Test
public void testExecuteStartVMJob() {
VirtualMachine vm = Mockito.mock(VirtualMachine.class);
Mockito.when(asyncJobManager.submitAsyncJob(Mockito.any(AsyncJobVO.class))).thenReturn(1L);
try (MockedStatic<ComponentContext> ignored = Mockito.mockStatic(ComponentContext.class)) {
when(ComponentContext.inject(StartVMCmd.class)).thenReturn(Mockito.mock(StartVMCmd.class));
long jobId = vmScheduler.executeStartVMJob(vm, 1L);
Assert.assertEquals(1L, jobId);
}
}
}