diff --git a/api/src/main/java/com/cloud/network/VirtualNetworkApplianceService.java b/api/src/main/java/com/cloud/network/VirtualNetworkApplianceService.java index 98fb8be7c7a9..cdfd1469e1bb 100644 --- a/api/src/main/java/com/cloud/network/VirtualNetworkApplianceService.java +++ b/api/src/main/java/com/cloud/network/VirtualNetworkApplianceService.java @@ -43,7 +43,7 @@ public interface VirtualNetworkApplianceService { * the command specifying router's id * @return router if successful */ - VirtualRouter rebootRouter(long routerId, boolean reprogramNetwork) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + VirtualRouter rebootRouter(long routerId, boolean reprogramNetwork, boolean forced) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; VirtualRouter upgradeRouter(UpgradeRouterCmd cmd); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/RebootRouterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/RebootRouterCmd.java index 802e3df3dcf2..764d304e25b6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/RebootRouterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/RebootRouterCmd.java @@ -49,6 +49,9 @@ public class RebootRouterCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DomainRouterResponse.class, required = true, description = "the ID of the router") private Long id; + @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, required = false, description = "Force reboot the router (Router is force Stopped and then Started)", since = "4.16.0") + private Boolean forced; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -96,10 +99,14 @@ public Long getInstanceId() { return getId(); } + public boolean isForced() { + return (forced != null) ? forced : false; + } + @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { CallContext.current().setEventDetails("Router Id: " + this._uuidMgr.getUuid(VirtualMachine.class,getId())); - VirtualRouter result = _routerService.rebootRouter(getId(), true); + VirtualRouter result = _routerService.rebootRouter(getId(), true, isForced()); if (result != null) { DomainRouterResponse response = _responseGenerator.createDomainRouterResponse(result); response.setResponseName("router"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/RebootSystemVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/RebootSystemVmCmd.java index ebc50ae7e1d5..352fd3b330b2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/RebootSystemVmCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/RebootSystemVmCmd.java @@ -52,6 +52,9 @@ public class RebootSystemVmCmd extends BaseAsyncCmd { description = "The ID of the system virtual machine") private Long id; + @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, required = false, description = "Force reboot the system VM (System VM is Stopped and then Started)", since = "4.16.0") + private Boolean forced; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -104,6 +107,10 @@ public Long getInstanceId() { return getId(); } + public boolean isForced() { + return (forced != null) ? forced : false; + } + @Override public void execute() { CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java index 5bdbbb651ebb..d827a6b894ac 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java @@ -53,6 +53,9 @@ public class RebootVMCmd extends BaseAsyncCmd implements UserCmd { required=true, description="The ID of the virtual machine") private Long id; + @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, required = false, description = "Force reboot the VM (VM is Stopped and then Started)", since = "4.16.0") + private Boolean forced; + @Parameter(name = ApiConstants.BOOT_INTO_SETUP, type = CommandType.BOOLEAN, required = false, description = "Boot into hardware setup menu or not", since = "4.15.0.0") private Boolean bootIntoSetup; @@ -64,6 +67,10 @@ public Long getId() { return id; } + public boolean isForced() { + return (forced != null) ? forced : false; + } + public Boolean getBootIntoSetup() { return bootIntoSetup; } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index de1ef20f883e..373995acddb5 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -3430,7 +3430,6 @@ private void orchestrateReboot(final String vmUuid, final Map params = null; + if (enterSetup) { + params = new HashMap(); + params.put(VirtualMachineProfile.Param.BootIntoSetup, Boolean.TRUE); + } + return startVirtualMachine(vmId, null, null, hostId, params, null).first(); + } + } catch (ResourceUnavailableException e) { + throw new CloudRuntimeException("Unable to reboot the VM: " + vmId, e); + } catch (CloudException e) { + throw new CloudRuntimeException("Unable to reboot the VM: " + vmId, e); + } + return null; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VM_UPGRADE, eventDescription = "upgrading Vm") /* @@ -2887,7 +2914,7 @@ public UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityE // Verify input parameters UserVmVO vmInstance = _vmDao.findById(vmId); if (vmInstance == null) { - throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); + throw new InvalidParameterValueException("Unable to find a virtual machine with id " + vmId); } _accountMgr.checkAccess(caller, null, true, vmInstance); @@ -2909,7 +2936,8 @@ public UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityE if (enterSetup != null && enterSetup && !HypervisorType.VMware.equals(vmInstance.getHypervisorType())) { throw new InvalidParameterValueException("Booting into a hardware setup menu is not implemented on " + vmInstance.getHypervisorType()); } - UserVm userVm = rebootVirtualMachine(CallContext.current().getCallingUserId(), vmId, enterSetup == null ? false : cmd.getBootIntoSetup()); + + UserVm userVm = rebootVirtualMachine(CallContext.current().getCallingUserId(), vmId, enterSetup == null ? false : cmd.getBootIntoSetup(), cmd.isForced()); if (userVm != null ) { // update the vmIdCountMap if the vm is in advanced shared network with out services final List nics = _nicDao.listByVmId(vmId); diff --git a/server/src/test/java/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java b/server/src/test/java/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java index 45bf4c1763b5..dc3e3abf723a 100644 --- a/server/src/test/java/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java +++ b/server/src/test/java/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java @@ -109,7 +109,7 @@ public VirtualRouter startRouter(final long routerId, final boolean reprogramNet * @see com.cloud.network.VirtualNetworkApplianceService#rebootRouter(long, boolean) */ @Override - public VirtualRouter rebootRouter(final long routerId, final boolean reprogramNetwork) throws ConcurrentOperationException, ResourceUnavailableException { + public VirtualRouter rebootRouter(final long routerId, final boolean reprogramNetwork, final boolean forced) throws ConcurrentOperationException, ResourceUnavailableException { // TODO Auto-generated method stub return null; } diff --git a/test/integration/smoke/test_routers.py b/test/integration/smoke/test_routers.py index 3a20d64fe152..356bd213cfc3 100644 --- a/test/integration/smoke/test_routers.py +++ b/test/integration/smoke/test_routers.py @@ -815,3 +815,46 @@ def test_09_reboot_router(self): "Router response after reboot is either is invalid\ or in stopped state") return + + @attr(tags=["advanced", "advancedns", "smoke", "dvs"], required_hardware="false") + def test_10_reboot_router_forced(self): + """Test force reboot router + """ + + list_router_response = list_routers( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid + ) + self.assertEqual( + isinstance(list_router_response, list), + True, + "Check list response returns a valid list" + ) + router = list_router_response[0] + + public_ip = router.publicip + + self.debug("Force rebooting the router with ID: %s" % router.id) + # Reboot the router + cmd = rebootRouter.rebootRouterCmd() + cmd.id = router.id + cmd.forced = True + self.apiclient.rebootRouter(cmd) + + # List routers to check state of router + retries_cnt = 10 + while retries_cnt >= 0: + router_response = list_routers( + self.apiclient, + id=router.id + ) + if self.verifyRouterResponse(router_response, public_ip): + self.debug("Router is running successfully after force reboot") + return + time.sleep(10) + retries_cnt = retries_cnt - 1 + self.fail( + "Router response after force reboot is either invalid\ + or router in stopped state") + return diff --git a/test/integration/smoke/test_ssvm.py b/test/integration/smoke/test_ssvm.py index bb83931c1fd9..0392d9eb9f1c 100644 --- a/test/integration/smoke/test_ssvm.py +++ b/test/integration/smoke/test_ssvm.py @@ -959,7 +959,122 @@ def test_08_reboot_cpvm(self): "basic", "sg"], required_hardware="true") - def test_09_destroy_ssvm(self): + def test_09_reboot_ssvm_forced(self): + """Test force reboot SSVM + """ + + list_ssvm_response = list_ssvms( + self.apiclient, + systemvmtype='secondarystoragevm', + state='Running', + zoneid=self.zone.id + ) + + self.assertEqual( + isinstance(list_ssvm_response, list), + True, + "Check list response returns a valid list" + ) + + ssvm_response = list_ssvm_response[0] + + hosts = list_hosts( + self.apiclient, + id=ssvm_response.hostid + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check list response returns a valid list" + ) + + self.debug("Force rebooting SSVM: %s" % ssvm_response.id) + cmd = rebootSystemVm.rebootSystemVmCmd() + cmd.id = ssvm_response.id + cmd.forced = True + self.apiclient.rebootSystemVm(cmd) + + ssvm_response = self.checkForRunningSystemVM(ssvm_response) + self.debug("SSVM State: %s" % ssvm_response.state) + self.assertEqual( + 'Running', + str(ssvm_response.state), + "Check whether SSVM is running or not" + ) + + # Wait for the agent to be up + self.waitForSystemVMAgent(ssvm_response.name) + + # Wait until NFS stores mounted before running the script + time.sleep(90) + # Call to verify cloud process is running + self.test_03_ssvm_internals() + + @attr( + tags=[ + "advanced", + "advancedns", + "smoke", + "basic", + "sg"], + required_hardware="true") + def test_10_reboot_cpvm_forced(self): + """Test force reboot CPVM + """ + + list_cpvm_response = list_ssvms( + self.apiclient, + systemvmtype='consoleproxy', + state='Running', + zoneid=self.zone.id + ) + self.assertEqual( + isinstance(list_cpvm_response, list), + True, + "Check list response returns a valid list" + ) + cpvm_response = list_cpvm_response[0] + + hosts = list_hosts( + self.apiclient, + id=cpvm_response.hostid + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check list response returns a valid list" + ) + + self.debug("Force rebooting CPVM: %s" % cpvm_response.id) + + cmd = rebootSystemVm.rebootSystemVmCmd() + cmd.id = cpvm_response.id + cmd.forced = True + self.apiclient.rebootSystemVm(cmd) + + cpvm_response = self.checkForRunningSystemVM(cpvm_response) + self.debug("CPVM state: %s" % cpvm_response.state) + self.assertEqual( + 'Running', + str(cpvm_response.state), + "Check whether CPVM is running or not" + ) + + # Wait for the agent to be up + self.waitForSystemVMAgent(cpvm_response.name) + + # Call to verify cloud process is running + self.test_04_cpvm_internals() + + @attr( + tags=[ + "advanced", + "advancedns", + "smoke", + "basic", + "sg"], + required_hardware="true") + def test_11_destroy_ssvm(self): """Test destroy SSVM """ @@ -1031,7 +1146,7 @@ def test_09_destroy_ssvm(self): "basic", "sg"], required_hardware="true") - def test_10_destroy_cpvm(self): + def test_12_destroy_cpvm(self): """Test destroy CPVM """ @@ -1102,7 +1217,7 @@ def test_10_destroy_cpvm(self): "basic", "sg"], required_hardware="true") - def test_11_ss_nfs_version_on_ssvm(self): + def test_13_ss_nfs_version_on_ssvm(self): """Test NFS Version on Secondary Storage mounted properly on SSVM """ diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 08668e47b727..a64293be297e 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -499,6 +499,40 @@ def test_03_reboot_vm(self): ) return + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_04_reboot_vm_forced(self): + """Test Force Reboot Virtual Machine + """ + + try: + self.debug("Force rebooting VM - ID: %s" % self.virtual_machine.id) + self.small_virtual_machine.reboot(self.apiclient, forced=True) + except Exception as e: + self.fail("Failed to force reboot VM: %s" % e) + + list_vm_response = VirtualMachine.list( + self.apiclient, + id=self.small_virtual_machine.id + ) + self.assertEqual( + isinstance(list_vm_response, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + len(list_vm_response), + 0, + "Check VM available in List Virtual Machines" + ) + + self.assertEqual( + list_vm_response[0].state, + "Running", + "Check virtual machine is in running state" + ) + return + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") def test_06_destroy_vm(self): """Test destroy Virtual Machine diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 6bb7b669096c..865631093c9d 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -723,10 +723,12 @@ def stop(self, apiclient, forced=None): raise Exception(response[1]) return - def reboot(self, apiclient): + def reboot(self, apiclient, forced=None): """Reboot the instance""" cmd = rebootVirtualMachine.rebootVirtualMachineCmd() cmd.id = self.id + if forced: + cmd.forced = forced apiclient.rebootVirtualMachine(cmd) response = self.getState(apiclient, VirtualMachine.RUNNING) @@ -4436,10 +4438,12 @@ def stop(cls, apiclient, id, forced=None): return apiclient.stopRouter(cmd) @classmethod - def reboot(cls, apiclient, id): + def reboot(cls, apiclient, id, forced=None): """Reboots the router""" cmd = rebootRouter.rebootRouterCmd() cmd.id = id + if forced: + cmd.forced = forced return apiclient.rebootRouter(cmd) @classmethod diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index e55e51684fe5..b4e0efc46f87 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -125,6 +125,7 @@ export default { show: (record) => { return ['Running'].includes(record.state) }, args: (record, store) => { var fields = [] + fields.push('forced') if (record.hypervisor === 'VMware') { if (store.apis.rebootVirtualMachine.params.filter(x => x.name === 'bootintosetup').length > 0) { fields.push('bootintosetup') diff --git a/ui/src/config/section/infra/routers.js b/ui/src/config/section/infra/routers.js index 6ffa4680a803..a338237c5fb7 100644 --- a/ui/src/config/section/infra/routers.js +++ b/ui/src/config/section/infra/routers.js @@ -66,6 +66,7 @@ export default { label: 'label.action.reboot.router', message: 'message.action.reboot.router', dataView: true, + args: ['forced'], hidden: (record) => { return record.state === 'Running' } }, { diff --git a/ui/src/config/section/infra/systemVms.js b/ui/src/config/section/infra/systemVms.js index bc20b904d6c5..5c02734e34fa 100644 --- a/ui/src/config/section/infra/systemVms.js +++ b/ui/src/config/section/infra/systemVms.js @@ -47,7 +47,8 @@ export default { label: 'label.action.reboot.systemvm', message: 'message.action.reboot.systemvm', dataView: true, - show: (record) => { return record.state === 'Running' } + show: (record) => { return record.state === 'Running' }, + args: ['forced'] }, { api: 'scaleSystemVm',