diff --git a/api/src/com/cloud/vm/VirtualMachineProfile.java b/api/src/com/cloud/vm/VirtualMachineProfile.java index c44bbcc3475..d65b7b6be16 100644 --- a/api/src/com/cloud/vm/VirtualMachineProfile.java +++ b/api/src/com/cloud/vm/VirtualMachineProfile.java @@ -23,7 +23,6 @@ import com.cloud.agent.api.to.VolumeTO; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.offering.ServiceOffering; import com.cloud.template.VirtualMachineTemplate; -import com.cloud.template.VirtualMachineTemplate.BootloaderType; import com.cloud.user.Account; @@ -66,11 +65,6 @@ public interface VirtualMachineProfile { */ HypervisorType getHypervisorType(); - /** - * @return os to be run on the virtual machine. - */ - String getGuestOs(); - /** * @return template the virtual machine is based on. */ @@ -109,11 +103,6 @@ public interface VirtualMachineProfile { void addDisk(VolumeTO disk); - void setBootloader(BootloaderType type); - BootloaderType getBootloader(); - - String getOs(); - VirtualMachine.Type getType(); void setParameter(String name, Object value); diff --git a/core/src/com/cloud/vm/VMInstanceVO.java b/core/src/com/cloud/vm/VMInstanceVO.java index 1e24b5e52ac..07e8e1c16a7 100644 --- a/core/src/com/cloud/vm/VMInstanceVO.java +++ b/core/src/com/cloud/vm/VMInstanceVO.java @@ -134,6 +134,9 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject profile, DeployDestination dest, ReservationContext context) { + public boolean finalizeStart(Commands cmds, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) { CheckSshAnswer answer = (CheckSshAnswer)cmds.getAnswer("checkSsh"); if (!answer.getResult()) { s_logger.warn("Unable to ssh to the VM: " + answer.getDetails()); @@ -2521,4 +2521,8 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, VirtualMach } return findById(VirtualMachineName.getConsoleProxyId(name)); } + + @Override + public void finalizeStop(VirtualMachineProfile profile, long hostId, String reservationId) { + } } diff --git a/server/src/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/com/cloud/hypervisor/HypervisorGuruBase.java index 83d74fd6746..1498cb17996 100644 --- a/server/src/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/com/cloud/hypervisor/HypervisorGuruBase.java @@ -57,7 +57,7 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis ServiceOffering offering = vmProfile.getServiceOffering(); VirtualMachine vm = vmProfile.getVirtualMachine(); - VirtualMachineTO to = new VirtualMachineTO(vm.getId(), vm.getInstanceName(), vm.getType(), offering.getCpu(), offering.getSpeed(), offering.getRamSize() * 1024l * 1024l, offering.getRamSize() * 1024l * 1024l, null, vmProfile.getOs()); + VirtualMachineTO to = new VirtualMachineTO(vm.getId(), vm.getInstanceName(), vm.getType(), offering.getCpu(), offering.getSpeed(), offering.getRamSize() * 1024l * 1024l, offering.getRamSize() * 1024l * 1024l, null, null); to.setBootArgs(vmProfile.getBootArgs()); List nicProfiles = vmProfile.getNics(); diff --git a/server/src/com/cloud/hypervisor/XenServerGuru.java b/server/src/com/cloud/hypervisor/XenServerGuru.java index 15778db6a1f..1df3fb6e561 100644 --- a/server/src/com/cloud/hypervisor/XenServerGuru.java +++ b/server/src/com/cloud/hypervisor/XenServerGuru.java @@ -21,14 +21,18 @@ import javax.ejb.Local; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.GuestOSVO; import com.cloud.storage.Storage; +import com.cloud.storage.dao.GuestOSDao; import com.cloud.template.VirtualMachineTemplate; import com.cloud.template.VirtualMachineTemplate.BootloaderType; +import com.cloud.utils.component.Inject; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; @Local(value=HypervisorGuru.class) public class XenServerGuru extends HypervisorGuruBase implements HypervisorGuru { + @Inject GuestOSDao _guestOsDao; protected XenServerGuru() { super(); @@ -51,6 +55,10 @@ public class XenServerGuru extends HypervisorGuruBase implements HypervisorGuru VirtualMachineTO to = toVirtualMachineTO(vm); to.setBootloader(bt); + // Determine the VM's OS description + GuestOSVO guestOS = _guestOsDao.findById(vm.getVirtualMachine().getGuestOSId()); + to.setOs(guestOS.getDisplayName()); + return to; } } diff --git a/server/src/com/cloud/network/router/DomainRouterManagerImpl.java b/server/src/com/cloud/network/router/DomainRouterManagerImpl.java index 7b0a8b0fef1..c9ee5d6e130 100644 --- a/server/src/com/cloud/network/router/DomainRouterManagerImpl.java +++ b/server/src/com/cloud/network/router/DomainRouterManagerImpl.java @@ -2132,7 +2132,7 @@ public class DomainRouterManagerImpl implements DomainRouterManager, VirtualMach } @Override - public boolean processDeploymentResult(Commands cmds, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) { + public boolean finalizeStart(Commands cmds, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) { CheckSshAnswer answer = (CheckSshAnswer)cmds.getAnswer("checkSsh"); if (!answer.getResult()) { s_logger.warn("Unable to ssh to the VM: " + answer.getDetails()); @@ -2140,6 +2140,10 @@ public class DomainRouterManagerImpl implements DomainRouterManager, VirtualMach } return true; } + + @Override + public void finalizeStop(VirtualMachineProfile profile, long hostId, String reservationId) { + } @Override public RemoteAccessVpnVO startRemoteAccessVpn(RemoteAccessVpnVO vpnVO) throws ResourceUnavailableException { diff --git a/server/src/com/cloud/vm/ItWorkDaoImpl.java b/server/src/com/cloud/vm/ItWorkDaoImpl.java index 899fced1592..87ee53ae2fd 100644 --- a/server/src/com/cloud/vm/ItWorkDaoImpl.java +++ b/server/src/com/cloud/vm/ItWorkDaoImpl.java @@ -22,7 +22,7 @@ import javax.ejb.Local; import com.cloud.utils.db.GenericDaoBase; @Local(value=ItWorkDao.class) -public class ItWorkDaoImpl extends GenericDaoBase implements ItWorkDao { +public class ItWorkDaoImpl extends GenericDaoBase implements ItWorkDao { protected ItWorkDaoImpl() { super(); } diff --git a/server/src/com/cloud/vm/ItWorkVO.java b/server/src/com/cloud/vm/ItWorkVO.java index f104d791c73..a823c7be11b 100644 --- a/server/src/com/cloud/vm/ItWorkVO.java +++ b/server/src/com/cloud/vm/ItWorkVO.java @@ -32,7 +32,8 @@ import com.cloud.utils.db.GenericDao; @Table(name="op_it_work") public class ItWorkVO { enum Type { - Start; + Start, + Cleanup; } enum State { @@ -90,6 +91,10 @@ public class ItWorkVO { return type; } + public void setType(Type type) { + this.type = type; + } + public String getThreadName() { return threadName; } diff --git a/server/src/com/cloud/vm/MauriceMoss.java b/server/src/com/cloud/vm/MauriceMoss.java index 39a06cbb456..b9bc48a2a27 100644 --- a/server/src/com/cloud/vm/MauriceMoss.java +++ b/server/src/com/cloud/vm/MauriceMoss.java @@ -32,6 +32,8 @@ import com.cloud.agent.AgentManager; import com.cloud.agent.AgentManager.OnError; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Start2Command; +import com.cloud.agent.api.StopAnswer; +import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.manager.Commands; import com.cloud.cluster.ClusterManager; @@ -45,6 +47,8 @@ import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.domain.dao.DomainDao; +import com.cloud.event.EventTypes; +import com.cloud.event.EventVO; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; @@ -59,12 +63,10 @@ import com.cloud.network.NetworkManager; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; -import com.cloud.storage.GuestOSVO; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StorageManager; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume.VolumeType; -import com.cloud.storage.dao.GuestOSDao; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.user.Account; import com.cloud.user.User; @@ -79,6 +81,7 @@ import com.cloud.utils.component.Inject; import com.cloud.utils.db.DB; import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.ItWorkVO.Type; import com.cloud.vm.VirtualMachine.Event; import com.cloud.vm.dao.VMInstanceDao; @@ -92,7 +95,6 @@ public class MauriceMoss implements VmManager, ClusterManagerListener { @Inject private AgentManager _agentMgr; @Inject private VMInstanceDao _vmDao; @Inject private ServiceOfferingDao _offeringDao; - @Inject private GuestOSDao _guestOsDao; @Inject private VMTemplateDao _templateDao; @Inject private UserDao _userDao; @Inject private AccountDao _accountDao; @@ -130,9 +132,7 @@ public class MauriceMoss implements VmManager, ClusterManagerListener { s_logger.debug("Allocating entries for VM: " + vm); } - // Determine the VM's OS description - GuestOSVO guestOS = _guestOsDao.findById(vm.getGuestOSId()); - VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vm, guestOS.getDisplayName(), template, serviceOffering, owner, params); + VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vm, template, serviceOffering, owner, params); vm.setDataCenterId(plan.getDataCenterId()); if (plan.getPodId() != null) { @@ -286,8 +286,8 @@ public class MauriceMoss implements VmManager, ClusterManagerListener { VirtualMachineGuru vmGuru = (VirtualMachineGuru)_vmGurus.get(vm.getType()); - // Determine the VM's OS description - GuestOSVO guestOS = _guestOsDao.findById(vm.getGuestOSId()); + vm.setReservationId(work.getId()); + if (!_vmDao.updateIf(vm, Event.StartRequested, null)) { throw new ConcurrentOperationException("Unable to start vm " + vm + " due to concurrent operations"); } @@ -295,7 +295,7 @@ public class MauriceMoss implements VmManager, ClusterManagerListener { ExcludeList avoids = new ExcludeList(); int retry = _retry; while (retry-- != 0) { // It's != so that it can match -1. - VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vm, guestOS.getDisplayName(), template, offering, null, params); + VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vm, template, offering, null, params); DeployDestination dest = null; for (DeploymentPlanner planner : _planners) { dest = planner.plan(vmProfile, plan, avoids); @@ -334,7 +334,7 @@ public class MauriceMoss implements VmManager, ClusterManagerListener { vmGuru.finalizeDeployment(cmds, vmProfile, dest, context); try { Answer[] answers = _agentMgr.send(dest.getHost().getId(), cmds); - if (answers[0].getResult() && vmGuru.processDeploymentResult(cmds, vmProfile, dest, context)) { + if (answers[0].getResult() && vmGuru.finalizeStart(cmds, vmProfile, dest, context)) { if (!_vmDao.updateIf(vm, Event.OperationSucceeded, dest.getHost().getId())) { throw new CloudRuntimeException("Unable to transition to a new state."); } @@ -358,9 +358,93 @@ public class MauriceMoss implements VmManager, ClusterManagerListener { } @Override - public T stop(T vm, User user, Account account) throws AgentUnavailableException { - // TODO Auto-generated method stub - return null; + public boolean stop(T vm, User user, Account account) throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + State state = vm.getState(); + if (state == State.Stopped) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("VM is already stopped: " + vm); + } + return true; + } + + if (state == State.Creating || state == State.Destroyed || state == State.Expunging) { + s_logger.warn("Stopped called on " + vm.toString() + " but the state is " + state.toString()); + return true; + } + + if (!_vmDao.updateIf(vm, Event.StopRequested, vm.getHostId())) { + throw new ConcurrentOperationException("VM is being operated on by someone else."); + } + + if (vm.getHostId() == null) { + s_logger.debug("Host id is null so we can't stop it. How did we get into here?"); + return false; + } + + String reservationId = vm.getReservationId(); + + EventVO event = new EventVO(); + event.setUserId(user.getId()); + event.setAccountId(vm.getAccountId()); + event.setType(EventTypes.EVENT_VM_STOP); + event.setStartId(1); // FIXME: + event.setParameters("id="+vm.getId() + "\n" + "vmName=" + vm.getHostName() + "\nsoId=" + vm.getServiceOfferingId() + "\ntId=" + vm.getTemplateId() + "\ndcId=" + vm.getDataCenterId()); + + StopCommand stop = new StopCommand(vm, vm.getInstanceName(), null); + + boolean stopped = false; + try { + StopAnswer answer = (StopAnswer)_agentMgr.send(vm.getHostId(), stop); + stopped = answer.getResult(); + if (!stopped) { + throw new CloudRuntimeException("Unable to stop the virtual machine due to " + answer.getDetails()); + } + } finally { + if (!stopped) { + _vmDao.updateIf(vm, Event.OperationFailed, vm.getHostId()); + } + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug(vm + " is stopped on the host. Proceeding to release resource held."); + } + + boolean cleanup = false; + + VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + try { + _networkMgr.release(profile); + } catch (Exception e) { + s_logger.warn("Unable to release some network resources.", e); + cleanup = true; + } + + try { + _storageMgr.release(profile); + } catch (Exception e) { + s_logger.warn("Unable to release storage resources.", e); + cleanup = true; + } + + @SuppressWarnings("unchecked") + VirtualMachineGuru guru = (VirtualMachineGuru)_vmGurus.get(vm.getType()); + try { + guru.finalizeStop(profile, vm.getHostId(), vm.getReservationId()); + } catch (Exception e) { + s_logger.warn("Guru " + guru.getClass() + " has trouble processing stop "); + cleanup = true; + } + + vm.setReservationId(null); + vm.setHostId(null); + _vmDao.updateIf(vm, Event.OperationSucceeded, null); + + if (cleanup) { + ItWorkVO work = new ItWorkVO(reservationId, _nodeId, Type.Cleanup); + _workDao.persist(work); + } + + return stopped; } @Override diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 764f32908e2..552a8c639f4 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -77,6 +77,7 @@ import com.cloud.api.commands.RecoverVMCmd; import com.cloud.api.commands.ResetVMPasswordCmd; import com.cloud.api.commands.StartVMCmd; import com.cloud.api.commands.StopVMCmd; +import com.cloud.api.commands.StopVm2Cmd; import com.cloud.api.commands.UpdateVMCmd; import com.cloud.api.commands.UpgradeVMCmd; import com.cloud.async.AsyncJobExecutor; @@ -3745,7 +3746,7 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, VirtualM } @Override - public boolean processDeploymentResult(Commands cmds, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) { + public boolean finalizeStart(Commands cmds, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) { return true; } @@ -3766,4 +3767,41 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, VirtualM } return findById(VirtualMachineName.getVmId(name)); } + + @Override + public UserVm stopVirtualMachine(StopVm2Cmd cmd) throws ConcurrentOperationException { + //Input validation + Account caller = UserContext.current().getAccount(); + Long userId = UserContext.current().getUserId(); + long id = cmd.getId(); + + //if account is removed, return error + if (caller != null && caller.getRemoved() != null) + throw new PermissionDeniedException("The account " + caller.getId()+" is removed"); + + UserVmVO vmInstance = _vmDao.findById(id); + if (vmInstance == null) { + throw new InvalidParameterValueException("unable to find a virtual machine with id " + id); + } + + long eventId = EventUtils.saveScheduledEvent(userId, vmInstance.getAccountId(), EventTypes.EVENT_VM_STOP, "stopping Vm with Id: "+id); + + userId = accountAndUserValidation(id, caller, userId, vmInstance); + UserVO user = _userDao.findById(userId); + + try { + _itMgr.stop(vmInstance, user, caller); + } catch (AgentUnavailableException e) { + throw new CloudRuntimeException("Unable to contact the agent to stop the virtual machine " + vmInstance, e); + } catch (OperationTimedoutException e) { + throw new CloudRuntimeException("Waiting too long for agent to stop the virtual machine " + vmInstance, e); + } + + return _vmDao.findById(id); + } + + @Override + public void finalizeStop(VirtualMachineProfile profile, long hostId, String reservationId) { + } + } diff --git a/server/src/com/cloud/vm/UserVmService.java b/server/src/com/cloud/vm/UserVmService.java index 4dcba157fea..b448edc9581 100755 --- a/server/src/com/cloud/vm/UserVmService.java +++ b/server/src/com/cloud/vm/UserVmService.java @@ -29,6 +29,7 @@ import com.cloud.api.commands.RecoverVMCmd; import com.cloud.api.commands.ResetVMPasswordCmd; import com.cloud.api.commands.StartVMCmd; import com.cloud.api.commands.StopVMCmd; +import com.cloud.api.commands.StopVm2Cmd; import com.cloud.api.commands.UpdateVMCmd; import com.cloud.api.commands.UpgradeVMCmd; import com.cloud.async.executor.OperationResponse; @@ -145,4 +146,6 @@ public interface UserVmService extends Manager { * @throws InvalidParameterValueException */ UserVm upgradeVirtualMachine(UpgradeVMCmd cmd); + + UserVm stopVirtualMachine(StopVm2Cmd cmd) throws ConcurrentOperationException; } diff --git a/server/src/com/cloud/vm/VirtualMachineGuru.java b/server/src/com/cloud/vm/VirtualMachineGuru.java index b21da6a07ee..2b7c14c976e 100644 --- a/server/src/com/cloud/vm/VirtualMachineGuru.java +++ b/server/src/com/cloud/vm/VirtualMachineGuru.java @@ -47,7 +47,6 @@ public interface VirtualMachineGuru { */ boolean finalizeDeployment(Commands cmds, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context); - /** * Check the deployment results. * @param cmds commands and answers that were sent. @@ -55,5 +54,7 @@ public interface VirtualMachineGuru { * @param dest destination it was sent to. * @return true if deployment was fine; false if it didn't go well. */ - boolean processDeploymentResult(Commands cmds, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context); + boolean finalizeStart(Commands cmds, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context); + + void finalizeStop(VirtualMachineProfile profile, long hostId, String reservationId); } diff --git a/server/src/com/cloud/vm/VirtualMachineProfileImpl.java b/server/src/com/cloud/vm/VirtualMachineProfileImpl.java index 3b9dfb41a83..bd65aa022b5 100644 --- a/server/src/com/cloud/vm/VirtualMachineProfileImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineProfileImpl.java @@ -47,13 +47,12 @@ public class VirtualMachineProfileImpl implements Virtua List _nics = new ArrayList(); List _disks = new ArrayList(); StringBuilder _bootArgs = new StringBuilder(); - String _guestOs; Account _owner; BootloaderType _bootloader; VirtualMachine.Type _type; - public VirtualMachineProfileImpl(T vm, String guestOs, VMTemplateVO template, ServiceOfferingVO offering, Account owner, Map params) { + public VirtualMachineProfileImpl(T vm, VMTemplateVO template, ServiceOfferingVO offering, Account owner, Map params) { _vm = vm; _template = template; _offering = offering; @@ -62,29 +61,17 @@ public class VirtualMachineProfileImpl implements Virtua if (_params == null) { _params = new HashMap(); } - _guestOs = guestOs; _type = vm.getType(); } + public VirtualMachineProfileImpl(T vm) { + this(vm, null, null, null, null); + } + public VirtualMachineProfileImpl(VirtualMachine.Type type) { _type = type; } - @Override - public String getOs() { - return _guestOs; - } - - @Override - public void setBootloader(BootloaderType bootloader) { - _bootloader = bootloader; - } - - @Override - public BootloaderType getBootloader() { - return _bootloader; - } - @Override public String toString() { return _vm.toString(); @@ -108,11 +95,6 @@ public class VirtualMachineProfileImpl implements Virtua _params.put(name, value); } - @Override - public String getGuestOs() { - return _guestOs; - } - @Override public VirtualMachineTemplate getTemplate() { if (_template == null) { diff --git a/server/src/com/cloud/vm/VmManager.java b/server/src/com/cloud/vm/VmManager.java index 66f872591b9..08c50292975 100644 --- a/server/src/com/cloud/vm/VmManager.java +++ b/server/src/com/cloud/vm/VmManager.java @@ -24,6 +24,7 @@ import com.cloud.deploy.DeploymentPlan; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageUnavailableException; import com.cloud.network.NetworkConfigurationVO; @@ -68,7 +69,7 @@ public interface VmManager extends Manager { T start(T vm, Map params, User caller, Account account) throws InsufficientCapacityException, StorageUnavailableException, ConcurrentOperationException, ResourceUnavailableException; - T stop(T vm, User caller, Account account) throws AgentUnavailableException, ConcurrentOperationException; + boolean stop(T vm, User caller, Account account) throws AgentUnavailableException, ConcurrentOperationException, OperationTimedoutException; void destroy(); diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index b98d6d387e1..f507887ced4 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -616,6 +616,7 @@ CREATE TABLE `cloud`.`vm_instance` ( `account_id` bigint unsigned NOT NULL COMMENT 'user id of owner', `domain_id` bigint unsigned NOT NULL, `service_offering_id` bigint unsigned NOT NULL COMMENT 'service offering id', + `reservation_id` char(40) COMMENT 'reservation id', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;