DaanHoogland commented on code in PR #7289:
URL: https://github.com/apache/cloudstack/pull/7289#discussion_r1149219224
##########
server/src/main/java/com/cloud/vm/schedule/VMScheduleManagerImpl.java:
##########
@@ -447,18 +445,17 @@ public void scheduleVMs() {
private Map<String, String> setVMTag(VMScheduleVO vmSchedule) {
Map<String,String> Tag = new HashMap<>();
if (!vmSchedule.getTag().isEmpty()) {
- Tag.put("ScheduleMessage", vmSchedule.getTag());
+ Tag.put("ScheduleMessage" + currentTimestamp, vmSchedule.getTag());
Review Comment:
not a code comment but more of a functional comment; I would not use tags
for this but annotations/comments.
##########
server/src/main/java/com/cloud/vm/schedule/VMScheduleManagerImpl.java:
##########
@@ -0,0 +1,598 @@
+// 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 com.cloud.vm.schedule;
+
+import com.cloud.alert.AlertManager;
+import com.cloud.api.ApiDispatcher;
+import com.cloud.api.ApiGsonHelper;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.server.ResourceTag;
+import com.cloud.user.User;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.ListUtils;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.db.DB;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionStatus;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.VirtualMachineProfile;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.vm.schedule.dao.VMScheduleDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.user.vmschedule.CreateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.ListVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.UpdateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DeleteVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.EnableVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DisableVMScheduleCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+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.managed.context.ManagedContextTimerTask;
+import org.apache.cloudstack.poll.BackgroundPollManager;
+import org.apache.commons.collections.MapUtils;
+import org.apache.log4j.Logger;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.server.TaggedResourceService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.TimerTask;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.TimeZone;
+import java.util.Collections;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class VMScheduleManagerImpl extends ManagerBase implements
VMScheduleManager, Configurable, PluggableService {
+ public static final Logger LOGGER =
Logger.getLogger(VMScheduleManagerImpl.class);
+
+ @Inject
+ private VMScheduleDao vmScheduleDao;
+
+ @Inject
+ private VirtualMachineManager vmManager;
+
+ @Inject
+ private BackgroundPollManager backgroundPollManager;
+
+ @Inject
+ private AsyncJobManager asyncJobManager;
+
+ @Inject
+ private VMInstanceDao vmInstanceDao;
+
+ @Inject
+ private ApiDispatcher apiDispatcher;
+
+ private AsyncJobDispatcher asyncJobDispatcher;
+
+ @Inject
+ public TaggedResourceService taggedResourceService;
+
+ private Date currentTimestamp;
+
+ @Inject
+ private AlertManager alertManager;
+
+ private ScheduledExecutorService backgroundPollTaskScheduler;
+
+ private volatile boolean isConfiguredAndStarted = false;
+
+ private static final ConfigKey<Boolean> EnableVMSchedulerInterval = new
ConfigKey<>("Advanced", Boolean.class,
+ "vm.scheduler.enable", "false",
+ "Enable the VMScheduler Interval to schedule tasks on VM.",
false);
+
+ private static final ConfigKey<Integer> VMSchedulerInterval = new
ConfigKey<>("Advanced", Integer.class,
+ "vm.scheduler.interval", "60",
+ "The interval at which VM Scheduler runs in milliseconds", false,
EnableVMSchedulerInterval.key());
+
+ @Override
+ public boolean start() {
+ if (isConfiguredAndStarted) {
+ return true;
+ }
+ currentTimestamp = new Date();
+ for (final VMScheduleVO vmSchedule : vmScheduleDao.listAll()) {
+ scheduleNextVMJob(vmSchedule);
+ }
+ backgroundPollTaskScheduler = Executors.newScheduledThreadPool(100,
new NamedThreadFactory("BackgroundTaskPollManager"));
+ final TimerTask vmPollTask = new ManagedContextTimerTask() {
+ @Override
+ protected void runInContext() {
+ try {
+ poll(new Date());
+ } catch (final Throwable t) {
+ LOGGER.warn("Catch throwable in vm scheduler ", t);
+ }
+ }
+ };
+ backgroundPollTaskScheduler.scheduleWithFixedDelay(vmPollTask,
VMSchedulerInterval.value() * 1000L, VMSchedulerInterval.value() * 1000L,
TimeUnit.MILLISECONDS);
+ isConfiguredAndStarted = true;
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ if (isConfiguredAndStarted) {
+ backgroundPollTaskScheduler.shutdown();
+ }
+ return true;
+ }
+
+ @Override
+ public String getConfigComponentName() {
+ return VMScheduleManager.class.getSimpleName();
+ }
+
+ @Override
+ public ConfigKey<?>[] getConfigKeys() {
+ return new ConfigKey<?>[] {
+ VMSchedulerInterval,
+ EnableVMSchedulerInterval
+ };
+ }
+
+ @Override
+ public boolean configure(final String name, final Map<String, Object>
params) throws ConfigurationException {
+ return true;
+ }
+
+ @Override
+ public List<Class<?>> getCommands() {
+ final List<Class<?>> cmdList = new ArrayList<>();
+ cmdList.add(CreateVMScheduleCmd.class);
+ cmdList.add(ListVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ cmdList.add(DeleteVMScheduleCmd.class);
+ cmdList.add(EnableVMScheduleCmd.class);
+ cmdList.add(DisableVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ return cmdList;
+ }
+
+ @Override
+ public VMSchedule findVMSchedule(Long id) {
+ if (id == null || id < 1L) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VMSchedule ID is invalid [%s]",
id));
+ return null;
+ }
+ }
+
+ VMSchedule vmSchedule = vmScheduleDao.findById(id);
+ if (vmSchedule == null) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VmSchedule ID not found [id=%s]",
id));
+ return null;
+ }
+ }
+
+ return vmSchedule;
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_CREATE,
eventDescription = "creating vm schedule", async = true)
+ public VMSchedule createVMSchedule(CreateVMScheduleCmd cmd) {
+ VMSchedule.State state = VMSchedule.State.DISABLED;
+ final String action = cmd.getAction();
+ final Long vmId = cmd.getVmId();
+ String intervalType = cmd.getIntervalType();
+ String scheduleString = cmd.getSchedule();
+ String description = cmd.getDescription();
+ String tag = cmd.getTag();
+ TimeZone timeZone = TimeZone.getTimeZone(cmd.getTimezone());
+ Boolean enable = cmd.getEnable();
+
+ if (enable) {
+ state = VMSchedule.State.ENABLED;
+ }
+
+ if (intervalType.equals("hourly"))
+ intervalType = "HOURLY";
+
+ if (intervalType.equals("daily"))
+ intervalType = "DAILY";
+
+ if (intervalType.equals("weekly"))
+ intervalType = "WEEKLY";
+
+ if (intervalType.equals("monthly"))
+ intervalType = "MONTHLY";
+
+ if (description == null) {
+ description = action + " VM Instance with vmId:" + vmId + " " +
intervalType + " at " + scheduleString;
+ }
+
+ if (tag == null) {
+ tag = String.format("VM " + cmd.getAction() +
cmd.getIntervalType() + cmd.getSchedule());
+ }
+
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ if (intervalType == null) {
+ throw new CloudRuntimeException("Invalid interval type provided");
+ }
+
+ final String timezoneId = timeZone.getID();
+ if (!timezoneId.equals(cmd.getTimezone())) {
+ LOGGER.warn("Using timezone: " + timezoneId + " for running this
schedule as an equivalent of " + cmd.getTimezone());
+ }
+
+ Date nextDateTime = null;
+ try {
+ nextDateTime =
DateUtil.getNextRunTime(DateUtil.IntervalType.valueOf(intervalType),
cmd.getSchedule(), timezoneId, null);
+ } catch (Exception e) {
+ throw new InvalidParameterValueException("Invalid schedule: " +
cmd.getSchedule() + " for interval type: " + cmd.getIntervalType());
+ }
+
+ return vmScheduleDao.persist(new VMScheduleVO(vmId, description,
action, intervalType, scheduleString,
+ timezoneId, nextDateTime, tag, state, 1L));
+ }
+
+ @Override
+ public List<VMSchedule> listVMSchedules(ListVMScheduleCmd cmd) {
+ if (cmd.getId() != null) {
+ VMSchedule vmSchedule = findVMSchedule(cmd.getId());
+ List<VMSchedule> arr = new ArrayList<>();
+ arr.add(vmSchedule);
+ return arr;
+ }
+
+ if (cmd.getVmId() != null) {
+ List<VMSchedule> vmSchedules = new ArrayList<>();
+ List<VMScheduleVO> vmScheduleVOS =
vmScheduleDao.findByVm(cmd.getVmId());
+ for (VMScheduleVO vmSchedule : vmScheduleVOS) {
+ vmSchedules.add(vmSchedule);
+ }
+ return vmSchedules;
+ }
+
+ List<? extends VMScheduleVO> vmSchedules = vmScheduleDao.listAll();
+ return ListUtils.toListOfInterface(vmSchedules);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_DELETE,
eventDescription = "deleting VM Schedule")
+ public boolean deleteVMSchedule(Long vmScheduleId) {
+ VMSchedule vmSchedule = vmScheduleDao.findById(vmScheduleId);
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + vmScheduleId);
+ }
+
+ return vmScheduleDao.remove(vmSchedule.getId());
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_ENABLE,
eventDescription = "enable VM Schedule")
+ public boolean enableVMSchedule(Long vmScheduleId) {
+ VMScheduleVO vmSchedule = vmScheduleDao.findById(vmScheduleId);
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + vmScheduleId);
+ }
+
+ vmSchedule.setState(VMSchedule.State.ENABLED);
+ return vmScheduleDao.update(vmSchedule.getId(), vmSchedule);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_DISABLE,
eventDescription = "disable VM Schedule")
+ public boolean disableVMSchedule(Long vmScheduleId) {
+ VMScheduleVO vmSchedule = vmScheduleDao.findById(vmScheduleId);
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + vmScheduleId);
+ }
+
+ vmSchedule.setState(VMSchedule.State.DISABLED);
+ return vmScheduleDao.update(vmSchedule.getId(), vmSchedule);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_UPDATE,
eventDescription = "update VM Schedule")
+ public VMSchedule updateVMSchedule(UpdateVMScheduleCmd cmd) {
+ VMScheduleVO vmSchedule = vmScheduleDao.findById(cmd.getId());
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + cmd.getId());
+ }
+ String description = cmd.getDescription();
+ String schedule = cmd.getSchedule();
+ String intervalType = cmd.getIntervalType();
+ String action = cmd.getAction();
+ String tag = cmd.getTag();
+ String timezone = cmd.getTimezone();
+
+ if (vmSchedule.getState() == VMSchedule.State.DISABLED) {
+ if (description != null)
+ vmSchedule.setDescription(description);
+ if(schedule != null)
+ vmSchedule.setSchedule(schedule);
+ if(intervalType != null)
+ vmSchedule.setScheduleType(intervalType);
+ if (action != null)
+ vmSchedule.setAction(action);
+ if (tag != null)
+ vmSchedule.setTag(tag);
+ if (timezone != null)
+ vmSchedule.setTimezone(timezone);
+
+ vmScheduleDao.update(vmSchedule.getId(), vmSchedule);
+ } else {
+ throw new InvalidParameterValueException("Disable the state of VM
Schedule before updating it");
+ }
+ return vmSchedule;
+ }
+
+ public void poll(final Date timestamp) {
+ currentTimestamp = timestamp;
+ GlobalLock scanLock = GlobalLock.getInternLock("vm.poll");
+
+ try {
+ if (scanLock.lock(5)) {
+ try {
+ checkStatusOfCurrentlyExecutingVMs();
+ } finally {
+ scanLock.unlock();
+ }
+ }
+ } finally {
+ scanLock.releaseRef();
+ }
+
+ scanLock = GlobalLock.getInternLock("vm.poll");
+ try {
+ if (scanLock.lock(5)) {
+ try {
+ scheduleVMs();
+ } finally {
+ scanLock.unlock();
+ }
+ }
+ } finally {
+ scanLock.releaseRef();
+ }
+ }
+
+ @DB
+ public void scheduleVMs() {
+ String displayTime =
DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
+ LOGGER.debug("VM poll is being called at " + displayTime);
+
+ final List<VMScheduleVO> vmsToBeExecuted = vmScheduleDao.listAll();
Review Comment:
This list can become very long, should we have a more precise search method
for this? i.e. all schedules to perform in the near future?
##########
server/src/main/java/com/cloud/vm/schedule/VMScheduleManagerImpl.java:
##########
@@ -0,0 +1,598 @@
+// 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 com.cloud.vm.schedule;
+
+import com.cloud.alert.AlertManager;
+import com.cloud.api.ApiDispatcher;
+import com.cloud.api.ApiGsonHelper;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.server.ResourceTag;
+import com.cloud.user.User;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.ListUtils;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.db.DB;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionStatus;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.VirtualMachineProfile;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.vm.schedule.dao.VMScheduleDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.user.vmschedule.CreateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.ListVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.UpdateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DeleteVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.EnableVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DisableVMScheduleCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+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.managed.context.ManagedContextTimerTask;
+import org.apache.cloudstack.poll.BackgroundPollManager;
+import org.apache.commons.collections.MapUtils;
+import org.apache.log4j.Logger;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.server.TaggedResourceService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.TimerTask;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.TimeZone;
+import java.util.Collections;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class VMScheduleManagerImpl extends ManagerBase implements
VMScheduleManager, Configurable, PluggableService {
+ public static final Logger LOGGER =
Logger.getLogger(VMScheduleManagerImpl.class);
+
+ @Inject
+ private VMScheduleDao vmScheduleDao;
+
+ @Inject
+ private VirtualMachineManager vmManager;
+
+ @Inject
+ private BackgroundPollManager backgroundPollManager;
+
+ @Inject
+ private AsyncJobManager asyncJobManager;
+
+ @Inject
+ private VMInstanceDao vmInstanceDao;
+
+ @Inject
+ private ApiDispatcher apiDispatcher;
+
+ private AsyncJobDispatcher asyncJobDispatcher;
+
+ @Inject
+ public TaggedResourceService taggedResourceService;
+
+ private Date currentTimestamp;
+
+ @Inject
+ private AlertManager alertManager;
+
+ private ScheduledExecutorService backgroundPollTaskScheduler;
+
+ private volatile boolean isConfiguredAndStarted = false;
+
+ private static final ConfigKey<Boolean> EnableVMSchedulerInterval = new
ConfigKey<>("Advanced", Boolean.class,
+ "vm.scheduler.enable", "false",
+ "Enable the VMScheduler Interval to schedule tasks on VM.",
false);
+
+ private static final ConfigKey<Integer> VMSchedulerInterval = new
ConfigKey<>("Advanced", Integer.class,
+ "vm.scheduler.interval", "60",
+ "The interval at which VM Scheduler runs in milliseconds", false,
EnableVMSchedulerInterval.key());
+
+ @Override
+ public boolean start() {
+ if (isConfiguredAndStarted) {
+ return true;
+ }
+ currentTimestamp = new Date();
+ for (final VMScheduleVO vmSchedule : vmScheduleDao.listAll()) {
+ scheduleNextVMJob(vmSchedule);
+ }
+ backgroundPollTaskScheduler = Executors.newScheduledThreadPool(100,
new NamedThreadFactory("BackgroundTaskPollManager"));
+ final TimerTask vmPollTask = new ManagedContextTimerTask() {
+ @Override
+ protected void runInContext() {
+ try {
+ poll(new Date());
+ } catch (final Throwable t) {
+ LOGGER.warn("Catch throwable in vm scheduler ", t);
+ }
+ }
+ };
+ backgroundPollTaskScheduler.scheduleWithFixedDelay(vmPollTask,
VMSchedulerInterval.value() * 1000L, VMSchedulerInterval.value() * 1000L,
TimeUnit.MILLISECONDS);
+ isConfiguredAndStarted = true;
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ if (isConfiguredAndStarted) {
+ backgroundPollTaskScheduler.shutdown();
+ }
+ return true;
+ }
+
+ @Override
+ public String getConfigComponentName() {
+ return VMScheduleManager.class.getSimpleName();
+ }
+
+ @Override
+ public ConfigKey<?>[] getConfigKeys() {
+ return new ConfigKey<?>[] {
+ VMSchedulerInterval,
+ EnableVMSchedulerInterval
+ };
+ }
+
+ @Override
+ public boolean configure(final String name, final Map<String, Object>
params) throws ConfigurationException {
+ return true;
+ }
+
+ @Override
+ public List<Class<?>> getCommands() {
+ final List<Class<?>> cmdList = new ArrayList<>();
+ cmdList.add(CreateVMScheduleCmd.class);
+ cmdList.add(ListVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ cmdList.add(DeleteVMScheduleCmd.class);
+ cmdList.add(EnableVMScheduleCmd.class);
+ cmdList.add(DisableVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ return cmdList;
+ }
+
+ @Override
+ public VMSchedule findVMSchedule(Long id) {
+ if (id == null || id < 1L) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VMSchedule ID is invalid [%s]",
id));
+ return null;
+ }
+ }
+
+ VMSchedule vmSchedule = vmScheduleDao.findById(id);
+ if (vmSchedule == null) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VmSchedule ID not found [id=%s]",
id));
+ return null;
+ }
+ }
+
+ return vmSchedule;
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_CREATE,
eventDescription = "creating vm schedule", async = true)
+ public VMSchedule createVMSchedule(CreateVMScheduleCmd cmd) {
+ VMSchedule.State state = VMSchedule.State.DISABLED;
+ final String action = cmd.getAction();
+ final Long vmId = cmd.getVmId();
+ String intervalType = cmd.getIntervalType();
+ String scheduleString = cmd.getSchedule();
+ String description = cmd.getDescription();
+ String tag = cmd.getTag();
+ TimeZone timeZone = TimeZone.getTimeZone(cmd.getTimezone());
+ Boolean enable = cmd.getEnable();
+
+ if (enable) {
+ state = VMSchedule.State.ENABLED;
+ }
+
+ if (intervalType.equals("hourly"))
+ intervalType = "HOURLY";
+
+ if (intervalType.equals("daily"))
+ intervalType = "DAILY";
+
+ if (intervalType.equals("weekly"))
+ intervalType = "WEEKLY";
+
+ if (intervalType.equals("monthly"))
+ intervalType = "MONTHLY";
+
+ if (description == null) {
+ description = action + " VM Instance with vmId:" + vmId + " " +
intervalType + " at " + scheduleString;
+ }
+
+ if (tag == null) {
+ tag = String.format("VM " + cmd.getAction() +
cmd.getIntervalType() + cmd.getSchedule());
+ }
+
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ if (intervalType == null) {
+ throw new CloudRuntimeException("Invalid interval type provided");
+ }
+
+ final String timezoneId = timeZone.getID();
+ if (!timezoneId.equals(cmd.getTimezone())) {
+ LOGGER.warn("Using timezone: " + timezoneId + " for running this
schedule as an equivalent of " + cmd.getTimezone());
+ }
+
+ Date nextDateTime = null;
+ try {
+ nextDateTime =
DateUtil.getNextRunTime(DateUtil.IntervalType.valueOf(intervalType),
cmd.getSchedule(), timezoneId, null);
+ } catch (Exception e) {
+ throw new InvalidParameterValueException("Invalid schedule: " +
cmd.getSchedule() + " for interval type: " + cmd.getIntervalType());
+ }
+
+ return vmScheduleDao.persist(new VMScheduleVO(vmId, description,
action, intervalType, scheduleString,
+ timezoneId, nextDateTime, tag, state, 1L));
+ }
+
+ @Override
+ public List<VMSchedule> listVMSchedules(ListVMScheduleCmd cmd) {
+ if (cmd.getId() != null) {
+ VMSchedule vmSchedule = findVMSchedule(cmd.getId());
+ List<VMSchedule> arr = new ArrayList<>();
+ arr.add(vmSchedule);
+ return arr;
+ }
+
+ if (cmd.getVmId() != null) {
+ List<VMSchedule> vmSchedules = new ArrayList<>();
+ List<VMScheduleVO> vmScheduleVOS =
vmScheduleDao.findByVm(cmd.getVmId());
+ for (VMScheduleVO vmSchedule : vmScheduleVOS) {
+ vmSchedules.add(vmSchedule);
+ }
+ return vmSchedules;
+ }
+
+ List<? extends VMScheduleVO> vmSchedules = vmScheduleDao.listAll();
+ return ListUtils.toListOfInterface(vmSchedules);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_DELETE,
eventDescription = "deleting VM Schedule")
+ public boolean deleteVMSchedule(Long vmScheduleId) {
+ VMSchedule vmSchedule = vmScheduleDao.findById(vmScheduleId);
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + vmScheduleId);
+ }
+
+ return vmScheduleDao.remove(vmSchedule.getId());
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_ENABLE,
eventDescription = "enable VM Schedule")
+ public boolean enableVMSchedule(Long vmScheduleId) {
+ VMScheduleVO vmSchedule = vmScheduleDao.findById(vmScheduleId);
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + vmScheduleId);
+ }
+
+ vmSchedule.setState(VMSchedule.State.ENABLED);
+ return vmScheduleDao.update(vmSchedule.getId(), vmSchedule);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_DISABLE,
eventDescription = "disable VM Schedule")
+ public boolean disableVMSchedule(Long vmScheduleId) {
+ VMScheduleVO vmSchedule = vmScheduleDao.findById(vmScheduleId);
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + vmScheduleId);
+ }
+
+ vmSchedule.setState(VMSchedule.State.DISABLED);
+ return vmScheduleDao.update(vmSchedule.getId(), vmSchedule);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_UPDATE,
eventDescription = "update VM Schedule")
+ public VMSchedule updateVMSchedule(UpdateVMScheduleCmd cmd) {
+ VMScheduleVO vmSchedule = vmScheduleDao.findById(cmd.getId());
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + cmd.getId());
+ }
+ String description = cmd.getDescription();
+ String schedule = cmd.getSchedule();
+ String intervalType = cmd.getIntervalType();
+ String action = cmd.getAction();
+ String tag = cmd.getTag();
+ String timezone = cmd.getTimezone();
+
+ if (vmSchedule.getState() == VMSchedule.State.DISABLED) {
+ if (description != null)
+ vmSchedule.setDescription(description);
+ if(schedule != null)
+ vmSchedule.setSchedule(schedule);
+ if(intervalType != null)
+ vmSchedule.setScheduleType(intervalType);
+ if (action != null)
+ vmSchedule.setAction(action);
+ if (tag != null)
+ vmSchedule.setTag(tag);
+ if (timezone != null)
+ vmSchedule.setTimezone(timezone);
+
+ vmScheduleDao.update(vmSchedule.getId(), vmSchedule);
+ } else {
+ throw new InvalidParameterValueException("Disable the state of VM
Schedule before updating it");
+ }
+ return vmSchedule;
+ }
+
+ public void poll(final Date timestamp) {
+ currentTimestamp = timestamp;
+ GlobalLock scanLock = GlobalLock.getInternLock("vm.poll");
+
+ try {
+ if (scanLock.lock(5)) {
+ try {
+ checkStatusOfCurrentlyExecutingVMs();
+ } finally {
+ scanLock.unlock();
+ }
+ }
+ } finally {
+ scanLock.releaseRef();
+ }
+
+ scanLock = GlobalLock.getInternLock("vm.poll");
+ try {
+ if (scanLock.lock(5)) {
+ try {
+ scheduleVMs();
+ } finally {
+ scanLock.unlock();
+ }
+ }
+ } finally {
+ scanLock.releaseRef();
+ }
+ }
+
+ @DB
+ public void scheduleVMs() {
+ String displayTime =
DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
+ LOGGER.debug("VM poll is being called at " + displayTime);
+
+ final List<VMScheduleVO> vmsToBeExecuted = vmScheduleDao.listAll();
+ for (final VMScheduleVO vmSchedule : vmsToBeExecuted) {
+ final long timeDifference =
DateUtil.getTimeDifference(vmSchedule.getScheduledTimestamp(),
currentTimestamp);
+
+ if (timeDifference <= 1) {
+ final Long vmScheduleId = vmSchedule.getId();
+ final Long vmId = vmSchedule.getVmId();
+
+ final VMInstanceVO vm = vmInstanceDao.findById(vmId);
+ if (vm == null) {
+ vmScheduleDao.remove(vmScheduleId);
+ continue;
+ }
+
+ if (LOGGER.isDebugEnabled()) {
+ final Date scheduledTimestamp =
vmSchedule.getScheduledTimestamp();
+ displayTime =
DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
+ LOGGER.debug(String.format("Scheduling for VM [ID: %s,
name: %s] schedule id: [%s] at [%s].",
+ vm.getId(), vm.getInstanceName(),
vmSchedule.getId(), displayTime));
+ }
+
+ VMScheduleVO tmpVMScheduleVO = null;
+
+ try {
+ if (vmSchedule.getState() == VMSchedule.State.ENABLED) {
+ tmpVMScheduleVO =
vmScheduleDao.acquireInLockTable(vmScheduleId);
+ Long jobId = performActionOnVM(vmSchedule.getAction(),
vm, vmSchedule);
+ if (jobId != null) {
+ tmpVMScheduleVO .setAsyncJobId(jobId);
+ vmScheduleDao.update(vmScheduleId,
tmpVMScheduleVO);
+ }
+ scheduleNextVMJob(vmSchedule);
+ }
+ } catch (Exception e) {
+ LOGGER.error(String.format("Scheduling VM failed due to:
[%s].", e.getMessage()), e);
+ } finally {
+ if (tmpVMScheduleVO != null) {
+ vmScheduleDao.releaseFromLockTable(vmScheduleId);
+ }
+ }
+ }
+ }
+ }
+
+ private Map<String, String> setVMTag(VMScheduleVO vmSchedule) {
+ Map<String,String> Tag = new HashMap<>();
+ if (!vmSchedule.getTag().isEmpty()) {
+ Tag.put("ScheduleMessage" + currentTimestamp, vmSchedule.getTag());
+ } else {
+ String tagValue = String.format("VM {" + vmSchedule.getAction() +
'}');
+ Tag.put("ScheduleMessage" + currentTimestamp, tagValue);
+ }
+ return Tag;
+ }
+
+ protected void createTagForVMInstance(Map<String, String> tag,
VMInstanceVO vmInstance) {
+ if (MapUtils.isNotEmpty(tag)) {
+
taggedResourceService.createTags(Collections.singletonList(vmInstance.getUuid()),
+ ResourceTag.ResourceObjectType.UserVm, tag, null);
+ }
+ }
+
+ private Long setAsyncJobForVMSchedule(VMInstanceVO vmInstance, Long
eventId) {
+ Long jobId;
+ final Map<String, String> params = new HashMap<>();
+ params.put(ApiConstants.VIRTUAL_MACHINE_ID, "" + vmInstance.getId());
+ params.put("ctxUserId", "1");
+ params.put("ctxAccountId", "" + vmInstance.getAccountId());
+ params.put("ctxEventId", String.valueOf(eventId));
+ params.put("ctxHostId", "" + vmInstance.getHostId());
+ params.put("id", "" + vmInstance.getId());
+
+ AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM,
vmInstance.getAccountId(), VirtualMachineManager.class.getName(),
+ ApiGsonHelper.getBuilder().create().toJson(params),
vmInstance.getId(), null, null);
+ job.setDispatcher(asyncJobDispatcher.getName());
+ jobId = asyncJobManager.submitAsyncJob(job);
+
+ return jobId;
+ }
+
+ private Long performActionOnVM(String action, VMInstanceVO vmInstance,
VMScheduleVO vmSchedule) throws ResourceUnavailableException,
InsufficientCapacityException {
Review Comment:
shouldn't `String action` be an enum instead? i.e. `VmSchedule.Action action`
##########
test/integration/smoke/test_vm_schedule.py:
##########
@@ -0,0 +1,177 @@
+# 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.
+
+# Import Local Modules
+from nose.plugins.attrib import attr
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.utils import random_gen, cleanup_resources, validateList,
is_snapshot_on_nfs, isAlmostEqual
+from marvin.cloudstackAPI import (deleteVMSchedule,
+ createVMSchedule,
+ listVMSchedules,
+ updateVMSchedule,
+ enableVMSchedule,
+ disableVMSchedule)
+
+from marvin.lib.base import * # import all resources like Account
+from marvin.lib.common import * # import all utility methods like get_zone etc.
+
+import time
+import os
+
+class Services:
+
+ """Test VM Schedule Services
+ """
+
+ def __init__(self):
+ self.services = {
+ "domain": {
+ "name": "Domain",
+ },
+ "vm_schedule": {
+ "virtualmachineid" : 9,
+ "description": "start vm",
+ "action": "start",
+ "schedule": "30",
+ "intervaltype": "HOURLY",
+ "tag": "hello",
+ "timezone": "Asia/Kolkata"
+ }
+ }
+
+class TestVmSchedule(cloudstackTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ testClient = super(TestVmSchedule, cls).getClsTestClient()
+ cls.apiclient = testClient.getApiClient()
+ cls.services = testClient.getParsedTestDataConfig()
+
+ # Get attributes
+ cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
+ cls.domain = get_domain(cls.apiclient)
+ cls.hypervisor = testClient.getHypervisorInfo()
+
+ cls.cleanup = []
+
+ template = get_suitable_test_template(
+ cls.apiclient,
+ cls.zone.id,
+ cls.services["ostype"],
+ cls.hypervisor
+ )
+
+ if template == FAILED:
+ assert False, "get_suitable_test_template() failed to return
template\
+ with description %s" % cls.services["ostype"]
+
+ cls.services["domainid"] = cls.domain.id
+ cls.services["small"]["zoneid"] = cls.zone.id
+ cls.services["templates"]["ostypeid"] = template.ostypeid
+ cls.services["zoneid"] = cls.zone.id
+
+ # Create VMs, NAT Rules etc
+ cls.account = Account.create(
+ cls.apiclient,
+ cls.services["account"],
+ domainid=cls.domain.id
+ )
+ cls.cleanup.append(cls.account)
+
+ cls.service_offering = ServiceOffering.create(
+ cls.apiclient,
+ cls.services["service_offerings"]["tiny"]
+ )
+ cls.cleanup.append(cls.service_offering)
+
+ cls.virtual_machine = VirtualMachine.create(
+ cls.apiclient,
+ cls.services["small"],
+ templateid=template.id,
+ accountid=cls.account.name,
+ domainid=cls.account.domainid,
+ serviceofferingid=cls.service_offering.id,
+ mode=cls.zone.networktype
+ )
+ # cls.cleanup.append(cls.virtual_machine)
Review Comment:
why code in comment? take it our or uncomment, I'd say
##########
server/src/main/java/com/cloud/vm/schedule/VMScheduleManagerImpl.java:
##########
@@ -0,0 +1,598 @@
+// 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 com.cloud.vm.schedule;
+
+import com.cloud.alert.AlertManager;
+import com.cloud.api.ApiDispatcher;
+import com.cloud.api.ApiGsonHelper;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.server.ResourceTag;
+import com.cloud.user.User;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.ListUtils;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.db.DB;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionStatus;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.VirtualMachineProfile;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.vm.schedule.dao.VMScheduleDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.user.vmschedule.CreateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.ListVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.UpdateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DeleteVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.EnableVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DisableVMScheduleCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+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.managed.context.ManagedContextTimerTask;
+import org.apache.cloudstack.poll.BackgroundPollManager;
+import org.apache.commons.collections.MapUtils;
+import org.apache.log4j.Logger;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.server.TaggedResourceService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.TimerTask;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.TimeZone;
+import java.util.Collections;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class VMScheduleManagerImpl extends ManagerBase implements
VMScheduleManager, Configurable, PluggableService {
+ public static final Logger LOGGER =
Logger.getLogger(VMScheduleManagerImpl.class);
+
+ @Inject
+ private VMScheduleDao vmScheduleDao;
+
+ @Inject
+ private VirtualMachineManager vmManager;
+
+ @Inject
+ private BackgroundPollManager backgroundPollManager;
+
+ @Inject
+ private AsyncJobManager asyncJobManager;
+
+ @Inject
+ private VMInstanceDao vmInstanceDao;
+
+ @Inject
+ private ApiDispatcher apiDispatcher;
+
+ private AsyncJobDispatcher asyncJobDispatcher;
+
+ @Inject
+ public TaggedResourceService taggedResourceService;
+
+ private Date currentTimestamp;
+
+ @Inject
+ private AlertManager alertManager;
+
+ private ScheduledExecutorService backgroundPollTaskScheduler;
+
+ private volatile boolean isConfiguredAndStarted = false;
+
+ private static final ConfigKey<Boolean> EnableVMSchedulerInterval = new
ConfigKey<>("Advanced", Boolean.class,
+ "vm.scheduler.enable", "false",
+ "Enable the VMScheduler Interval to schedule tasks on VM.",
false);
+
+ private static final ConfigKey<Integer> VMSchedulerInterval = new
ConfigKey<>("Advanced", Integer.class,
+ "vm.scheduler.interval", "60",
+ "The interval at which VM Scheduler runs in milliseconds", false,
EnableVMSchedulerInterval.key());
+
+ @Override
+ public boolean start() {
+ if (isConfiguredAndStarted) {
+ return true;
+ }
+ currentTimestamp = new Date();
+ for (final VMScheduleVO vmSchedule : vmScheduleDao.listAll()) {
+ scheduleNextVMJob(vmSchedule);
+ }
+ backgroundPollTaskScheduler = Executors.newScheduledThreadPool(100,
new NamedThreadFactory("BackgroundTaskPollManager"));
+ final TimerTask vmPollTask = new ManagedContextTimerTask() {
+ @Override
+ protected void runInContext() {
+ try {
+ poll(new Date());
+ } catch (final Throwable t) {
+ LOGGER.warn("Catch throwable in vm scheduler ", t);
+ }
+ }
+ };
+ backgroundPollTaskScheduler.scheduleWithFixedDelay(vmPollTask,
VMSchedulerInterval.value() * 1000L, VMSchedulerInterval.value() * 1000L,
TimeUnit.MILLISECONDS);
+ isConfiguredAndStarted = true;
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ if (isConfiguredAndStarted) {
+ backgroundPollTaskScheduler.shutdown();
+ }
+ return true;
+ }
+
+ @Override
+ public String getConfigComponentName() {
+ return VMScheduleManager.class.getSimpleName();
+ }
+
+ @Override
+ public ConfigKey<?>[] getConfigKeys() {
+ return new ConfigKey<?>[] {
+ VMSchedulerInterval,
+ EnableVMSchedulerInterval
+ };
+ }
+
+ @Override
+ public boolean configure(final String name, final Map<String, Object>
params) throws ConfigurationException {
+ return true;
+ }
+
+ @Override
+ public List<Class<?>> getCommands() {
+ final List<Class<?>> cmdList = new ArrayList<>();
+ cmdList.add(CreateVMScheduleCmd.class);
+ cmdList.add(ListVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ cmdList.add(DeleteVMScheduleCmd.class);
+ cmdList.add(EnableVMScheduleCmd.class);
+ cmdList.add(DisableVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ return cmdList;
+ }
+
+ @Override
+ public VMSchedule findVMSchedule(Long id) {
+ if (id == null || id < 1L) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VMSchedule ID is invalid [%s]",
id));
+ return null;
+ }
+ }
+
+ VMSchedule vmSchedule = vmScheduleDao.findById(id);
+ if (vmSchedule == null) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VmSchedule ID not found [id=%s]",
id));
+ return null;
+ }
+ }
+
+ return vmSchedule;
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_CREATE,
eventDescription = "creating vm schedule", async = true)
+ public VMSchedule createVMSchedule(CreateVMScheduleCmd cmd) {
+ VMSchedule.State state = VMSchedule.State.DISABLED;
+ final String action = cmd.getAction();
+ final Long vmId = cmd.getVmId();
+ String intervalType = cmd.getIntervalType();
+ String scheduleString = cmd.getSchedule();
+ String description = cmd.getDescription();
+ String tag = cmd.getTag();
+ TimeZone timeZone = TimeZone.getTimeZone(cmd.getTimezone());
+ Boolean enable = cmd.getEnable();
+
+ if (enable) {
+ state = VMSchedule.State.ENABLED;
+ }
+
+ if (intervalType.equals("hourly"))
+ intervalType = "HOURLY";
+
+ if (intervalType.equals("daily"))
+ intervalType = "DAILY";
+
+ if (intervalType.equals("weekly"))
+ intervalType = "WEEKLY";
+
+ if (intervalType.equals("monthly"))
+ intervalType = "MONTHLY";
Review Comment:
this can be condesed to `intervalType = intervalType.toUpperCase()`
##########
test/integration/smoke/test_vm_schedule.py:
##########
@@ -0,0 +1,177 @@
+# 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.
+
+# Import Local Modules
+from nose.plugins.attrib import attr
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.utils import random_gen, cleanup_resources, validateList,
is_snapshot_on_nfs, isAlmostEqual
+from marvin.cloudstackAPI import (deleteVMSchedule,
+ createVMSchedule,
+ listVMSchedules,
+ updateVMSchedule,
+ enableVMSchedule,
+ disableVMSchedule)
+
+from marvin.lib.base import * # import all resources like Account
+from marvin.lib.common import * # import all utility methods like get_zone etc.
+
+import time
+import os
+
+class Services:
+
+ """Test VM Schedule Services
+ """
+
+ def __init__(self):
+ self.services = {
+ "domain": {
+ "name": "Domain",
+ },
+ "vm_schedule": {
+ "virtualmachineid" : 9,
+ "description": "start vm",
+ "action": "start",
+ "schedule": "30",
+ "intervaltype": "HOURLY",
+ "tag": "hello",
+ "timezone": "Asia/Kolkata"
+ }
+ }
+
+class TestVmSchedule(cloudstackTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ testClient = super(TestVmSchedule, cls).getClsTestClient()
+ cls.apiclient = testClient.getApiClient()
+ cls.services = testClient.getParsedTestDataConfig()
+
+ # Get attributes
+ cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
+ cls.domain = get_domain(cls.apiclient)
+ cls.hypervisor = testClient.getHypervisorInfo()
+
+ cls.cleanup = []
+
+ template = get_suitable_test_template(
+ cls.apiclient,
+ cls.zone.id,
+ cls.services["ostype"],
+ cls.hypervisor
+ )
+
+ if template == FAILED:
+ assert False, "get_suitable_test_template() failed to return
template\
+ with description %s" % cls.services["ostype"]
+
+ cls.services["domainid"] = cls.domain.id
+ cls.services["small"]["zoneid"] = cls.zone.id
+ cls.services["templates"]["ostypeid"] = template.ostypeid
+ cls.services["zoneid"] = cls.zone.id
+
+ # Create VMs, NAT Rules etc
+ cls.account = Account.create(
+ cls.apiclient,
+ cls.services["account"],
+ domainid=cls.domain.id
+ )
+ cls.cleanup.append(cls.account)
+
+ cls.service_offering = ServiceOffering.create(
+ cls.apiclient,
+ cls.services["service_offerings"]["tiny"]
+ )
+ cls.cleanup.append(cls.service_offering)
+
+ cls.virtual_machine = VirtualMachine.create(
+ cls.apiclient,
+ cls.services["small"],
+ templateid=template.id,
+ accountid=cls.account.name,
+ domainid=cls.account.domainid,
+ serviceofferingid=cls.service_offering.id,
+ mode=cls.zone.networktype
+ )
+ # cls.cleanup.append(cls.virtual_machine)
+
+ # Class's tearDown method, runs only once per test class after test cases
+ @classmethod
+ def tearDownClass(cls):
+ try:
+ cleanup_resources(cls.apiclient, cls.cleanup)
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
Review Comment:
```suggestion
@classmethod
def tearDownClass(cls):
super(TestVmSchedule, cls).tearDownClass()
```
##########
api/src/main/java/org/apache/cloudstack/api/ApiConstants.java:
##########
@@ -1020,6 +1020,16 @@ public class ApiConstants {
public static final String PRIVATE_MTU = "privatemtu";
public static final String MTU = "mtu";
public static final String LIST_APIS = "listApis";
+ public static final String VM_SCHEDULE_DESCRIPTION = "description";
Review Comment:
I don“t see this fixed yet.
##########
usage/pom.xml:
##########
@@ -117,19 +117,6 @@
</filterchain>
</copy>
</target>
- <target>
- <copy overwrite="true"
todir="${basedir}/target/transformed">
- <fileset dir="${basedir}/conf">
- <include name="*.in" />
- </fileset>
- <globmapper from="*.in" to="*" />
- <filterchain>
- <filterreader
classname="org.apache.tools.ant.filters.ReplaceTokens">
- <param type="propertiesfile"
value="${cs.replace.properties}" />
- </filterreader>
- </filterchain>
- </copy>
- </target>
Review Comment:
why does this need removing?
##########
api/src/main/java/org/apache/cloudstack/api/command/user/vmschedule/CreateVMScheduleCmd.java:
##########
@@ -0,0 +1,73 @@
+// 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.api.command.user.vmschedule;
+
+import com.cloud.event.EventTypes;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.user.Account;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.BaseAsyncCreateCmd;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.response.VMScheduleResponse;
+import org.apache.log4j.Logger;
+
+@APICommand(name = CreateVMScheduleCmd.APINAME,
+ description = "Creates Schedule for a VM",
+ responseObject = VMScheduleResponse.class,
+ since = "4.18.0",
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin,
RoleType.DomainAdmin, RoleType.User})
+public class CreateVMScheduleCmd extends BaseAsyncCreateCmd {
+ public static final String APINAME = "createVMSchedule";
+ public static final Logger s_logger =
Logger.getLogger(CreateVMScheduleCmd.class.getName());
+
+ // ///////////////////////////////////////////////////
+ // ////////////// API parameters /////////////////////
+ // ///////////////////////////////////////////////////
+
+
+ @Override
+ public void create() throws ResourceAllocationException {
+
+ }
+
+ @Override
+ public void execute() {
+
+ }
+
+ @Override
+ public String getEventType() {
+ return EventTypes.EVENT_VMSCHEDULE_CREATE;
+ }
+
+ @Override
+ public String getEventDescription() {
+ return "creating vm schedule";
+ }
+
+ @Override
+ public String getCommandName() {
+ return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+ }
Review Comment:
will you remove this call, as @GutoVeronezi advises @rahulbcn27 ?
##########
test/integration/smoke/test_vm_schedule.py:
##########
@@ -0,0 +1,177 @@
+# 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.
+
+# Import Local Modules
+from nose.plugins.attrib import attr
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.lib.utils import random_gen, cleanup_resources, validateList,
is_snapshot_on_nfs, isAlmostEqual
+from marvin.cloudstackAPI import (deleteVMSchedule,
+ createVMSchedule,
+ listVMSchedules,
+ updateVMSchedule,
+ enableVMSchedule,
+ disableVMSchedule)
+
+from marvin.lib.base import * # import all resources like Account
+from marvin.lib.common import * # import all utility methods like get_zone etc.
+
+import time
+import os
+
+class Services:
+
+ """Test VM Schedule Services
+ """
+
+ def __init__(self):
+ self.services = {
+ "domain": {
+ "name": "Domain",
+ },
+ "vm_schedule": {
+ "virtualmachineid" : 9,
+ "description": "start vm",
+ "action": "start",
+ "schedule": "30",
+ "intervaltype": "HOURLY",
+ "tag": "hello",
+ "timezone": "Asia/Kolkata"
+ }
+ }
+
+class TestVmSchedule(cloudstackTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ testClient = super(TestVmSchedule, cls).getClsTestClient()
+ cls.apiclient = testClient.getApiClient()
+ cls.services = testClient.getParsedTestDataConfig()
+
+ # Get attributes
+ cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
+ cls.domain = get_domain(cls.apiclient)
+ cls.hypervisor = testClient.getHypervisorInfo()
+
+ cls.cleanup = []
+
+ template = get_suitable_test_template(
+ cls.apiclient,
+ cls.zone.id,
+ cls.services["ostype"],
+ cls.hypervisor
+ )
+
+ if template == FAILED:
+ assert False, "get_suitable_test_template() failed to return
template\
+ with description %s" % cls.services["ostype"]
+
+ cls.services["domainid"] = cls.domain.id
+ cls.services["small"]["zoneid"] = cls.zone.id
+ cls.services["templates"]["ostypeid"] = template.ostypeid
+ cls.services["zoneid"] = cls.zone.id
+
+ # Create VMs, NAT Rules etc
+ cls.account = Account.create(
+ cls.apiclient,
+ cls.services["account"],
+ domainid=cls.domain.id
+ )
+ cls.cleanup.append(cls.account)
+
+ cls.service_offering = ServiceOffering.create(
+ cls.apiclient,
+ cls.services["service_offerings"]["tiny"]
+ )
+ cls.cleanup.append(cls.service_offering)
+
+ cls.virtual_machine = VirtualMachine.create(
+ cls.apiclient,
+ cls.services["small"],
+ templateid=template.id,
+ accountid=cls.account.name,
+ domainid=cls.account.domainid,
+ serviceofferingid=cls.service_offering.id,
+ mode=cls.zone.networktype
+ )
+ # cls.cleanup.append(cls.virtual_machine)
+
+ # Class's tearDown method, runs only once per test class after test cases
+ @classmethod
+ def tearDownClass(cls):
+ try:
+ cleanup_resources(cls.apiclient, cls.cleanup)
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
+
+ # Define setUp that runs before each test case
+ def setUp(self):
+ # Define probes here
+ # Probe: apiclient to make API calls
+ self.apiclient = self.testClient.getApiClient()
+ # Probe: DB client to query the database
+ self.dbclient = self.testClient.getDbConnection()
+ # Get hypervisor detail
+ self.hypervisor = self.testClient.getHypervisorInfo()
+ # Command to get the default test_data config
+ self.services = self.testClient.getParsedTestDataConfig()
+ # List to hold any resources requiring cleanup
+ self.cleanup = []
+
+ # Define tearDown that runs after each test case
+ def tearDown(self):
+ try:
+ cleanup_resources(self.apiclient, self.cleanup)
+ except Exception as e:
+ raise Exception("Warning: Exception during cleanup : %s" % e)
Review Comment:
```suggestion
def tearDown(self):
super(TestVmSchedule, self).tearDown()
```
##########
server/src/main/java/com/cloud/vm/schedule/VMScheduleManagerImpl.java:
##########
@@ -0,0 +1,598 @@
+// 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 com.cloud.vm.schedule;
+
+import com.cloud.alert.AlertManager;
+import com.cloud.api.ApiDispatcher;
+import com.cloud.api.ApiGsonHelper;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.server.ResourceTag;
+import com.cloud.user.User;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.ListUtils;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.db.DB;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionStatus;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.VirtualMachineProfile;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.vm.schedule.dao.VMScheduleDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.user.vmschedule.CreateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.ListVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.UpdateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DeleteVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.EnableVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DisableVMScheduleCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+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.managed.context.ManagedContextTimerTask;
+import org.apache.cloudstack.poll.BackgroundPollManager;
+import org.apache.commons.collections.MapUtils;
+import org.apache.log4j.Logger;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.server.TaggedResourceService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.TimerTask;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.TimeZone;
+import java.util.Collections;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class VMScheduleManagerImpl extends ManagerBase implements
VMScheduleManager, Configurable, PluggableService {
+ public static final Logger LOGGER =
Logger.getLogger(VMScheduleManagerImpl.class);
+
+ @Inject
+ private VMScheduleDao vmScheduleDao;
+
+ @Inject
+ private VirtualMachineManager vmManager;
+
+ @Inject
+ private BackgroundPollManager backgroundPollManager;
+
+ @Inject
+ private AsyncJobManager asyncJobManager;
+
+ @Inject
+ private VMInstanceDao vmInstanceDao;
+
+ @Inject
+ private ApiDispatcher apiDispatcher;
+
+ private AsyncJobDispatcher asyncJobDispatcher;
+
+ @Inject
+ public TaggedResourceService taggedResourceService;
+
+ private Date currentTimestamp;
+
+ @Inject
+ private AlertManager alertManager;
+
+ private ScheduledExecutorService backgroundPollTaskScheduler;
+
+ private volatile boolean isConfiguredAndStarted = false;
+
+ private static final ConfigKey<Boolean> EnableVMSchedulerInterval = new
ConfigKey<>("Advanced", Boolean.class,
+ "vm.scheduler.enable", "false",
+ "Enable the VMScheduler Interval to schedule tasks on VM.",
false);
+
+ private static final ConfigKey<Integer> VMSchedulerInterval = new
ConfigKey<>("Advanced", Integer.class,
+ "vm.scheduler.interval", "60",
+ "The interval at which VM Scheduler runs in milliseconds", false,
EnableVMSchedulerInterval.key());
+
+ @Override
+ public boolean start() {
+ if (isConfiguredAndStarted) {
+ return true;
+ }
+ currentTimestamp = new Date();
+ for (final VMScheduleVO vmSchedule : vmScheduleDao.listAll()) {
+ scheduleNextVMJob(vmSchedule);
+ }
+ backgroundPollTaskScheduler = Executors.newScheduledThreadPool(100,
new NamedThreadFactory("BackgroundTaskPollManager"));
+ final TimerTask vmPollTask = new ManagedContextTimerTask() {
+ @Override
+ protected void runInContext() {
+ try {
+ poll(new Date());
+ } catch (final Throwable t) {
+ LOGGER.warn("Catch throwable in vm scheduler ", t);
+ }
+ }
+ };
+ backgroundPollTaskScheduler.scheduleWithFixedDelay(vmPollTask,
VMSchedulerInterval.value() * 1000L, VMSchedulerInterval.value() * 1000L,
TimeUnit.MILLISECONDS);
+ isConfiguredAndStarted = true;
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ if (isConfiguredAndStarted) {
+ backgroundPollTaskScheduler.shutdown();
+ }
+ return true;
+ }
+
+ @Override
+ public String getConfigComponentName() {
+ return VMScheduleManager.class.getSimpleName();
+ }
+
+ @Override
+ public ConfigKey<?>[] getConfigKeys() {
+ return new ConfigKey<?>[] {
+ VMSchedulerInterval,
+ EnableVMSchedulerInterval
+ };
+ }
+
+ @Override
+ public boolean configure(final String name, final Map<String, Object>
params) throws ConfigurationException {
+ return true;
+ }
+
+ @Override
+ public List<Class<?>> getCommands() {
+ final List<Class<?>> cmdList = new ArrayList<>();
+ cmdList.add(CreateVMScheduleCmd.class);
+ cmdList.add(ListVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ cmdList.add(DeleteVMScheduleCmd.class);
+ cmdList.add(EnableVMScheduleCmd.class);
+ cmdList.add(DisableVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ return cmdList;
+ }
+
+ @Override
+ public VMSchedule findVMSchedule(Long id) {
+ if (id == null || id < 1L) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VMSchedule ID is invalid [%s]",
id));
+ return null;
+ }
+ }
+
+ VMSchedule vmSchedule = vmScheduleDao.findById(id);
+ if (vmSchedule == null) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VmSchedule ID not found [id=%s]",
id));
+ return null;
+ }
+ }
+
+ return vmSchedule;
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_CREATE,
eventDescription = "creating vm schedule", async = true)
+ public VMSchedule createVMSchedule(CreateVMScheduleCmd cmd) {
+ VMSchedule.State state = VMSchedule.State.DISABLED;
+ final String action = cmd.getAction();
+ final Long vmId = cmd.getVmId();
+ String intervalType = cmd.getIntervalType();
+ String scheduleString = cmd.getSchedule();
+ String description = cmd.getDescription();
+ String tag = cmd.getTag();
+ TimeZone timeZone = TimeZone.getTimeZone(cmd.getTimezone());
+ Boolean enable = cmd.getEnable();
+
+ if (enable) {
+ state = VMSchedule.State.ENABLED;
+ }
+
+ if (intervalType.equals("hourly"))
+ intervalType = "HOURLY";
+
+ if (intervalType.equals("daily"))
+ intervalType = "DAILY";
+
+ if (intervalType.equals("weekly"))
+ intervalType = "WEEKLY";
+
+ if (intervalType.equals("monthly"))
+ intervalType = "MONTHLY";
+
+ if (description == null) {
+ description = action + " VM Instance with vmId:" + vmId + " " +
intervalType + " at " + scheduleString;
+ }
+
+ if (tag == null) {
+ tag = String.format("VM " + cmd.getAction() +
cmd.getIntervalType() + cmd.getSchedule());
+ }
+
+ if (timeZone == null) {
+ timeZone = TimeZone.getDefault();
+ }
+
+ if (intervalType == null) {
+ throw new CloudRuntimeException("Invalid interval type provided");
+ }
+
+ final String timezoneId = timeZone.getID();
+ if (!timezoneId.equals(cmd.getTimezone())) {
+ LOGGER.warn("Using timezone: " + timezoneId + " for running this
schedule as an equivalent of " + cmd.getTimezone());
+ }
+
+ Date nextDateTime = null;
+ try {
+ nextDateTime =
DateUtil.getNextRunTime(DateUtil.IntervalType.valueOf(intervalType),
cmd.getSchedule(), timezoneId, null);
+ } catch (Exception e) {
+ throw new InvalidParameterValueException("Invalid schedule: " +
cmd.getSchedule() + " for interval type: " + cmd.getIntervalType());
+ }
+
+ return vmScheduleDao.persist(new VMScheduleVO(vmId, description,
action, intervalType, scheduleString,
+ timezoneId, nextDateTime, tag, state, 1L));
+ }
+
+ @Override
+ public List<VMSchedule> listVMSchedules(ListVMScheduleCmd cmd) {
+ if (cmd.getId() != null) {
+ VMSchedule vmSchedule = findVMSchedule(cmd.getId());
+ List<VMSchedule> arr = new ArrayList<>();
+ arr.add(vmSchedule);
+ return arr;
+ }
+
+ if (cmd.getVmId() != null) {
+ List<VMSchedule> vmSchedules = new ArrayList<>();
+ List<VMScheduleVO> vmScheduleVOS =
vmScheduleDao.findByVm(cmd.getVmId());
+ for (VMScheduleVO vmSchedule : vmScheduleVOS) {
+ vmSchedules.add(vmSchedule);
+ }
+ return vmSchedules;
+ }
+
+ List<? extends VMScheduleVO> vmSchedules = vmScheduleDao.listAll();
+ return ListUtils.toListOfInterface(vmSchedules);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_DELETE,
eventDescription = "deleting VM Schedule")
+ public boolean deleteVMSchedule(Long vmScheduleId) {
+ VMSchedule vmSchedule = vmScheduleDao.findById(vmScheduleId);
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + vmScheduleId);
+ }
+
+ return vmScheduleDao.remove(vmSchedule.getId());
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_ENABLE,
eventDescription = "enable VM Schedule")
+ public boolean enableVMSchedule(Long vmScheduleId) {
+ VMScheduleVO vmSchedule = vmScheduleDao.findById(vmScheduleId);
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + vmScheduleId);
+ }
+
+ vmSchedule.setState(VMSchedule.State.ENABLED);
+ return vmScheduleDao.update(vmSchedule.getId(), vmSchedule);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_DISABLE,
eventDescription = "disable VM Schedule")
+ public boolean disableVMSchedule(Long vmScheduleId) {
+ VMScheduleVO vmSchedule = vmScheduleDao.findById(vmScheduleId);
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + vmScheduleId);
+ }
+
+ vmSchedule.setState(VMSchedule.State.DISABLED);
+ return vmScheduleDao.update(vmSchedule.getId(), vmSchedule);
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_UPDATE,
eventDescription = "update VM Schedule")
+ public VMSchedule updateVMSchedule(UpdateVMScheduleCmd cmd) {
+ VMScheduleVO vmSchedule = vmScheduleDao.findById(cmd.getId());
+ if (vmSchedule == null) {
+ throw new InvalidParameterValueException("unable to find the vm
schedule with id " + cmd.getId());
+ }
+ String description = cmd.getDescription();
+ String schedule = cmd.getSchedule();
+ String intervalType = cmd.getIntervalType();
+ String action = cmd.getAction();
+ String tag = cmd.getTag();
+ String timezone = cmd.getTimezone();
+
+ if (vmSchedule.getState() == VMSchedule.State.DISABLED) {
+ if (description != null)
+ vmSchedule.setDescription(description);
+ if(schedule != null)
+ vmSchedule.setSchedule(schedule);
+ if(intervalType != null)
+ vmSchedule.setScheduleType(intervalType);
+ if (action != null)
+ vmSchedule.setAction(action);
+ if (tag != null)
+ vmSchedule.setTag(tag);
+ if (timezone != null)
+ vmSchedule.setTimezone(timezone);
+
+ vmScheduleDao.update(vmSchedule.getId(), vmSchedule);
+ } else {
+ throw new InvalidParameterValueException("Disable the state of VM
Schedule before updating it");
+ }
+ return vmSchedule;
+ }
+
+ public void poll(final Date timestamp) {
+ currentTimestamp = timestamp;
+ GlobalLock scanLock = GlobalLock.getInternLock("vm.poll");
+
+ try {
+ if (scanLock.lock(5)) {
+ try {
+ checkStatusOfCurrentlyExecutingVMs();
+ } finally {
+ scanLock.unlock();
+ }
+ }
+ } finally {
+ scanLock.releaseRef();
+ }
+
+ scanLock = GlobalLock.getInternLock("vm.poll");
+ try {
+ if (scanLock.lock(5)) {
+ try {
+ scheduleVMs();
+ } finally {
+ scanLock.unlock();
+ }
+ }
+ } finally {
+ scanLock.releaseRef();
+ }
+ }
+
+ @DB
+ public void scheduleVMs() {
+ String displayTime =
DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, currentTimestamp);
+ LOGGER.debug("VM poll is being called at " + displayTime);
+
+ final List<VMScheduleVO> vmsToBeExecuted = vmScheduleDao.listAll();
+ for (final VMScheduleVO vmSchedule : vmsToBeExecuted) {
+ final long timeDifference =
DateUtil.getTimeDifference(vmSchedule.getScheduledTimestamp(),
currentTimestamp);
+
+ if (timeDifference <= 1) {
+ final Long vmScheduleId = vmSchedule.getId();
+ final Long vmId = vmSchedule.getVmId();
+
+ final VMInstanceVO vm = vmInstanceDao.findById(vmId);
+ if (vm == null) {
+ vmScheduleDao.remove(vmScheduleId);
+ continue;
+ }
+
+ if (LOGGER.isDebugEnabled()) {
+ final Date scheduledTimestamp =
vmSchedule.getScheduledTimestamp();
+ displayTime =
DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, scheduledTimestamp);
+ LOGGER.debug(String.format("Scheduling for VM [ID: %s,
name: %s] schedule id: [%s] at [%s].",
+ vm.getId(), vm.getInstanceName(),
vmSchedule.getId(), displayTime));
+ }
+
+ VMScheduleVO tmpVMScheduleVO = null;
+
+ try {
+ if (vmSchedule.getState() == VMSchedule.State.ENABLED) {
+ tmpVMScheduleVO =
vmScheduleDao.acquireInLockTable(vmScheduleId);
+ Long jobId = performActionOnVM(vmSchedule.getAction(),
vm, vmSchedule);
+ if (jobId != null) {
+ tmpVMScheduleVO .setAsyncJobId(jobId);
+ vmScheduleDao.update(vmScheduleId,
tmpVMScheduleVO);
+ }
+ scheduleNextVMJob(vmSchedule);
+ }
+ } catch (Exception e) {
+ LOGGER.error(String.format("Scheduling VM failed due to:
[%s].", e.getMessage()), e);
+ } finally {
+ if (tmpVMScheduleVO != null) {
+ vmScheduleDao.releaseFromLockTable(vmScheduleId);
+ }
+ }
+ }
+ }
+ }
+
+ private Map<String, String> setVMTag(VMScheduleVO vmSchedule) {
+ Map<String,String> Tag = new HashMap<>();
+ if (!vmSchedule.getTag().isEmpty()) {
+ Tag.put("ScheduleMessage" + currentTimestamp, vmSchedule.getTag());
+ } else {
+ String tagValue = String.format("VM {" + vmSchedule.getAction() +
'}');
+ Tag.put("ScheduleMessage" + currentTimestamp, tagValue);
+ }
+ return Tag;
+ }
+
+ protected void createTagForVMInstance(Map<String, String> tag,
VMInstanceVO vmInstance) {
+ if (MapUtils.isNotEmpty(tag)) {
+
taggedResourceService.createTags(Collections.singletonList(vmInstance.getUuid()),
+ ResourceTag.ResourceObjectType.UserVm, tag, null);
+ }
+ }
+
+ private Long setAsyncJobForVMSchedule(VMInstanceVO vmInstance, Long
eventId) {
+ Long jobId;
+ final Map<String, String> params = new HashMap<>();
+ params.put(ApiConstants.VIRTUAL_MACHINE_ID, "" + vmInstance.getId());
+ params.put("ctxUserId", "1");
+ params.put("ctxAccountId", "" + vmInstance.getAccountId());
+ params.put("ctxEventId", String.valueOf(eventId));
+ params.put("ctxHostId", "" + vmInstance.getHostId());
+ params.put("id", "" + vmInstance.getId());
+
+ AsyncJobVO job = new AsyncJobVO("", User.UID_SYSTEM,
vmInstance.getAccountId(), VirtualMachineManager.class.getName(),
+ ApiGsonHelper.getBuilder().create().toJson(params),
vmInstance.getId(), null, null);
+ job.setDispatcher(asyncJobDispatcher.getName());
+ jobId = asyncJobManager.submitAsyncJob(job);
+
+ return jobId;
+ }
+
+ private Long performActionOnVM(String action, VMInstanceVO vmInstance,
VMScheduleVO vmSchedule) throws ResourceUnavailableException,
InsufficientCapacityException {
+ Long jobId = null;
+ Map<String,String> vmTag;
+ Map<VirtualMachineProfile.Param,Object> params;
+ params = new HashMap();
+ params.put(VirtualMachineProfile.Param.BootIntoSetup, Boolean.TRUE);
+
+ switch (action) {
+ case "start":
+ if (vmInstance.getState() == VirtualMachine.State.Running) {
+ LOGGER.debug("Virtual Machine is already running" +
vmInstance.getId());
+ break;
+ }
+ final Long eventStartId =
ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM,
vmInstance.getAccountId(),
+ EventTypes.EVENT_VM_START, "Starting a VM for VM ID:"
+ vmInstance.getUuid(),
+ vmInstance.getId(),
ApiCommandResourceType.VirtualMachine.toString(),
+ true, 0);
+ vmManager.start(vmInstance.getUuid(), params);
+ vmTag = setVMTag(vmSchedule);
+ createTagForVMInstance(vmTag, vmInstance);
+ if (CallContext.current().getCallingUser().getEmail() != null)
{
+ String subject = "VM START";
+ String message = "VM started on " +
vmSchedule.getScheduleType() + " at " + vmSchedule.getSchedule();
+
alertManager.sendAlert(AlertManager.AlertType.ALERT_TYPE_VM_START,
vmInstance.getDataCenterId(),
+ vmInstance.getPodIdToDeployIn(),subject,message);
+ }
+ jobId = setAsyncJobForVMSchedule(vmInstance, eventStartId);
Review Comment:
I would like to see these blocks in separate methods for each type of action.
##########
api/src/main/java/org/apache/cloudstack/api/command/user/vmschedule/DisableVMScheduleCmd.java:
##########
@@ -0,0 +1,83 @@
+// 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.api.command.user.vmschedule;
+
+import com.cloud.event.EventTypes;
+import com.cloud.user.Account;
+import com.cloud.vm.schedule.VMSchedule;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.acl.SecurityChecker;
+import org.apache.cloudstack.api.ACL;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.api.response.VMScheduleResponse;
+import org.apache.log4j.Logger;
+
+@APICommand(name = DisableVMScheduleCmd.APINAME, description = "Updates a VM
Schedule", responseObject = SuccessResponse.class,
Review Comment:
I do not see an indetation problem here.
##########
server/src/main/java/com/cloud/vm/schedule/VMScheduleManagerImpl.java:
##########
@@ -0,0 +1,598 @@
+// 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 com.cloud.vm.schedule;
+
+import com.cloud.alert.AlertManager;
+import com.cloud.api.ApiDispatcher;
+import com.cloud.api.ApiGsonHelper;
+import com.cloud.event.ActionEvent;
+import com.cloud.event.ActionEventUtils;
+import com.cloud.event.EventTypes;
+import com.cloud.exception.InsufficientCapacityException;
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.ResourceUnavailableException;
+import com.cloud.server.ResourceTag;
+import com.cloud.user.User;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.ListUtils;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.db.DB;
+import com.cloud.utils.db.GlobalLock;
+import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.Transaction;
+import com.cloud.utils.db.TransactionStatus;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.VirtualMachineManager;
+import com.cloud.vm.VirtualMachineProfile;
+import com.cloud.vm.dao.VMInstanceDao;
+import com.cloud.vm.schedule.dao.VMScheduleDao;
+import org.apache.cloudstack.api.ApiCommandResourceType;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.command.user.vmschedule.CreateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.ListVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.UpdateVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DeleteVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.EnableVMScheduleCmd;
+import org.apache.cloudstack.api.command.user.vmschedule.DisableVMScheduleCmd;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+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.managed.context.ManagedContextTimerTask;
+import org.apache.cloudstack.poll.BackgroundPollManager;
+import org.apache.commons.collections.MapUtils;
+import org.apache.log4j.Logger;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import com.cloud.utils.db.TransactionCallback;
+import com.cloud.server.TaggedResourceService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.TimerTask;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.TimeZone;
+import java.util.Collections;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class VMScheduleManagerImpl extends ManagerBase implements
VMScheduleManager, Configurable, PluggableService {
+ public static final Logger LOGGER =
Logger.getLogger(VMScheduleManagerImpl.class);
+
+ @Inject
+ private VMScheduleDao vmScheduleDao;
+
+ @Inject
+ private VirtualMachineManager vmManager;
+
+ @Inject
+ private BackgroundPollManager backgroundPollManager;
+
+ @Inject
+ private AsyncJobManager asyncJobManager;
+
+ @Inject
+ private VMInstanceDao vmInstanceDao;
+
+ @Inject
+ private ApiDispatcher apiDispatcher;
+
+ private AsyncJobDispatcher asyncJobDispatcher;
+
+ @Inject
+ public TaggedResourceService taggedResourceService;
+
+ private Date currentTimestamp;
+
+ @Inject
+ private AlertManager alertManager;
+
+ private ScheduledExecutorService backgroundPollTaskScheduler;
+
+ private volatile boolean isConfiguredAndStarted = false;
+
+ private static final ConfigKey<Boolean> EnableVMSchedulerInterval = new
ConfigKey<>("Advanced", Boolean.class,
+ "vm.scheduler.enable", "false",
+ "Enable the VMScheduler Interval to schedule tasks on VM.",
false);
+
+ private static final ConfigKey<Integer> VMSchedulerInterval = new
ConfigKey<>("Advanced", Integer.class,
+ "vm.scheduler.interval", "60",
+ "The interval at which VM Scheduler runs in milliseconds", false,
EnableVMSchedulerInterval.key());
+
+ @Override
+ public boolean start() {
+ if (isConfiguredAndStarted) {
+ return true;
+ }
+ currentTimestamp = new Date();
+ for (final VMScheduleVO vmSchedule : vmScheduleDao.listAll()) {
+ scheduleNextVMJob(vmSchedule);
+ }
+ backgroundPollTaskScheduler = Executors.newScheduledThreadPool(100,
new NamedThreadFactory("BackgroundTaskPollManager"));
+ final TimerTask vmPollTask = new ManagedContextTimerTask() {
+ @Override
+ protected void runInContext() {
+ try {
+ poll(new Date());
+ } catch (final Throwable t) {
+ LOGGER.warn("Catch throwable in vm scheduler ", t);
+ }
+ }
+ };
+ backgroundPollTaskScheduler.scheduleWithFixedDelay(vmPollTask,
VMSchedulerInterval.value() * 1000L, VMSchedulerInterval.value() * 1000L,
TimeUnit.MILLISECONDS);
+ isConfiguredAndStarted = true;
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ if (isConfiguredAndStarted) {
+ backgroundPollTaskScheduler.shutdown();
+ }
+ return true;
+ }
+
+ @Override
+ public String getConfigComponentName() {
+ return VMScheduleManager.class.getSimpleName();
+ }
+
+ @Override
+ public ConfigKey<?>[] getConfigKeys() {
+ return new ConfigKey<?>[] {
+ VMSchedulerInterval,
+ EnableVMSchedulerInterval
+ };
+ }
+
+ @Override
+ public boolean configure(final String name, final Map<String, Object>
params) throws ConfigurationException {
+ return true;
+ }
+
+ @Override
+ public List<Class<?>> getCommands() {
+ final List<Class<?>> cmdList = new ArrayList<>();
+ cmdList.add(CreateVMScheduleCmd.class);
+ cmdList.add(ListVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ cmdList.add(DeleteVMScheduleCmd.class);
+ cmdList.add(EnableVMScheduleCmd.class);
+ cmdList.add(DisableVMScheduleCmd.class);
+ cmdList.add(UpdateVMScheduleCmd.class);
+ return cmdList;
+ }
+
+ @Override
+ public VMSchedule findVMSchedule(Long id) {
+ if (id == null || id < 1L) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VMSchedule ID is invalid [%s]",
id));
+ return null;
+ }
+ }
+
+ VMSchedule vmSchedule = vmScheduleDao.findById(id);
+ if (vmSchedule == null) {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace(String.format("VmSchedule ID not found [id=%s]",
id));
+ return null;
+ }
+ }
+
+ return vmSchedule;
+ }
+
+ @Override
+ @ActionEvent(eventType = EventTypes.EVENT_VMSCHEDULE_CREATE,
eventDescription = "creating vm schedule", async = true)
+ public VMSchedule createVMSchedule(CreateVMScheduleCmd cmd) {
+ VMSchedule.State state = VMSchedule.State.DISABLED;
+ final String action = cmd.getAction();
+ final Long vmId = cmd.getVmId();
+ String intervalType = cmd.getIntervalType();
+ String scheduleString = cmd.getSchedule();
+ String description = cmd.getDescription();
+ String tag = cmd.getTag();
+ TimeZone timeZone = TimeZone.getTimeZone(cmd.getTimezone());
+ Boolean enable = cmd.getEnable();
+
+ if (enable) {
+ state = VMSchedule.State.ENABLED;
+ }
+
+ if (intervalType.equals("hourly"))
+ intervalType = "HOURLY";
+
+ if (intervalType.equals("daily"))
+ intervalType = "DAILY";
+
+ if (intervalType.equals("weekly"))
+ intervalType = "WEEKLY";
+
+ if (intervalType.equals("monthly"))
+ intervalType = "MONTHLY";
Review Comment:
I wonder if we should introduce an enum for this btw.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]