From 3e4430d811d5e148c4fef1900bdd8dd07a38bdeb Mon Sep 17 00:00:00 2001 From: Nitin Mehta Date: Thu, 28 Mar 2013 16:43:37 +0530 Subject: [PATCH] CLOUDSTACK-658 - Scaleup vm support for Xenserver Added the framweork so that it can be extended for vmware and kvm as well. Added unitests and marvin tests. --- .../com/cloud/agent/api/ScaleVmCommand.java | 6 +- api/src/com/cloud/event/EventTypes.java | 1 + api/src/com/cloud/vm/UserVmService.java | 16 +- api/src/com/cloud/vm/VirtualMachine.java | 2 +- .../api/command/user/vm/ScaleVMCmd.java | 110 +++++++ .../api/command/test/ScaleVMCmdTest.java | 122 +++++++ client/tomcatconf/commands.properties.in | 1 + core/src/com/cloud/vm/VMInstanceVO.java | 2 +- .../xen/resource/CitrixResourceBase.java | 82 +++++ .../xen/resource/XenServer56FP1Resource.java | 11 +- .../xen/resource/CitrixResourceBaseTest.java | 148 +++++++++ .../xenserver/Add-To-VCPUs-Params-Live.sh | 33 ++ scripts/vm/hypervisor/xenserver/vmops | 13 + .../src/com/cloud/configuration/Config.java | 3 +- .../cloud/server/ManagementServerImpl.java | 1 + server/src/com/cloud/vm/ItWorkVO.java | 3 +- .../src/com/cloud/vm/UserVmManagerImpl.java | 92 +++++- .../com/cloud/vm/VirtualMachineManager.java | 12 + .../cloud/vm/VirtualMachineManagerImpl.java | 311 ++++++++++++++++-- .../com/cloud/vm/MockUserVmManagerImpl.java | 17 +- .../vm/MockVirtualMachineManagerImpl.java | 17 + .../test/com/cloud/vm/UserVmManagerTest.java | 192 ++++++++++- .../vm/VirtualMachineManagerImplTest.java | 208 ++++++++++++ test/integration/smoke/test_ScaleVm.py | 221 +++++++++++++ 24 files changed, 1545 insertions(+), 79 deletions(-) create mode 100644 api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java create mode 100644 api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java create mode 100644 plugins/hypervisors/xen/test/com/cloud/hypervisor/xen/resource/CitrixResourceBaseTest.java create mode 100644 scripts/vm/hypervisor/xenserver/Add-To-VCPUs-Params-Live.sh create mode 100644 server/test/com/cloud/vm/VirtualMachineManagerImplTest.java create mode 100644 test/integration/smoke/test_ScaleVm.py diff --git a/api/src/com/cloud/agent/api/ScaleVmCommand.java b/api/src/com/cloud/agent/api/ScaleVmCommand.java index e5078d5e8c04..35d22ad9d96a 100644 --- a/api/src/com/cloud/agent/api/ScaleVmCommand.java +++ b/api/src/com/cloud/agent/api/ScaleVmCommand.java @@ -40,14 +40,14 @@ public int getCpus() { } public ScaleVmCommand(String vmName, int cpus, - Integer speed, long minRam, long maxRam) { + Integer speed, long minRam, long maxRam, boolean limitCpuUse) { super(); this.vmName = vmName; this.cpus = cpus; - //this.speed = speed; + this.speed = speed; this.minRam = minRam; this.maxRam = maxRam; - this.vm = new VirtualMachineTO(1L, vmName, null, cpus, null, minRam, maxRam, null, null, false, false, null); + this.vm = new VirtualMachineTO(1L, vmName, null, cpus, speed, minRam, maxRam, null, null, false, false, null); /*vm.setName(vmName); vm.setCpus(cpus); vm.setRam(minRam, maxRam);*/ diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index 6fdd28cf165c..2e961e5b3901 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -59,6 +59,7 @@ public class EventTypes { public static final String EVENT_VM_REBOOT = "VM.REBOOT"; public static final String EVENT_VM_UPDATE = "VM.UPDATE"; public static final String EVENT_VM_UPGRADE = "VM.UPGRADE"; + public static final String EVENT_VM_SCALE = "VM.SCALE"; public static final String EVENT_VM_RESETPASSWORD = "VM.RESETPASSWORD"; public static final String EVENT_VM_RESETSSHKEY = "VM.RESETSSHKEY"; public static final String EVENT_VM_MIGRATE = "VM.MIGRATE"; diff --git a/api/src/com/cloud/vm/UserVmService.java b/api/src/com/cloud/vm/UserVmService.java index 9d6b221ca214..2c33d41cd351 100755 --- a/api/src/com/cloud/vm/UserVmService.java +++ b/api/src/com/cloud/vm/UserVmService.java @@ -23,18 +23,7 @@ import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; -import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd; -import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; -import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; -import org.apache.cloudstack.api.command.user.vm.RebootVMCmd; -import org.apache.cloudstack.api.command.user.vm.RemoveNicFromVMCmd; -import org.apache.cloudstack.api.command.user.vm.ResetVMPasswordCmd; -import org.apache.cloudstack.api.command.user.vm.ResetVMSSHKeyCmd; -import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd; -import org.apache.cloudstack.api.command.user.vm.StartVMCmd; -import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; -import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; -import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; +import org.apache.cloudstack.api.command.user.vm.*; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd; import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd; @@ -402,4 +391,7 @@ UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityExceptio VirtualMachine vmStorageMigration(Long vmId, StoragePool destPool); UserVm restoreVM(RestoreVMCmd cmd); + + UserVm upgradeVirtualMachine(ScaleVMCmd scaleVMCmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; + } diff --git a/api/src/com/cloud/vm/VirtualMachine.java b/api/src/com/cloud/vm/VirtualMachine.java index 4300dd548c1c..8f807d450c70 100755 --- a/api/src/com/cloud/vm/VirtualMachine.java +++ b/api/src/com/cloud/vm/VirtualMachine.java @@ -176,7 +176,7 @@ public enum Event { AgentReportShutdowned, AgentReportMigrated, RevertRequested, - SnapshotRequested + SnapshotRequested, }; public enum Type { diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java new file mode 100644 index 000000000000..4fc65c37e589 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java @@ -0,0 +1,110 @@ +// 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.vm; + +import com.cloud.exception.*; +import com.cloud.user.Account; +import com.cloud.user.UserContext; +import com.cloud.uservm.UserVm; +import org.apache.cloudstack.api.*; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.log4j.Logger; + + +@APICommand(name = "scaleVirtualMachine", description="Scales the virtual machine to a new service offering.", responseObject=UserVmResponse.class) +public class ScaleVMCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(ScaleVMCmd.class.getName()); + private static final String s_name = "scalevirtualmachineresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL + @Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType=UserVmResponse.class, + required=true, description="The ID of the virtual machine") + private Long id; + + @ACL + @Parameter(name=ApiConstants.SERVICE_OFFERING_ID, type=CommandType.UUID, entityType=ServiceOfferingResponse.class, + required=true, description="the ID of the service offering for the virtual machine") + private Long serviceOfferingId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getServiceOfferingId() { + return serviceOfferingId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + public static String getResultObjectName() { + return "virtualmachine"; + } + + @Override + public long getEntityOwnerId() { + UserVm userVm = _entityMgr.findById(UserVm.class, getId()); + if (userVm != null) { + return userVm.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } + + @Override + public void execute(){ + //UserContext.current().setEventDetails("Vm Id: "+getId()); + UserVm result = null; + try { + result = _userVmService.upgradeVirtualMachine(this); + } catch (ResourceUnavailableException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (ConcurrentOperationException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (ManagementServerException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (VirtualMachineMigrationException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } + if (result != null){ + UserVmResponse response = _responseGenerator.createUserVmResponse("virtualmachine", result).get(0); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to scale vm"); + } + } +} \ No newline at end of file diff --git a/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java b/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java new file mode 100644 index 000000000000..301fa02ca295 --- /dev/null +++ b/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java @@ -0,0 +1,122 @@ +// 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.test; + +import com.cloud.user.Account; +import com.cloud.user.UserContext; +import com.cloud.uservm.UserVm; +import com.cloud.vm.UserVmService; +import junit.framework.Assert; +import junit.framework.TestCase; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.region.AddRegionCmd; +import org.apache.cloudstack.api.command.user.vm.ScaleVMCmd; +import org.apache.cloudstack.api.response.RegionResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.region.Region; +import org.apache.cloudstack.region.RegionService; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + + +public class ScaleVMCmdTest extends TestCase{ + + private ScaleVMCmd scaleVMCmd; + private ResponseGenerator responseGenerator; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Before + public void setUp() { + + scaleVMCmd = new ScaleVMCmd(){ + @Override + public Long getId() { + return 2L; + } + }; + + //Account account = new AccountVO("testaccount", 1L, "networkdomain", (short) 0, "uuid"); + //UserContext.registerContext(1, account, null, true); + + + } + + + @Test + public void testCreateSuccess() { + + UserVmService userVmService = Mockito.mock(UserVmService.class); + + UserVm uservm = Mockito.mock(UserVm.class); + try { + Mockito.when( + userVmService.upgradeVirtualMachine(scaleVMCmd)) + .thenReturn(uservm); + }catch (Exception e){ + Assert.fail("Received exception when success expected " +e.getMessage()); + } + + scaleVMCmd._userVmService = userVmService; + responseGenerator = Mockito.mock(ResponseGenerator.class); + + UserVmResponse userVmResponse = Mockito.mock(UserVmResponse.class); + List responseList = new ArrayList(); + responseList.add(userVmResponse); + + Mockito.when(responseGenerator.createUserVmResponse("virtualmachine",uservm)) + .thenReturn(responseList); + + scaleVMCmd._responseGenerator = responseGenerator; + scaleVMCmd.execute(); + + } + + @Test + public void testCreateFailure() { + + UserVmService userVmService = Mockito.mock(UserVmService.class); + + try { + UserVm uservm = Mockito.mock(UserVm.class); + Mockito.when( + userVmService.upgradeVirtualMachine(scaleVMCmd)) + .thenReturn(null); + }catch (Exception e){ + Assert.fail("Received exception when success expected " +e.getMessage()); + } + + scaleVMCmd._userVmService = userVmService; + + try { + scaleVMCmd.execute(); + } catch (ServerApiException exception) { + Assert.assertEquals("Failed to scale vm", + exception.getDescription()); + } + + } +} diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 3b3ffdfae09b..163c2cee8615 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -66,6 +66,7 @@ listVirtualMachines=15 getVMPassword=15 restoreVirtualMachine=15 changeServiceForVirtualMachine=15 +scaleVirtualMachine=15 assignVirtualMachine=1 migrateVirtualMachine=1 recoverVirtualMachine=7 diff --git a/core/src/com/cloud/vm/VMInstanceVO.java b/core/src/com/cloud/vm/VMInstanceVO.java index 6149e43f8fae..77e9c0279c9c 100644 --- a/core/src/com/cloud/vm/VMInstanceVO.java +++ b/core/src/com/cloud/vm/VMInstanceVO.java @@ -480,4 +480,4 @@ public Long getDiskOfferingId() { return diskOfferingId; } -} +} diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 52d992f23ab5..d2f3f69f0882 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -53,6 +53,7 @@ import javax.naming.ConfigurationException; import javax.xml.parsers.DocumentBuilderFactory; +import com.cloud.agent.api.*; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; import com.cloud.agent.api.to.*; import com.cloud.network.rules.FirewallRule; @@ -600,12 +601,93 @@ public Answer executeRequest(Command cmd) { return execute((RevertToVMSnapshotCommand)cmd); } else if (clazz == NetworkRulesVmSecondaryIpCommand.class) { return execute((NetworkRulesVmSecondaryIpCommand)cmd); + } else if (clazz == ScaleVmCommand.class) { + return execute((ScaleVmCommand) cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } } + protected void scaleVM(Connection conn, VM vm, VirtualMachineTO vmSpec, Host host) throws XenAPIException, XmlRpcException { + vm.setMemoryDynamicRange(conn, vmSpec.getMinRam() * 1024 * 1024, vmSpec.getMaxRam() * 1024 * 1024); + vm.setVCPUsNumberLive(conn, (long)vmSpec.getCpus()); + + Integer speed = vmSpec.getSpeed(); + if (speed != null) { + + int cpuWeight = _maxWeight; //cpu_weight + + // weight based allocation + + cpuWeight = (int)((speed*0.99) / _host.speed * _maxWeight); + if (cpuWeight > _maxWeight) { + cpuWeight = _maxWeight; + } + + if (vmSpec.getLimitCpuUse()) { + long utilization = 0; // max CPU cap, default is unlimited + utilization = ((long)speed * 100 * vmSpec.getCpus()) / _host.speed ; + vm.addToVCPUsParamsLive(conn, "cap", Long.toString(utilization)); + } + //vm.addToVCPUsParamsLive(conn, "weight", Integer.toString(cpuWeight)); + callHostPlugin(conn, "vmops", "add_to_VCPUs_params_live", "key", "weight", "value", Integer.toString(cpuWeight), "vmname", vmSpec.getName() ); + } + } + + public ScaleVmAnswer execute(ScaleVmCommand cmd) { + VirtualMachineTO vmSpec = cmd.getVirtualMachine(); + String vmName = vmSpec.getName(); + try { + Connection conn = getConnection(); + Set vms = VM.getByNameLabel(conn, vmName); + Host host = Host.getByUuid(conn, _host.uuid); + // stop vm which is running on this host or is in halted state + Iterator iter = vms.iterator(); + while ( iter.hasNext() ) { + VM vm = iter.next(); + VM.Record vmr = vm.getRecord(conn); + + if ((vmr.powerState == VmPowerState.HALTED) || (vmr.powerState == VmPowerState.RUNNING && !isRefNull(vmr.residentOn) && !vmr.residentOn.getUuid(conn).equals(_host.uuid))) { + iter.remove(); + } + } + + if (vms.size() == 0) { + s_logger.info("No running VM " + vmName +" exists on XenServer" + _host.uuid); + return new ScaleVmAnswer(cmd, false, "VM does not exist"); + } + + for (VM vm : vms) { + VM.Record vmr = vm.getRecord(conn); + try { + scaleVM(conn, vm, vmSpec, host); + + } catch (Exception e) { + String msg = "Catch exception " + e.getClass().getName() + " when scaling VM:" + vmName + " due to " + e.toString(); + s_logger.debug(msg); + return new ScaleVmAnswer(cmd, false, msg); + } + + } + String msg = "scaling VM " + vmName + " is successful on host " + host; + s_logger.debug(msg); + return new ScaleVmAnswer(cmd, true, msg); + + } catch (XenAPIException e) { + String msg = "Upgrade Vm " + vmName + " fail due to " + e.toString(); + s_logger.warn(msg, e); + return new ScaleVmAnswer(cmd, false, msg); + } catch (XmlRpcException e) { + String msg = "Upgrade Vm " + vmName + " fail due to " + e.getMessage(); + s_logger.warn(msg, e); + return new ScaleVmAnswer(cmd, false, msg); + } catch (Exception e) { + String msg = "Unable to upgrade " + vmName + " due to " + e.getMessage(); + s_logger.warn(msg, e); + return new ScaleVmAnswer(cmd, false, msg); + } + } private Answer execute(RevertToVMSnapshotCommand cmd) { String vmName = cmd.getVmName(); diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java index 7040311d04eb..d64e17337a18 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java @@ -138,9 +138,14 @@ protected VM createVmFromTemplate(Connection conn, VirtualMachineTO vmSpec, Host record.actionsAfterShutdown = Types.OnNormalExit.DESTROY; record.memoryDynamicMax = vmSpec.getMaxRam(); record.memoryDynamicMin = vmSpec.getMinRam(); - record.memoryStaticMax = vmSpec.getMaxRam(); - record.memoryStaticMin = vmSpec.getMinRam(); - record.VCPUsMax = (long) vmSpec.getCpus(); + record.memoryStaticMax = 8589934592L; //128GB + record.memoryStaticMin = 134217728L; //128MB + if (guestOsTypeName.toLowerCase().contains("windows")) { + record.VCPUsMax = (long) vmSpec.getCpus(); + } else { + record.VCPUsMax = 32L; + } + record.VCPUsAtStartup = (long) vmSpec.getCpus(); record.consoles.clear(); diff --git a/plugins/hypervisors/xen/test/com/cloud/hypervisor/xen/resource/CitrixResourceBaseTest.java b/plugins/hypervisors/xen/test/com/cloud/hypervisor/xen/resource/CitrixResourceBaseTest.java new file mode 100644 index 000000000000..7392cb1d53ec --- /dev/null +++ b/plugins/hypervisors/xen/test/com/cloud/hypervisor/xen/resource/CitrixResourceBaseTest.java @@ -0,0 +1,148 @@ +/* + * 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.hypervisor.xen.resource; + +import org.junit.Test; +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.MockitoAnnotations; +import static org.mockito.Mockito.*; + +import com.cloud.hypervisor.xen.resource.CitrixResourceBase.XsHost; +import com.cloud.agent.api.ScaleVmCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.api.ScaleVmAnswer; +import com.xensource.xenapi.*; +import org.apache.xmlrpc.XmlRpcException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.Iterator; + + + + +public class CitrixResourceBaseTest { + + @Spy CitrixResourceBase _resource = new CitrixResourceBase() { + + @Override + public ScaleVmAnswer execute(ScaleVmCommand cmd) { + return super.execute(cmd); + } + public String callHostPlugin(Connection conn, String plugin, String cmd, String... params) { + return "Success"; + } + @Override + protected void scaleVM(Connection conn, VM vm, VirtualMachineTO vmSpec, Host host) throws Types.XenAPIException, XmlRpcException { + _host.speed = 500; + super.scaleVM(conn, vm, vmSpec, host); + } + + + }; + @Mock XsHost _host; + @Mock Host host; + @Mock ScaleVmCommand cmd; + @Mock VirtualMachineTO vmSpec; + @Mock Connection conn; + @Mock VM vm; + + @Before + public void setup(){ + + MockitoAnnotations.initMocks(this); + + doReturn(vmSpec).when(cmd).getVirtualMachine(); + doReturn("i-2-3-VM").when(vmSpec).getName(); + + } + + + // Expecting XmlRpcException while trying to get the record of vm using connection + @Test(expected = XmlRpcException.class) + public void testScaleVMF1() throws + Types.BadServerResponse, + Types.XenAPIException, + XmlRpcException { + doReturn(conn).when(_resource).getConnection(); + Set vms = (Set )mock(Set.class); + + Iterator iter = mock(Iterator.class); + doReturn(iter).when(vms).iterator(); + when(iter.hasNext()).thenReturn(true).thenReturn(false); + doReturn(vm).when(iter).next(); + VM.Record vmr = mock(VM.Record.class); + when(vm.getRecord(conn)).thenThrow(new XmlRpcException("XmlRpcException")); + when(vm.getRecord(conn)).thenReturn(vmr); + vmr.powerState = Types.VmPowerState.RUNNING; + vmr.residentOn = mock(Host.class); + XenAPIObject object = mock(XenAPIObject.class); + doReturn(new String("OpaqueRef:NULL")).when(object).toWireString(); + doNothing().when(_resource).scaleVM(conn, vm, vmSpec, host); + + _resource.execute(cmd); + verify(iter, times(2)).hasNext(); + verify(iter, times(2)).next(); + + } + + // Test to scale vm "i-2-3-VM" cpu-cap disabled + @Test + public void testScaleVMF2() throws Types.XenAPIException, XmlRpcException { + + doReturn(null).when(vm).setMemoryDynamicRangeAsync(conn, 536870912L, 536870912L); + doReturn(1).when(vmSpec).getCpus(); + doNothing().when(vm).setVCPUsNumberLive(conn, 1L); + doReturn(500).when(vmSpec).getSpeed(); + doReturn(false).when(vmSpec).getLimitCpuUse(); + Map args = (Map)mock(HashMap.class); + when(host.callPlugin(conn, "vmops", "add_to_VCPUs_params_live", args)).thenReturn("Success"); + doReturn(null).when(_resource).callHostPlugin(conn, "vmops", "add_to_VCPUs_params_live", "key", "weight", "value", "253", "vmname", "i-2-3-VM"); + + _resource.scaleVM(conn, vm, vmSpec, host); + + verify(vmSpec, times(1)).getLimitCpuUse(); + verify(_resource, times(1)).callHostPlugin(conn, "vmops", "add_to_VCPUs_params_live", "key", "weight", "value", "253", "vmname", "i-2-3-VM"); + } + + // Test to scale vm "i-2-3-VM" cpu-cap enabled + @Test + public void testScaleVMF3() throws Types.XenAPIException, XmlRpcException { + + doReturn(null).when(vm).setMemoryDynamicRangeAsync(conn, 536870912L, 536870912L); + doReturn(1).when(vmSpec).getCpus(); + doNothing().when(vm).setVCPUsNumberLive(conn, 1L); + doReturn(500).when(vmSpec).getSpeed(); + doReturn(true).when(vmSpec).getLimitCpuUse(); + doNothing().when(vm).addToVCPUsParamsLive(conn, "cap", "100"); + Map args = (Map)mock(HashMap.class); + when(host.callPlugin(conn, "vmops", "add_to_VCPUs_params_live", args)).thenReturn("Success"); + doReturn(null).when(_resource).callHostPlugin(conn, "vmops", "add_to_VCPUs_params_live", "key", "weight", "value", "253", "vmname", "i-2-3-VM"); + + _resource.scaleVM(conn, vm, vmSpec, host); + + verify(vmSpec, times(1)).getLimitCpuUse(); + verify(_resource, times(1)).callHostPlugin(conn, "vmops", "add_to_VCPUs_params_live", "key", "weight", "value", "253", "vmname", "i-2-3-VM"); + verify(vm, times(1)).addToVCPUsParamsLive(conn, "cap", "100"); + } +} \ No newline at end of file diff --git a/scripts/vm/hypervisor/xenserver/Add-To-VCPUs-Params-Live.sh b/scripts/vm/hypervisor/xenserver/Add-To-VCPUs-Params-Live.sh new file mode 100644 index 000000000000..0fadcd858605 --- /dev/null +++ b/scripts/vm/hypervisor/xenserver/Add-To-VCPUs-Params-Live.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# 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. + +set -x + +vmname=$1 +key=$2 +value=$3 +uuid=`xe vm-list name-label=$vmname | grep uuid | awk '{print $NF}'` +if [[ $key == "weight" ]] +then + xe vm-param-set VCPUs-params:weight=$value uuid=$uuid +fi +if [[ $key == "cap" ]] +then + xe vm-param-set VCPUs-params:cap=$value uuid=$uuid +fi + diff --git a/scripts/vm/hypervisor/xenserver/vmops b/scripts/vm/hypervisor/xenserver/vmops index 31c9a59663ed..30b5300e93e4 100755 --- a/scripts/vm/hypervisor/xenserver/vmops +++ b/scripts/vm/hypervisor/xenserver/vmops @@ -42,6 +42,18 @@ def echo(fn): return res return wrapped +@echo +def add_to_VCPUs_params_live(session, args): + key = args['key'] + value = args['value'] + vmname = args['vmname'] + try: + cmd = ["bash", "/opt/xensource/bin/Add-To-VCPUs-Params-Live.sh", vmname, key, value] + txt = util.pread2(cmd) + except: + return 'false' + return 'true' + @echo def gethostvmstats(session, args): collect_host_stats = args['collectHostStats'] @@ -1661,6 +1673,7 @@ if __name__ == "__main__": "default_network_rules_systemvm":default_network_rules_systemvm, "network_rules_vmSecondaryIp":network_rules_vmSecondaryIp, "get_rule_logs_for_vms":get_rule_logs_for_vms, + "add_to_VCPUs_params_live":add_to_VCPUs_params_live, "setLinkLocalIP":setLinkLocalIP, "cleanup_rules":cleanup_rules, "bumpUpPriority":bumpUpPriority, diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 1a57a64488d8..ba4ae623abef 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -177,7 +177,8 @@ public enum Config { RouterTemplateId("Advanced", NetworkManager.class, Long.class, "router.template.id", "1", "Default ID for template.", null), RouterExtraPublicNics("Advanced", NetworkManager.class, Integer.class, "router.extra.public.nics", "2", "specify extra public nics used for virtual router(up to 5)", "0-5"), StartRetry("Advanced", AgentManager.class, Integer.class, "start.retry", "10", "Number of times to retry create and start commands", null), - StopRetryInterval("Advanced", HighAvailabilityManager.class, Integer.class, "stop.retry.interval", "600", "Time in seconds between retries to stop or destroy a vm" , null), + ScaleRetry("Advanced", AgentManager.class, Integer.class, "scale.retry", "2", "Number of times to retry scaling up the vm", null), + StopRetryInterval("Advanced", HighAvailabilityManager.class, Integer.class, "stop.retry.interval", "600", "Time in seconds between retries to stop or destroy a vm" , null), StorageCleanupInterval("Advanced", StorageManager.class, Integer.class, "storage.cleanup.interval", "86400", "The interval (in seconds) to wait before running the storage cleanup thread.", null), StorageCleanupEnabled("Advanced", StorageManager.class, Boolean.class, "storage.cleanup.enabled", "true", "Enables/disables the storage cleanup thread.", null), UpdateWait("Advanced", AgentManager.class, Integer.class, "update.wait", "600", "Time to wait (in seconds) before alerting on a updating agent", null), diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index f80e0c253188..ecd7751553db 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -2115,6 +2115,7 @@ public List> getCommands() { cmdList.add(DestroyVMCmd.class); cmdList.add(GetVMPasswordCmd.class); cmdList.add(ListVMsCmd.class); + cmdList.add(ScaleVMCmd.class); cmdList.add(RebootVMCmd.class); cmdList.add(RemoveNicFromVMCmd.class); cmdList.add(ResetVMPasswordCmd.class); diff --git a/server/src/com/cloud/vm/ItWorkVO.java b/server/src/com/cloud/vm/ItWorkVO.java index 6f5349533813..d218880c9238 100644 --- a/server/src/com/cloud/vm/ItWorkVO.java +++ b/server/src/com/cloud/vm/ItWorkVO.java @@ -41,7 +41,8 @@ enum Step { Started, Release, Done, - Migrating + Migrating, + Reconfiguring } @Id diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 82a69bd24ba7..6e9cd43d88ed 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -32,22 +32,12 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.api.ApiDBUtils; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; -import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd; -import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; -import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; -import org.apache.cloudstack.api.command.user.vm.RebootVMCmd; -import org.apache.cloudstack.api.command.user.vm.RemoveNicFromVMCmd; -import org.apache.cloudstack.api.command.user.vm.ResetVMPasswordCmd; -import org.apache.cloudstack.api.command.user.vm.ResetVMSSHKeyCmd; -import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd; -import org.apache.cloudstack.api.command.user.vm.StartVMCmd; -import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; -import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; -import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; +import org.apache.cloudstack.api.command.user.vm.*; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd; import org.apache.cloudstack.engine.cloud.entity.api.VirtualMachineEntity; @@ -349,7 +339,7 @@ public enum UserVmCloneType { @Inject protected SecurityGroupDao _securityGroupDao; @Inject - protected CapacityManager _capacityMgr;; + protected CapacityManager _capacityMgr; @Inject protected VMInstanceDao _vmInstanceDao; @Inject @@ -397,6 +387,7 @@ public enum UserVmCloneType { protected String _instance; protected String _zone; protected boolean _instanceNameFlag; + protected int _scaleRetry; @Inject ConfigurationDao _configDao; private int _createprivatetemplatefromvolumewait; @@ -1047,6 +1038,78 @@ public UserVm updateDefaultNicForVirtualMachine(UpdateDefaultNicForVMCmd cmd) th throw new CloudRuntimeException("something strange happened, new default network(" + newdefault.getId() + ") is not null, and is not equal to the network(" + nic.getNetworkId() + ") of the chosen nic"); } + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_SCALE, eventDescription = "scaling Vm") + public UserVm + upgradeVirtualMachine(ScaleVMCmd cmd) throws InvalidParameterValueException { + Long vmId = cmd.getId(); + Long newServiceOfferingId = cmd.getServiceOfferingId(); + Account caller = UserContext.current().getCaller(); + + // Verify input parameters + VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId); + if(vmInstance.getHypervisorType() != HypervisorType.XenServer){ + throw new InvalidParameterValueException("This operation not permitted for this hypervisor of the vm"); + } + + _accountMgr.checkAccess(caller, null, true, vmInstance); + + // Check that the specified service offering ID is valid + _itMgr.checkIfCanUpgrade(vmInstance, newServiceOfferingId); + + //Check if its a scale "up" + ServiceOffering newServiceOffering = _configMgr.getServiceOffering(newServiceOfferingId); + ServiceOffering oldServiceOffering = _configMgr.getServiceOffering(vmInstance.getServiceOfferingId()); + if(newServiceOffering.getSpeed() <= oldServiceOffering.getSpeed() + && newServiceOffering.getRamSize() <= oldServiceOffering.getRamSize()){ + throw new InvalidParameterValueException("Only scaling up the vm is supported, new service offering should have both cpu and memory greater than the old values"); + } + + // Dynamically upgrade the running vms + if(vmInstance.getState().equals(State.Running)){ + boolean success = false; + int retry = _scaleRetry; + while (retry-- != 0) { // It's != so that it can match -1. + try{ + // #1 Check existing host has capacity + boolean existingHostHasCapacity = _capacityMgr.checkIfHostHasCapacity(vmInstance.getHostId(), newServiceOffering.getSpeed() - oldServiceOffering.getSpeed(), + (newServiceOffering.getRamSize() - oldServiceOffering.getRamSize()) * 1024L * 1024L, false, ApiDBUtils.getCpuOverprovisioningFactor(), 1f, false); // TO DO fill it with mem. + + // #2 migrate the vm if host doesn't have capacity + if (!existingHostHasCapacity){ + vmInstance = _itMgr.findHostAndMigrate(vmInstance.getType(), vmInstance, newServiceOfferingId); + } + + // #3 scale the vm now + _itMgr.upgradeVmDb(vmId, newServiceOfferingId); + vmInstance = _vmInstanceDao.findById(vmId); + vmInstance = _itMgr.reConfigureVm(vmInstance, oldServiceOffering, existingHostHasCapacity); + success = true; + return _vmDao.findById(vmInstance.getId()); + }catch(InsufficientCapacityException e ){ + s_logger.warn("Received exception while scaling ",e); + } catch (ResourceUnavailableException e) { + s_logger.warn("Received exception while scaling ",e); + } catch (ConcurrentOperationException e) { + s_logger.warn("Received exception while scaling ",e); + } catch (VirtualMachineMigrationException e) { + s_logger.warn("Received exception while scaling ",e); + } catch (ManagementServerException e) { + s_logger.warn("Received exception while scaling ",e); + }finally{ + if(!success){ + _itMgr.upgradeVmDb(vmId, oldServiceOffering.getId()); // rollback + } + } + } + if (!success) + return null; + } + + return _vmDao.findById(vmInstance.getId()); + + } + @Override public HashMap getVirtualMachineStatistics(long hostId, String hostName, List vmIds) throws CloudRuntimeException { @@ -1240,6 +1303,7 @@ public boolean configure(String name, Map params) new UserVmStateListener(_usageEventDao, _networkDao, _nicDao)); value = _configDao.getValue(Config.SetVmInternalNameUsingDisplayName.key()); + if(value == null) { _instanceNameFlag = false; } @@ -1248,6 +1312,8 @@ public boolean configure(String name, Map params) _instanceNameFlag = Boolean.parseBoolean(value); } + _scaleRetry = NumbersUtil.parseInt(configs.get(Config.ScaleRetry.key()), 2); + s_logger.info("User VM Manager is configured."); return true; diff --git a/server/src/com/cloud/vm/VirtualMachineManager.java b/server/src/com/cloud/vm/VirtualMachineManager.java index 7b34f7f06164..4a30d97cc540 100644 --- a/server/src/com/cloud/vm/VirtualMachineManager.java +++ b/server/src/com/cloud/vm/VirtualMachineManager.java @@ -186,4 +186,16 @@ NicProfile addVmToNetwork(VirtualMachine vm, Network network, NicProfile request */ VirtualMachineTO toVmTO(VirtualMachineProfile profile); + + VMInstanceVO reConfigureVm(VMInstanceVO vm, ServiceOffering newServiceOffering, boolean sameHost) + throws ResourceUnavailableException, ConcurrentOperationException; + + VMInstanceVO findHostAndMigrate(VirtualMachine.Type vmType, VMInstanceVO vm, Long newSvcOfferingId) throws InsufficientCapacityException, + ConcurrentOperationException, ResourceUnavailableException, + VirtualMachineMigrationException, ManagementServerException; + + T migrateForScale(T vm, long srcHostId, DeployDestination dest, Long newSvcOfferingId) + throws ResourceUnavailableException, ConcurrentOperationException, + ManagementServerException, VirtualMachineMigrationException; + } diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index 0aeef0e58e15..a6d0b1b5d11d 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -36,37 +36,18 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.capacity.CapacityManager; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import com.cloud.dc.*; +import com.cloud.agent.api.*; import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; import com.cloud.agent.AgentManager.OnError; import com.cloud.agent.Listener; -import com.cloud.agent.api.AgentControlAnswer; -import com.cloud.agent.api.AgentControlCommand; -import com.cloud.agent.api.Answer; -import com.cloud.agent.api.CheckVirtualMachineAnswer; -import com.cloud.agent.api.CheckVirtualMachineCommand; -import com.cloud.agent.api.ClusterSyncAnswer; -import com.cloud.agent.api.ClusterSyncCommand; -import com.cloud.agent.api.Command; -import com.cloud.agent.api.MigrateAnswer; -import com.cloud.agent.api.MigrateCommand; -import com.cloud.agent.api.PingRoutingCommand; -import com.cloud.agent.api.PrepareForMigrationAnswer; -import com.cloud.agent.api.PrepareForMigrationCommand; -import com.cloud.agent.api.RebootAnswer; -import com.cloud.agent.api.RebootCommand; -import com.cloud.agent.api.StartAnswer; -import com.cloud.agent.api.StartCommand; -import com.cloud.agent.api.StartupCommand; -import com.cloud.agent.api.StartupRoutingCommand; import com.cloud.agent.api.StartupRoutingCommand.VmState; -import com.cloud.agent.api.StopAnswer; -import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.manager.Commands; @@ -195,6 +176,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject protected UserVmDao _userVmDao; @Inject + protected CapacityManager _capacityMgr; + @Inject protected NicDao _nicsDao; @Inject protected AccountManager _accountMgr; @@ -2422,13 +2405,12 @@ public void checkIfCanUpgrade(VirtualMachine vmInstance, long newServiceOffering if (newServiceOffering == null) { throw new InvalidParameterValueException("Unable to find a service offering with id " + newServiceOfferingId); } - - // Check that the VM is stopped - if (!vmInstance.getState().equals(State.Stopped)) { + + // Check that the VM is stopped / running + if (!(vmInstance.getState().equals(State.Stopped) || vmInstance.getState().equals(State.Running) )) { s_logger.warn("Unable to upgrade virtual machine " + vmInstance.toString() + " in state " + vmInstance.getState()); - throw new InvalidParameterValueException("Unable to upgrade virtual machine " + vmInstance.toString() + " " + - "in state " + vmInstance.getState() - + "; make sure the virtual machine is stopped and not in an error state before upgrading."); + throw new InvalidParameterValueException("Unable to upgrade virtual machine " + vmInstance.toString() + " " + " in state " + vmInstance.getState() + + "; make sure the virtual machine is stopped/running"); } // Check if the service offering being upgraded to is what the VM is already running with @@ -2679,4 +2661,277 @@ public boolean removeVmFromNetwork(VirtualMachine vm, Network network, URI broad return true; } -} + @Override + public VMInstanceVO findHostAndMigrate(VirtualMachine.Type vmType, VMInstanceVO vm, Long newSvcOfferingId) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, VirtualMachineMigrationException, ManagementServerException { + + VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + + Long srcHostId = vm.getHostId(); + Long oldSvcOfferingId = vm.getServiceOfferingId(); + if (srcHostId == null) { + throw new CloudRuntimeException("Unable to scale the vm because it doesn't have a host id"); + } + Host host = _hostDao.findById(srcHostId); + DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), null, null, null); + ExcludeList excludes = new ExcludeList(); + excludes.addHost(vm.getHostId()); + vm.setServiceOfferingId(newSvcOfferingId); // Need to find the destination host based on new svc offering + + DeployDestination dest = null; + + for (DeploymentPlanner planner : _planners) { + if (planner.canHandle(profile, plan, excludes)) { + dest = planner.plan(profile, plan, excludes); + } else { + continue; + } + + if (dest != null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Planner " + planner + " found " + dest + " for scaling the vm to."); + } + break; + } + if (s_logger.isDebugEnabled()) { + s_logger.debug("Planner " + planner + " was unable to find anything."); + } + } + + if (dest == null) { + throw new InsufficientServerCapacityException("Unable to find a server to scale the vm to.", host.getClusterId()); + } + + excludes.addHost(dest.getHost().getId()); + VMInstanceVO vmInstance = null; + try { + vmInstance = migrateForScale(vm, srcHostId, dest, oldSvcOfferingId); + } catch (ResourceUnavailableException e) { + s_logger.debug("Unable to migrate to unavailable " + dest); + throw e; + } catch (ConcurrentOperationException e) { + s_logger.debug("Unable to migrate VM due to: " + e.getMessage()); + throw e; + } catch (ManagementServerException e) { + s_logger.debug("Unable to migrate VM: " + e.getMessage()); + throw e; + } catch (VirtualMachineMigrationException e) { + s_logger.debug("Got VirtualMachineMigrationException, Unable to migrate: " + e.getMessage()); + if (vm.getState() == State.Starting) { + s_logger.debug("VM seems to be still Starting, we should retry migration later"); + throw e; + } else { + s_logger.debug("Unable to migrate VM, VM is not in Running or even Starting state, current state: " + vm.getState().toString()); + } + } + if (vmInstance != null) { + return vmInstance; + }else{ + return null; + } + } + + @Override + public T migrateForScale(T vm, long srcHostId, DeployDestination dest, Long oldSvcOfferingId) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, + VirtualMachineMigrationException { + s_logger.info("Migrating " + vm + " to " + dest); + + Long newSvcOfferingId = vm.getServiceOfferingId(); + long dstHostId = dest.getHost().getId(); + Host fromHost = _hostDao.findById(srcHostId); + if (fromHost == null) { + s_logger.info("Unable to find the host to migrate from: " + srcHostId); + throw new CloudRuntimeException("Unable to find the host to migrate from: " + srcHostId); + } + + if (fromHost.getClusterId().longValue() != dest.getCluster().getId()) { + s_logger.info("Source and destination host are not in same cluster, unable to migrate to host: " + dest.getHost().getId()); + throw new CloudRuntimeException("Source and destination host are not in same cluster, unable to migrate to host: " + dest.getHost().getId()); + } + + VirtualMachineGuru vmGuru = getVmGuru(vm); + + long vmId = vm.getId(); + vm = vmGuru.findById(vmId); + if (vm == null) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Unable to find the vm " + vm); + } + throw new ManagementServerException("Unable to find a virtual machine with id " + vmId); + } + + if (vm.getState() != State.Running) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("VM is not Running, unable to migrate the vm " + vm); + } + throw new VirtualMachineMigrationException("VM is not Running, unable to migrate the vm currently " + vm + " , current state: " + vm.getState().toString()); + } + + short alertType = AlertManager.ALERT_TYPE_USERVM_MIGRATE; + if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) { + alertType = AlertManager.ALERT_TYPE_DOMAIN_ROUTER_MIGRATE; + } else if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) { + alertType = AlertManager.ALERT_TYPE_CONSOLE_PROXY_MIGRATE; + } + + VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + _networkMgr.prepareNicForMigration(profile, dest); + this.volumeMgr.prepareForMigration(profile, dest); + + VirtualMachineTO to = toVmTO(profile); + PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(to); + + ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Migrating, vm.getType(), vm.getId()); + work.setStep(Step.Prepare); + work.setResourceType(ItWorkVO.ResourceType.Host); + work.setResourceId(dstHostId); + work = _workDao.persist(work); + + PrepareForMigrationAnswer pfma = null; + try { + pfma = (PrepareForMigrationAnswer) _agentMgr.send(dstHostId, pfmc); + if (!pfma.getResult()) { + String msg = "Unable to prepare for migration due to " + pfma.getDetails(); + pfma = null; + throw new AgentUnavailableException(msg, dstHostId); + } + } catch (OperationTimedoutException e1) { + throw new AgentUnavailableException("Operation timed out", dstHostId); + } finally { + if (pfma == null) { + work.setStep(Step.Done); + _workDao.update(work.getId(), work); + } + } + + vm.setLastHostId(srcHostId); + try { + if (vm == null || vm.getHostId() == null || vm.getHostId() != srcHostId || !changeState(vm, Event.MigrationRequested, dstHostId, work, Step.Migrating)) { + s_logger.info("Migration cancelled because state has changed: " + vm); + throw new ConcurrentOperationException("Migration cancelled because state has changed: " + vm); + } + } catch (NoTransitionException e1) { + s_logger.info("Migration cancelled because " + e1.getMessage()); + throw new ConcurrentOperationException("Migration cancelled because " + e1.getMessage()); + } + + boolean migrated = false; + try { + boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); + MigrateCommand mc = new MigrateCommand(vm.getInstanceName(), dest.getHost().getPrivateIpAddress(), isWindows); + mc.setHostGuid(dest.getHost().getGuid()); + + try { + MigrateAnswer ma = (MigrateAnswer) _agentMgr.send(vm.getLastHostId(), mc); + if (!ma.getResult()) { + s_logger.error("Unable to migrate due to " + ma.getDetails()); + return null; + } + } catch (OperationTimedoutException e) { + if (e.isActive()) { + s_logger.warn("Active migration command so scheduling a restart for " + vm); + _haMgr.scheduleRestart(vm, true); + } + throw new AgentUnavailableException("Operation timed out on migrating " + vm, dstHostId); + } + + try { + long newServiceOfferingId = vm.getServiceOfferingId(); + vm.setServiceOfferingId(oldSvcOfferingId); // release capacity for the old service offering only + if (!changeState(vm, VirtualMachine.Event.OperationSucceeded, dstHostId, work, Step.Started)) { + throw new ConcurrentOperationException("Unable to change the state for " + vm); + } + vm.setServiceOfferingId(newServiceOfferingId); + } catch (NoTransitionException e1) { + throw new ConcurrentOperationException("Unable to change state due to " + e1.getMessage()); + } + + try { + if (!checkVmOnHost(vm, dstHostId)) { + s_logger.error("Unable to complete migration for " + vm); + try { + _agentMgr.send(srcHostId, new Commands(cleanup(vm.getInstanceName())), null); + } catch (AgentUnavailableException e) { + s_logger.error("AgentUnavailableException while cleanup on source host: " + srcHostId); + } + cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, Event.AgentReportStopped, true, _accountMgr.getSystemUser(), _accountMgr.getSystemAccount()); + return null; + } + } catch (OperationTimedoutException e) { + } + + migrated = true; + return vm; + } finally { + if (!migrated) { + s_logger.info("Migration was unsuccessful. Cleaning up: " + vm); + + _alertMgr.sendAlert(alertType, fromHost.getDataCenterId(), fromHost.getPodId(), "Unable to migrate vm " + vm.getInstanceName() + " from host " + fromHost.getName() + " in zone " + + dest.getDataCenter().getName() + " and pod " + dest.getPod().getName(), "Migrate Command failed. Please check logs."); + try { + _agentMgr.send(dstHostId, new Commands(cleanup(vm.getInstanceName())), null); + } catch (AgentUnavailableException ae) { + s_logger.info("Looks like the destination Host is unavailable for cleanup"); + } + + try { + stateTransitTo(vm, Event.OperationFailed, srcHostId); + } catch (NoTransitionException e) { + s_logger.warn(e.getMessage()); + } + } + + work.setStep(Step.Done); + _workDao.update(work.getId(), work); + } + } + @Override + public VMInstanceVO reConfigureVm(VMInstanceVO vm , ServiceOffering oldServiceOffering, boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException { + + long newServiceofferingId = vm.getServiceOfferingId(); + ServiceOffering newServiceOffering = _configMgr.getServiceOffering(newServiceofferingId); + ScaleVmCommand reconfigureCmd = new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), + newServiceOffering.getSpeed(), newServiceOffering.getRamSize(), newServiceOffering.getRamSize(), newServiceOffering.getLimitCpuUse()); + + Long dstHostId = vm.getHostId(); + ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Running, vm.getType(), vm.getId()); + work.setStep(Step.Prepare); + work.setResourceType(ItWorkVO.ResourceType.Host); + work.setResourceId(vm.getHostId()); + work = _workDao.persist(work); + boolean success = false; + try { + if(reconfiguringOnExistingHost){ + vm.setServiceOfferingId(oldServiceOffering.getId()); + _capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); //release the old capacity + vm.setServiceOfferingId(newServiceofferingId); + _capacityMgr.allocateVmCapacity(vm, false); // lock the new capacity + } + + Answer reconfigureAnswer = _agentMgr.send(vm.getHostId(), reconfigureCmd); + if (reconfigureAnswer == null || !reconfigureAnswer.getResult()) { + s_logger.error("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); + throw new CloudRuntimeException("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); + } + + success = true; + } catch (OperationTimedoutException e) { + throw new AgentUnavailableException("Operation timed out on reconfiguring " + vm, dstHostId); + } catch (AgentUnavailableException e) { + throw e; + } finally{ + // work.setStep(Step.Done); + //_workDao.update(work.getId(), work); + if(!success){ + _capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); // release the new capacity + vm.setServiceOfferingId(oldServiceOffering.getId()); + _capacityMgr.allocateVmCapacity(vm, false); // allocate the old capacity + } + } + + return vm; + + } + + + } diff --git a/server/test/com/cloud/vm/MockUserVmManagerImpl.java b/server/test/com/cloud/vm/MockUserVmManagerImpl.java index 09825a8eeb6c..dd8dd83df587 100644 --- a/server/test/com/cloud/vm/MockUserVmManagerImpl.java +++ b/server/test/com/cloud/vm/MockUserVmManagerImpl.java @@ -25,18 +25,7 @@ import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; -import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd; -import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; -import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; -import org.apache.cloudstack.api.command.user.vm.RebootVMCmd; -import org.apache.cloudstack.api.command.user.vm.RemoveNicFromVMCmd; -import org.apache.cloudstack.api.command.user.vm.ResetVMPasswordCmd; -import org.apache.cloudstack.api.command.user.vm.ResetVMSSHKeyCmd; -import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd; -import org.apache.cloudstack.api.command.user.vm.StartVMCmd; -import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; -import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; -import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; +import org.apache.cloudstack.api.command.user.vm.*; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd; import org.springframework.stereotype.Component; @@ -391,6 +380,10 @@ public UserVm restoreVM(RestoreVMCmd cmd) { return null; } + @Override + public UserVm upgradeVirtualMachine(ScaleVMCmd scaleVMCmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException { + return null; //To change body of implemented methods use File | Settings | File Templates. + } @Override diff --git a/server/test/com/cloud/vm/MockVirtualMachineManagerImpl.java b/server/test/com/cloud/vm/MockVirtualMachineManagerImpl.java index 612c70d02c87..4917e77cdef3 100755 --- a/server/test/com/cloud/vm/MockVirtualMachineManagerImpl.java +++ b/server/test/com/cloud/vm/MockVirtualMachineManagerImpl.java @@ -265,6 +265,23 @@ public VirtualMachineTO toVmTO(VirtualMachineProfile pro return null; } + @Override + public VMInstanceVO reConfigureVm(VMInstanceVO vm, ServiceOffering newServiceOffering, boolean sameHost) throws ResourceUnavailableException, ConcurrentOperationException { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public VMInstanceVO findHostAndMigrate(VirtualMachine.Type vmType, VMInstanceVO vm, Long newSvcOfferingId) throws InsufficientCapacityException, + ConcurrentOperationException, ResourceUnavailableException, + VirtualMachineMigrationException, ManagementServerException{ + return null; + } + + @Override + public T migrateForScale(T vm, long srcHostId, DeployDestination dest, Long newSvcOfferingId) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + /* (non-Javadoc) * @see com.cloud.vm.VirtualMachineManager#addVmToNetwork(com.cloud.vm.VirtualMachine, com.cloud.network.Network, com.cloud.vm.NicProfile) */ diff --git a/server/test/com/cloud/vm/UserVmManagerTest.java b/server/test/com/cloud/vm/UserVmManagerTest.java index 0795a359fdd0..bb1c07b27843 100755 --- a/server/test/com/cloud/vm/UserVmManagerTest.java +++ b/server/test/com/cloud/vm/UserVmManagerTest.java @@ -17,10 +17,21 @@ package com.cloud.vm; +import java.lang.reflect.Field; import java.util.List; +import com.cloud.api.ApiDBUtils; +import com.cloud.capacity.CapacityManager; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.offering.ServiceOffering; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.user.*; +import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd; +import org.apache.cloudstack.api.command.user.vm.ScaleVMCmd; import org.apache.log4j.Logger; import org.junit.Test; import org.junit.Before; @@ -39,10 +50,6 @@ import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VolumeDao; -import com.cloud.user.Account; -import com.cloud.user.AccountManager; -import com.cloud.user.AccountVO; -import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.exception.CloudRuntimeException; @@ -57,15 +64,21 @@ public class UserVmManagerTest { @Mock VolumeManager _storageMgr; @Mock Account _account; @Mock AccountManager _accountMgr; + @Mock ConfigurationManager _configMgr; + @Mock CapacityManager _capacityMgr; @Mock AccountDao _accountDao; + @Mock + ConfigurationDao _configDao; @Mock UserDao _userDao; @Mock UserVmDao _vmDao; + @Mock VMInstanceDao _vmInstanceDao; @Mock VMTemplateDao _templateDao; @Mock VolumeDao _volsDao; @Mock RestoreVMCmd _restoreVMCmd; @Mock AccountVO _accountMock; @Mock UserVO _userMock; @Mock UserVmVO _vmMock; + @Mock VMInstanceVO _vmInstance; @Mock VMTemplateVO _templateMock; @Mock VolumeVO _volumeMock; @Mock List _rootVols; @@ -74,6 +87,7 @@ public void setup(){ MockitoAnnotations.initMocks(this); _userVmMgr._vmDao = _vmDao; + _userVmMgr._vmInstanceDao = _vmInstanceDao; _userVmMgr._templateDao = _templateDao; _userVmMgr._volsDao = _volsDao; _userVmMgr._itMgr = _itMgr; @@ -81,6 +95,9 @@ public void setup(){ _userVmMgr._accountDao = _accountDao; _userVmMgr._userDao = _userDao; _userVmMgr._accountMgr = _accountMgr; + _userVmMgr._configMgr = _configMgr; + _userVmMgr._capacityMgr = _capacityMgr; + _userVmMgr._scaleRetry = 2; doReturn(3L).when(_account).getId(); doReturn(8L).when(_vmMock).getAccountId(); @@ -88,6 +105,8 @@ public void setup(){ when(_userDao.findById(anyLong())).thenReturn(_userMock); doReturn(Account.State.enabled).when(_account).getState(); when(_vmMock.getId()).thenReturn(314L); + when(_vmInstance.getId()).thenReturn(1L); + when(_vmInstance.getServiceOfferingId()).thenReturn(2L); } @@ -179,4 +198,169 @@ public void testRestoreVMF4() throws ResourceUnavailableException, Insufficient } + // Test scaleVm on incompatible HV. + @Test(expected=InvalidParameterValueException.class) + public void testScaleVMF1() throws Exception { + + ScaleVMCmd cmd = new ScaleVMCmd(); + Class _class = cmd.getClass(); + + Field idField = _class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(cmd, 1L); + + Field serviceOfferingIdField = _class.getDeclaredField("serviceOfferingId"); + serviceOfferingIdField.setAccessible(true); + serviceOfferingIdField.set(cmd, 1L); + + // UserContext.current().setEventDetails("Vm Id: "+getId()); + Account account = (Account) new AccountVO("testaccount", 1L, "networkdomain", (short) 0, "uuid"); + //AccountVO(String accountName, long domainId, String networkDomain, short type, int regionId) + UserContext.registerContext(1, account, null, true); + + when(_vmInstanceDao.findById(anyLong())).thenReturn(_vmInstance); + + _userVmMgr.upgradeVirtualMachine(cmd); + + } + + // Test scaleVm on incompatible HV. + @Test(expected=InvalidParameterValueException.class) + public void testScaleVMF2() throws Exception { + + ScaleVMCmd cmd = new ScaleVMCmd(); + Class _class = cmd.getClass(); + + Field idField = _class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(cmd, 1L); + + Field serviceOfferingIdField = _class.getDeclaredField("serviceOfferingId"); + serviceOfferingIdField.setAccessible(true); + serviceOfferingIdField.set(cmd, 1L); + + //UserContext.current().setEventDetails("Vm Id: "+getId()); + // Account account = (Account) new AccountVO("testaccount", 1L, "networkdomain", (short) 0, 1); + //AccountVO(String accountName, long domainId, String networkDomain, short type, int regionId) + // UserContext.registerContext(1, account, null, true); + + when(_vmInstanceDao.findById(anyLong())).thenReturn(_vmInstance); + doReturn(Hypervisor.HypervisorType.XenServer).when(_vmInstance).getHypervisorType(); + + + doNothing().when(_accountMgr).checkAccess(_account, null, true, _templateMock); + + doNothing().when(_itMgr).checkIfCanUpgrade(_vmMock, cmd.getServiceOfferingId()); + + + ServiceOffering so1 = (ServiceOffering) getSvcoffering(512); + ServiceOffering so2 = (ServiceOffering) getSvcoffering(256); + + when(_configMgr.getServiceOffering(anyLong())).thenReturn(so1); + when(_configMgr.getServiceOffering(1L)).thenReturn(so1); + + _userVmMgr.upgradeVirtualMachine(cmd); + + } + + // Test scaleVm for Stopped vm. Full positive test. + @Test + public void testScaleVMF3() throws Exception { + + ScaleVMCmd cmd = new ScaleVMCmd(); + Class _class = cmd.getClass(); + + Field idField = _class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(cmd, 1L); + + Field serviceOfferingIdField = _class.getDeclaredField("serviceOfferingId"); + serviceOfferingIdField.setAccessible(true); + serviceOfferingIdField.set(cmd, 1L); + + //UserContext.current().setEventDetails("Vm Id: "+getId()); + //Account account = (Account) new AccountVO("testaccount", 1L, "networkdomain", (short) 0, 1); + //AccountVO(String accountName, long domainId, String networkDomain, short type, int regionId) + //UserContext.registerContext(1, account, null, true); + + when(_vmInstanceDao.findById(anyLong())).thenReturn(_vmInstance); + doReturn(Hypervisor.HypervisorType.XenServer).when(_vmInstance).getHypervisorType(); + + + ServiceOffering so1 = (ServiceOffering) getSvcoffering(512); + ServiceOffering so2 = (ServiceOffering) getSvcoffering(256); + + when(_configMgr.getServiceOffering(anyLong())).thenReturn(so2); + when(_configMgr.getServiceOffering(1L)).thenReturn(so1); + + doReturn(VirtualMachine.State.Stopped).when(_vmInstance).getState(); + + doReturn(true).when(_itMgr).upgradeVmDb(anyLong(),anyLong()); + + when(_vmDao.findById(anyLong())).thenReturn(_vmMock); + + _userVmMgr.upgradeVirtualMachine(cmd); + + } + + // Test scaleVm for Running vm. Full positive test. + public void testScaleVMF4() throws Exception { + + ScaleVMCmd cmd = new ScaleVMCmd(); + Class _class = cmd.getClass(); + + Field idField = _class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(cmd, 1L); + + Field serviceOfferingIdField = _class.getDeclaredField("serviceOfferingId"); + serviceOfferingIdField.setAccessible(true); + serviceOfferingIdField.set(cmd, 1L); + + //UserContext.current().setEventDetails("Vm Id: "+getId()); + //Account account = (Account) new AccountVO("testaccount", 1L, "networkdomain", (short) 0, 1); + //AccountVO(String accountName, long domainId, String networkDomain, short type, int regionId) + //UserContext.registerContext(1, account, null, true); + + when(_vmInstanceDao.findById(anyLong())).thenReturn(_vmInstance); + doReturn(Hypervisor.HypervisorType.XenServer).when(_vmInstance).getHypervisorType(); + + ServiceOffering so1 = (ServiceOffering) getSvcoffering(512); + ServiceOffering so2 = (ServiceOffering) getSvcoffering(256); + + when(_configMgr.getServiceOffering(anyLong())).thenReturn(so2); + when(_configMgr.getServiceOffering(1L)).thenReturn(so1); + + doReturn(VirtualMachine.State.Running).when(_vmInstance).getState(); + + //when(ApiDBUtils.getCpuOverprovisioningFactor()).thenReturn(3f); + when(_capacityMgr.checkIfHostHasCapacity(anyLong(), anyInt(), anyLong(), anyBoolean(), anyFloat(), anyFloat(), anyBoolean())).thenReturn(false); + when(_itMgr.reConfigureVm(_vmInstance, so1, false)).thenReturn(_vmInstance); + + doReturn(true).when(_itMgr).upgradeVmDb(anyLong(), anyLong()); + + when(_vmDao.findById(anyLong())).thenReturn(_vmMock); + + _userVmMgr.upgradeVirtualMachine(cmd); + + } + + private ServiceOfferingVO getSvcoffering(int ramSize){ + + long id = 4L; + String name = "name"; + String displayText = "displayText"; + int cpu = 1; + //int ramSize = 256; + int speed = 128; + + boolean ha = false; + boolean useLocalStorage = false; + + ServiceOfferingVO serviceOffering = new ServiceOfferingVO(name, cpu, ramSize, speed, null, null, ha, displayText, useLocalStorage, false, null, false, null, false); + return serviceOffering; + } + + + } \ No newline at end of file diff --git a/server/test/com/cloud/vm/VirtualMachineManagerImplTest.java b/server/test/com/cloud/vm/VirtualMachineManagerImplTest.java new file mode 100644 index 000000000000..322f051c6751 --- /dev/null +++ b/server/test/com/cloud/vm/VirtualMachineManagerImplTest.java @@ -0,0 +1,208 @@ +// 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; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ScaleVmAnswer; +import com.cloud.agent.api.ScaleVmCommand; +import com.cloud.capacity.CapacityManager; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VolumeManager; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.*; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.api.command.user.vm.RestoreVMCmd; +import org.apache.cloudstack.api.command.user.vm.ScaleVMCmd; +import org.junit.Test; +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import static org.mockito.Mockito.*; + + +import java.lang.reflect.Field; +import java.util.List; + +public class VirtualMachineManagerImplTest { + + + @Spy VirtualMachineManagerImpl _vmMgr = new VirtualMachineManagerImpl(); + @Mock + VolumeManager _storageMgr; + @Mock + Account _account; + @Mock + AccountManager _accountMgr; + @Mock + ConfigurationManager _configMgr; + @Mock + CapacityManager _capacityMgr; + @Mock + AgentManager _agentMgr; + @Mock + AccountDao _accountDao; + @Mock + ConfigurationDao _configDao; + @Mock + HostDao _hostDao; + @Mock + UserDao _userDao; + @Mock + UserVmDao _vmDao; + @Mock + ItWorkDao _workDao; + @Mock + VMInstanceDao _vmInstanceDao; + @Mock + VMTemplateDao _templateDao; + @Mock + VolumeDao _volsDao; + @Mock + RestoreVMCmd _restoreVMCmd; + @Mock + AccountVO _accountMock; + @Mock + UserVO _userMock; + @Mock + UserVmVO _vmMock; + @Mock + VMInstanceVO _vmInstance; + @Mock + HostVO _host; + @Mock + VMTemplateVO _templateMock; + @Mock + VolumeVO _volumeMock; + @Mock + List _rootVols; + @Mock + ItWorkVO _work; + @Before + public void setup(){ + MockitoAnnotations.initMocks(this); + + _vmMgr._templateDao = _templateDao; + _vmMgr._volsDao = _volsDao; + _vmMgr.volumeMgr = _storageMgr; + _vmMgr._accountDao = _accountDao; + _vmMgr._userDao = _userDao; + _vmMgr._accountMgr = _accountMgr; + _vmMgr._configMgr = _configMgr; + _vmMgr._capacityMgr = _capacityMgr; + _vmMgr._hostDao = _hostDao; + _vmMgr._nodeId = 1L; + _vmMgr._workDao = _workDao; + _vmMgr._agentMgr = _agentMgr; + + when(_vmMock.getId()).thenReturn(314l); + when(_vmInstance.getId()).thenReturn(1L); + when(_vmInstance.getServiceOfferingId()).thenReturn(2L); + when(_vmInstance.getInstanceName()).thenReturn("myVm"); + when(_vmInstance.getHostId()).thenReturn(2L); + when(_vmInstance.getType()).thenReturn(VirtualMachine.Type.User); + when(_host.getId()).thenReturn(1L); + when(_hostDao.findById(anyLong())).thenReturn(null); + when(_configMgr.getServiceOffering(anyLong())).thenReturn(getSvcoffering(512)); + when(_workDao.persist(_work)).thenReturn(_work); + when(_workDao.update("1", _work)).thenReturn(true); + when(_work.getId()).thenReturn("1"); + doNothing().when(_work).setStep(ItWorkVO.Step.Done); + //doNothing().when(_volsDao).detachVolume(anyLong()); + //when(_work.setStep(ItWorkVO.Step.Done)).thenReturn("1"); + + } + + + @Test(expected=CloudRuntimeException.class) + public void testScaleVM1() throws Exception { + + + DeployDestination dest = new DeployDestination(null, null, null, _host); + long l = 1L; + + when(_vmInstanceDao.findById(anyLong())).thenReturn(_vmInstance); + _vmMgr.migrateForScale(_vmInstance, l, dest, l); + + } + + @Test (expected=CloudRuntimeException.class) + public void testScaleVM2() throws Exception { + + DeployDestination dest = new DeployDestination(null, null, null, _host); + long l = 1L; + + when(_vmInstanceDao.findById(anyLong())).thenReturn(_vmInstance); + ServiceOfferingVO newServiceOffering = getSvcoffering(512); + ScaleVmCommand reconfigureCmd = new ScaleVmCommand("myVmName", newServiceOffering.getCpu(), + newServiceOffering.getSpeed(), newServiceOffering.getRamSize(), newServiceOffering.getRamSize(), newServiceOffering.getLimitCpuUse()); + Answer answer = new ScaleVmAnswer(reconfigureCmd, true, "details"); + when(_agentMgr.send(2l, reconfigureCmd)).thenReturn(null); + _vmMgr.reConfigureVm(_vmInstance, getSvcoffering(256), false); + + } + + @Test (expected=CloudRuntimeException.class) + public void testScaleVM3() throws Exception { + + /*VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + + Long srcHostId = vm.getHostId(); + Long oldSvcOfferingId = vm.getServiceOfferingId(); + if (srcHostId == null) { + throw new CloudRuntimeException("Unable to scale the vm because it doesn't have a host id"); + }*/ + + when(_vmInstance.getHostId()).thenReturn(null); + when(_vmInstanceDao.findById(anyLong())).thenReturn(_vmInstance); + _vmMgr.findHostAndMigrate(VirtualMachine.Type.User, _vmInstance, 2l); + + } + + + private ServiceOfferingVO getSvcoffering(int ramSize){ + + long id = 4L; + String name = "name"; + String displayText = "displayText"; + int cpu = 1; + //int ramSize = 256; + int speed = 128; + + boolean ha = false; + boolean useLocalStorage = false; + + ServiceOfferingVO serviceOffering = new ServiceOfferingVO(name, cpu, ramSize, speed, null, null, ha, displayText, useLocalStorage, false, null, false, null, false); + return serviceOffering; + } + + +} diff --git a/test/integration/smoke/test_ScaleVm.py b/test/integration/smoke/test_ScaleVm.py new file mode 100644 index 000000000000..710d2add7b6e --- /dev/null +++ b/test/integration/smoke/test_ScaleVm.py @@ -0,0 +1,221 @@ +# 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. +""" P1 tests for Scaling up Vm +""" +#Import Local Modules +import marvin +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.remoteSSHClient import remoteSSHClient +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from nose.plugins.attrib import attr +#Import System modules +import time + +_multiprocess_shared_ = True +class Services: + """Test VM Life Cycle Services + """ + + def __init__(self): + self.services = { + + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended in create account to + # ensure unique username generated each time + "password": "password", + }, + "small": + # Create a small virtual machine instance with disk offering + { + "displayname": "testserver", + "username": "root", # VM creds for SSH + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "service_offerings": + { + "small": + { + # Small service offering ID to for change VM + # service offering from medium to small + "name": "SmallInstance", + "displaytext": "SmallInstance", + "cpunumber": 1, + "cpuspeed": 100, + "memory": 256, + }, + "big": + { + # Big service offering ID to for change VM + "name": "BigInstance", + "displaytext": "BigInstance", + "cpunumber": 1, + "cpuspeed": 100, + "memory": 512, + } + }, + #Change this + "template": { + "displaytext": "xs", + "name": "xs", + "passwordenabled": False, + }, + "diskdevice": '/dev/xvdd', + # Disk device where ISO is attached to instance + "mount_dir": "/mnt/tmp", + "sleep": 60, + "timeout": 10, + #Migrate VM to hostid + "ostype": 'CentOS 5.6 (64-bit)', + # CentOS 5.3 (64-bit) + } + +class TestScaleVm(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super(TestScaleVm, cls).getClsTestClient().getApiClient() + cls.services = Services().services + + # Get Zone, Domain and templates + domain = get_domain(cls.api_client, cls.services) + zone = get_zone(cls.api_client, cls.services) + cls.services['mode'] = zone.networktype + + template = get_template( + cls.api_client, + zone.id, + cls.services["ostype"] + ) + # Set Zones and disk offerings ?? + cls.services["small"]["zoneid"] = zone.id + cls.services["small"]["template"] = template.id + + # Create account, service offerings, vm. + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=domain.id + ) + + cls.small_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offerings"]["small"] + ) + + cls.big_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offerings"]["big"] + ) + + #create a virtual machine + cls.virtual_machine = VirtualMachine.create( + cls.api_client, + cls.services["small"], + accountid=cls.account.account.name, + domainid=cls.account.account.domainid, + serviceofferingid=cls.small_offering.id, + mode=cls.services["mode"] + ) + #how does it work ?? + cls._cleanup = [ + cls.small_offering, + cls.account + ] + + @classmethod + def tearDownClass(cls): + cls.api_client = super(TestScaleVm, cls).getClsTestClient().getApiClient() + cleanup_resources(cls.api_client, cls._cleanup) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + def tearDown(self): + #Clean up, terminate the created ISOs + cleanup_resources(self.apiclient, self.cleanup) + return + + @attr(tags = ["advanced", "basic", "multicluster", "storagemotion", "xenserver"]) + def test_01_scale_vm(self): + """Test scale virtual machine + """ + # Validate the following + # Scale up the vm and see if it scales to the new svc offering and is finally in running state + + + + self.debug("Scaling VM-ID: %s to service offering: %s" % ( + self.virtual_machine.id, + self.big_offering.id + )) + + cmd = scaleVirtualMachine.scaleVirtualMachineCmd() + cmd.serviceofferingid = self.big_offering.id + cmd.id = self.virtual_machine.id + self.apiclient.scaleVirtualMachine(cmd) + + list_vm_response = list_virtual_machines( + self.apiclient, + id=self.virtual_machine.id + ) + self.assertEqual( + isinstance(list_vm_response, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + list_vm_response, + None, + "Check virtual machine is listVirtualMachines" + ) + + vm_response = list_vm_response[0] + + self.assertEqual( + vm_response.id, + self.virtual_machine.id, + "Check virtual machine ID of scaled VM" + ) + + self.assertEqual( + vm_response.serviceofferingid, + self.big_offering.id, + "Check service offering of the VM" + ) + + self.assertEqual( + vm_response.state, + 'Running', + "Check the state of VM" + ) + return