diff --git a/api/src/com/cloud/api/commands/ListHostsCmd.java b/api/src/com/cloud/api/commands/ListHostsCmd.java index ba4586a984c..cb6b0784db5 100644 --- a/api/src/com/cloud/api/commands/ListHostsCmd.java +++ b/api/src/com/cloud/api/commands/ListHostsCmd.java @@ -27,10 +27,14 @@ import com.cloud.api.ApiConstants; import com.cloud.api.BaseListCmd; import com.cloud.api.Implementation; import com.cloud.api.Parameter; +import com.cloud.api.BaseCmd.CommandType; import com.cloud.api.response.HostResponse; import com.cloud.api.response.ListResponse; import com.cloud.async.AsyncJob; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.host.Host; +import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; @Implementation(description="Lists hosts.", responseObject=HostResponse.class) public class ListHostsCmd extends BaseListCmd { @@ -63,7 +67,9 @@ public class ListHostsCmd extends BaseListCmd { @Parameter(name=ApiConstants.ZONE_ID, type=CommandType.LONG, description="the Zone ID for the host") private Long zoneId; - + @Parameter(name=ApiConstants.VIRTUAL_MACHINE_ID, type=CommandType.LONG, required=false, description="lists hosts in the same cluster as this VM and flag hosts with enough CPU/RAm to host this VM") + private Long virtualMachineId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -96,7 +102,9 @@ public class ListHostsCmd extends BaseListCmd { return zoneId; } - + public Long getVirtualMachineId() { + return virtualMachineId; + } ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -112,12 +120,30 @@ public class ListHostsCmd extends BaseListCmd { @Override public void execute(){ - List result = _mgr.searchForServers(this); + List result = new ArrayList(); + List hostIdsWithCapacity = new ArrayList(); + + if(getVirtualMachineId() != null){ + UserVm userVm = _userVmService.getUserVm(getVirtualMachineId()); + if (userVm == null) { + throw new InvalidParameterValueException("Unable to find the VM by id=" + getVirtualMachineId()); + } + Pair, List> hostsForMigration = _mgr.listHostsForMigrationOfVM(userVm, this.getStartIndex(), this.getPageSizeVal()); + result = hostsForMigration.first(); + hostIdsWithCapacity = hostsForMigration.second(); + }else{ + result = _mgr.searchForServers(this); + } ListResponse response = new ListResponse(); List hostResponses = new ArrayList(); for (Host host : result) { HostResponse hostResponse = _responseGenerator.createHostResponse(host); + Boolean hasEnoughCapacity = false; + if(hostIdsWithCapacity.contains(host.getId())){ + hasEnoughCapacity = true; + } + hostResponse.setHasEnoughCapacity(hasEnoughCapacity); hostResponse.setObjectName("host"); hostResponses.add(hostResponse); } diff --git a/api/src/com/cloud/api/commands/MigrateVMCmd.java b/api/src/com/cloud/api/commands/MigrateVMCmd.java new file mode 100644 index 00000000000..80157234b69 --- /dev/null +++ b/api/src/com/cloud/api/commands/MigrateVMCmd.java @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.cloud.api.commands; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiConstants; +import com.cloud.api.BaseAsyncCmd; +import com.cloud.api.BaseCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.ServerApiException; +import com.cloud.api.response.UserVmResponse; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ManagementServerException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.user.Account; +import com.cloud.uservm.UserVm; + +@Implementation(description="Attempts Migration of a virtual machine to the host specified.", responseObject=UserVmResponse.class) +public class MigrateVMCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(MigrateVMCmd.class.getName()); + + private static final String s_name = "migrateVirtualMachine"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.HOST_ID, type=CommandType.LONG, required=true, description="destination Host ID to migrate VM to") + private Long hostId; + + @Parameter(name=ApiConstants.VIRTUAL_MACHINE_ID, type=CommandType.LONG, required=true, description="the ID of the virtual machine") + private Long virtualMachineId; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getHostId() { + return hostId; + } + + public Long getVirtualMachineId() { + return virtualMachineId; + } + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + UserVm userVm = _entityMgr.findById(UserVm.class, getVirtualMachineId()); + 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 String getEventType() { + return EventTypes.EVENT_VM_MIGRATE; + } + + @Override + public String getEventDescription() { + return "Attempting to migrate VM: " + getVirtualMachineId() + " to host: "+ getHostId(); + } + + @Override + public void execute(){ + UserVm userVm = _userVmService.getUserVm(getVirtualMachineId()); + if (userVm == null) { + throw new InvalidParameterValueException("Unable to find the VM by id=" + getVirtualMachineId()); + } + + Host destinationHost = _resourceService.getHost(getHostId()); + if (destinationHost == null) { + throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id=" + getHostId()); + } + try{ + UserVm migratedVm = _userVmService.migrateVirtualMachine(userVm, destinationHost); + + if (migratedVm != null) { + UserVmResponse response = _responseGenerator.createUserVmResponse(migratedVm); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Failed to migrate vm"); + } + } catch (ResourceUnavailableException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(BaseCmd.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (ConcurrentOperationException e) { + s_logger.warn("Exception: ", e); + throw new ServerApiException(BaseCmd.INTERNAL_ERROR, e.getMessage()); + } catch (ManagementServerException e) { + s_logger.warn("Exception: ", e); + throw new ServerApiException(BaseCmd.INTERNAL_ERROR, e.getMessage()); + } + } +} diff --git a/api/src/com/cloud/api/response/HostResponse.java b/api/src/com/cloud/api/response/HostResponse.java index 3c17966f675..8115fa21eee 100755 --- a/api/src/com/cloud/api/response/HostResponse.java +++ b/api/src/com/cloud/api/response/HostResponse.java @@ -143,6 +143,9 @@ public class HostResponse extends BaseResponse { @SerializedName("hosttags") @Param(description="comma-separated list of tags for the host") private String hostTags; + + @SerializedName("hasEnoughCapacity") @Param(description="true if this host has enough CPU and RAM capacity to migrate a VM to it, false otherwise") + private Boolean hasEnoughCapacity; @Override public Long getObjectId() { @@ -464,4 +467,12 @@ public class HostResponse extends BaseResponse { public void setHostTags(String hostTags) { this.hostTags = hostTags; } + + public Boolean hasEnoughCapacity() { + return hasEnoughCapacity; + } + + public void setHasEnoughCapacity(Boolean hasEnoughCapacity) { + this.hasEnoughCapacity = hasEnoughCapacity; + } } diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index a476b4a1155..84ddfc1bee1 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -27,7 +27,8 @@ 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_RESETPASSWORD = "VM.RESETPASSWORD"; + public static final String EVENT_VM_RESETPASSWORD = "VM.RESETPASSWORD"; + public static final String EVENT_VM_MIGRATE = "VM.MIGRATE"; // Domain Router public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE"; diff --git a/api/src/com/cloud/resource/ResourceService.java b/api/src/com/cloud/resource/ResourceService.java index 34de1412c70..b3f1fb7eb28 100644 --- a/api/src/com/cloud/resource/ResourceService.java +++ b/api/src/com/cloud/resource/ResourceService.java @@ -71,4 +71,6 @@ public interface ResourceService { * @throws InvalidParameterValueException */ boolean deleteHost(DeleteHostCmd cmd) throws InvalidParameterValueException; + + Host getHost(long hostId); } diff --git a/api/src/com/cloud/server/ManagementService.java b/api/src/com/cloud/server/ManagementService.java index 449a59b5cef..161cbd4ee3d 100644 --- a/api/src/com/cloud/server/ManagementService.java +++ b/api/src/com/cloud/server/ManagementService.java @@ -102,6 +102,7 @@ import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.SSHKeyPair; import com.cloud.user.UserAccount; +import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; import com.cloud.vm.InstanceGroup; import com.cloud.vm.VirtualMachine; @@ -427,5 +428,13 @@ public interface ManagementService { String getVMPassword(GetVMPasswordCmd cmd); Type findSystemVMTypeById(long instanceId); + + /** + * List hosts for migrating the given VM. The API returns list of all hosts in the VM's cluster minus the current host + * and also a list of hostIds that seem to have enough CPU and RAM capacity to host this VM. + * @param UserVm vm The VM to migrate + * @return Pair, List> List of all Hosts in VM's cluster and list of HostIds with enough capacity + */ + Pair, List> listHostsForMigrationOfVM(UserVm vm, Long startIndex, Long pageSize); } diff --git a/api/src/com/cloud/vm/UserVmService.java b/api/src/com/cloud/vm/UserVmService.java index 43d6fde09ad..aaff2676772 100755 --- a/api/src/com/cloud/vm/UserVmService.java +++ b/api/src/com/cloud/vm/UserVmService.java @@ -39,6 +39,7 @@ import com.cloud.dc.DataCenter; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ManagementServerException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; @@ -49,6 +50,7 @@ import com.cloud.storage.Volume; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.uservm.UserVm; +import com.cloud.host.Host; import com.cloud.utils.exception.ExecutionException; public interface UserVmService { @@ -270,4 +272,16 @@ public interface UserVmService { UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, ResourceAllocationException; + UserVm getUserVm(long vmId); + + /** + * Migrate the given VM to the destination host provided. The API returns the migrated VM + * if migration succeeds. Only Root Admin can migrate a VM. + * @param UserVm vm The VM to migrate + * @return Host destinationHost to migrate the VM + * @throws ManagementServerException in case we get error finding the VM or host or access errors or other internal errors. + * @throws ConcurrentOperationException if there are multiple users working on the same VM. + * @throws ResourceUnavailableException if the destination host to migrate the VM is not currently available. + */ + UserVm migrateVirtualMachine(UserVm vm, Host destinationHost) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException; } diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 79a65130d93..8afcedb49fe 100755 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -45,7 +45,8 @@ changeServiceForVirtualMachine=com.cloud.api.commands.UpgradeVMCmd;15 updateVirtualMachine=com.cloud.api.commands.UpdateVMCmd;15 recoverVirtualMachine=com.cloud.api.commands.RecoverVMCmd;3 listVirtualMachines=com.cloud.api.commands.ListVMsCmd;15 -getVMPassword=com.cloud.api.commands.GetVMPasswordCmd;15 +getVMPassword=com.cloud.api.commands.GetVMPasswordCmd;15 +migrateVirtualMachine=com.cloud.api.commands.MigrateVMCmd;1 #### snapshot commands createSnapshot=com.cloud.api.commands.CreateSnapshotCmd;15 diff --git a/server/src/com/cloud/agent/manager/AgentManagerImpl.java b/server/src/com/cloud/agent/manager/AgentManagerImpl.java index 5ae4ad6aa9e..4e3adef32a5 100755 --- a/server/src/com/cloud/agent/manager/AgentManagerImpl.java +++ b/server/src/com/cloud/agent/manager/AgentManagerImpl.java @@ -2809,6 +2809,10 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory, } } + + public Host getHost(long hostId){ + return _hostDao.findById(hostId); + } // create capacity entries if none exist for this server private void createCapacityEntry(final StartupCommand startup, HostVO server) { diff --git a/server/src/com/cloud/capacity/CapacityManager.java b/server/src/com/cloud/capacity/CapacityManager.java index 6dbb9de7ca1..8bcd36e0ed4 100644 --- a/server/src/com/cloud/capacity/CapacityManager.java +++ b/server/src/com/cloud/capacity/CapacityManager.java @@ -11,7 +11,7 @@ import com.cloud.vm.VirtualMachine; public interface CapacityManager extends Manager { public boolean releaseVmCapacity(VirtualMachine vm, boolean moveFromReserved, boolean moveToReservered, Long hostId); - boolean allocateVmCapacity(VirtualMachine vm, boolean fromLastHost); + void allocateVmCapacity(VirtualMachine vm, boolean fromLastHost); boolean checkIfHostHasCapacity(long hostId, Integer cpu, long ram, boolean checkFromReservedCapacity); } diff --git a/server/src/com/cloud/capacity/CapacityManagerImpl.java b/server/src/com/cloud/capacity/CapacityManagerImpl.java index fc853cc7c16..11d6536e522 100644 --- a/server/src/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/com/cloud/capacity/CapacityManagerImpl.java @@ -174,7 +174,7 @@ public class CapacityManagerImpl implements CapacityManager , StateListener= cpu && reservedMem >= ram) { capacityCpu.setReservedCapacity(reservedCpu - cpu); capacityMem.setReservedCapacity(reservedMem - ram); - - capacityCpu.setUsedCapacity(usedCpu + cpu); - capacityMem.setUsedCapacity(usedMem + ram); - - success = true; - } + } } else { /*alloc from free resource*/ - long freeCpu = totalCpu - (reservedCpu + usedCpu); - long freeMem = totalMem - (reservedMem + usedMem); - - if (s_logger.isDebugEnabled()) { - s_logger.debug("Free CPU: "+freeCpu + " , Requested CPU: "+cpu); - s_logger.debug("Free RAM: "+freeMem + " , Requested RAM: "+ram); - } - if ((reservedCpu + usedCpu + cpu <= totalCpu) && (reservedMem + usedMem + ram <= totalMem)) { - capacityCpu.setUsedCapacity(usedCpu + cpu); - capacityMem.setUsedCapacity(usedMem + ram); - success = true; + if (!((reservedCpu + usedCpu + cpu <= totalCpu) && (reservedMem + usedMem + ram <= totalMem))) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Host doesnt seem to have enough free capacity, but increasing the used capacity anyways, since the VM is already starting on this host "); + } } } - if (success) { - s_logger.debug("Success in alloc cpu from host: " + hostId + ", old used: " + usedCpu + ", old reserved: " + - reservedCpu + ", old total: " + totalCpu + - "; new used:" + capacityCpu.getUsedCapacity() + ", reserved:" + capacityCpu.getReservedCapacity() + ", total: " + capacityCpu.getTotalCapacity() + - "; requested cpu:" + cpu + ",alloc_from_last:" + fromLastHost); + s_logger.debug("CPU STATS after allocation: for host: " + hostId + ", old used: " + usedCpu + ", old reserved: " + + reservedCpu + ", old total: " + totalCpu + + "; new used:" + capacityCpu.getUsedCapacity() + ", reserved:" + capacityCpu.getReservedCapacity() + ", total: " + capacityCpu.getTotalCapacity() + + "; requested cpu:" + cpu + ",alloc_from_last:" + fromLastHost); - s_logger.debug("Success in alloc mem from host: " + hostId + ", old used: " + usedMem + ", old reserved: " + - reservedMem + ", old total: " + totalMem + "; new used: " + capacityMem.getUsedCapacity() + ", reserved: " + - capacityMem.getReservedCapacity() + ", total: " + capacityMem.getTotalCapacity() + "; requested mem: " + ram + ",alloc_from_last:" + fromLastHost); - - _capacityDao.update(capacityCpu.getId(), capacityCpu); - _capacityDao.update(capacityMem.getId(), capacityMem); - } else { - if (fromLastHost) { - s_logger.debug("Failed to alloc resource from host: " + hostId + " reservedCpu: " + reservedCpu + ", requested cpu: " + cpu + - ", reservedMem: " + reservedMem + ", requested mem: " + ram); - } else { - s_logger.debug("Failed to alloc resource from host: " + hostId + " reservedCpu: " + reservedCpu + ", used cpu: " + usedCpu + ", requested cpu: " + cpu + - ", total cpu: " + totalCpu + - ", reservedMem: " + reservedMem + ", used Mem: " + usedMem + ", requested mem: " + ram + ", total Mem:" + totalMem); - } - } + s_logger.debug("RAM STATS after allocation: for host: " + hostId + ", old used: " + usedMem + ", old reserved: " + + reservedMem + ", old total: " + totalMem + "; new used: " + capacityMem.getUsedCapacity() + ", reserved: " + + capacityMem.getReservedCapacity() + ", total: " + capacityMem.getTotalCapacity() + "; requested mem: " + ram + ",alloc_from_last:" + fromLastHost); + _capacityDao.update(capacityCpu.getId(), capacityCpu); + _capacityDao.update(capacityMem.getId(), capacityMem); txn.commit(); - return success; } catch (Exception e) { txn.rollback(); - return false; + return; } } @@ -297,8 +279,8 @@ public class CapacityManagerImpl implements CapacityManager , StateListener= cpu){ @@ -462,7 +444,7 @@ public class CapacityManagerImpl implements CapacityManager , StateListener { CapacityVO findByHostIdType(Long hostId, short capacityType); void clearNonStorageCapacities2(); List findByHostorPoolId(Long hostorPoolId); - List orderClustersInZoneOrPodByHostCapacities(long id, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone); + List orderClustersInZoneOrPodByHostCapacities(long id, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone); + List listHostsWithEnoughCapacity(int requiredCpu, long requiredRam, Long clusterId, String hostType); } diff --git a/server/src/com/cloud/capacity/dao/CapacityDaoImpl.java b/server/src/com/cloud/capacity/dao/CapacityDaoImpl.java index 145a69b69ba..ac09da421aa 100755 --- a/server/src/com/cloud/capacity/dao/CapacityDaoImpl.java +++ b/server/src/com/cloud/capacity/dao/CapacityDaoImpl.java @@ -53,6 +53,10 @@ public class CapacityDaoImpl extends GenericDaoBase implements private SearchBuilder _hostIdTypeSearch; private SearchBuilder _hostOrPoolIdSearch; + + private static final String LIST_HOSTS_IN_CLUSTER_WITH_ENOUGH_CAPACITY = "SELECT a.host_id FROM (host JOIN op_host_capacity a ON host.id = a.host_id AND host.cluster_id = ? AND host.type = ? " + + "AND a.total_capacity - (a.used_capacity + a.reserved_capacity) >= ? and a.capacity_type = 1) " + + "JOIN op_host_capacity b ON a.host_id = b.host_id AND b.total_capacity - (b.used_capacity + b.reserved_capacity) >= ? AND b.capacity_type = 0"; public CapacityDaoImpl() { _hostIdTypeSearch = createSearchBuilder(); @@ -200,5 +204,32 @@ public class CapacityDaoImpl extends GenericDaoBase implements } catch (Throwable e) { throw new CloudRuntimeException("Caught: " + sql, e); } - } + } + + + @Override + public List listHostsWithEnoughCapacity(int requiredCpu, long requiredRam, Long clusterId, String hostType){ + Transaction txn = Transaction.currentTxn(); + PreparedStatement pstmt = null; + List result = new ArrayList(); + + StringBuilder sql = new StringBuilder(LIST_HOSTS_IN_CLUSTER_WITH_ENOUGH_CAPACITY); + try { + pstmt = txn.prepareAutoCloseStatement(sql.toString()); + pstmt.setLong(1, clusterId); + pstmt.setString(2, hostType); + pstmt.setLong(3, requiredCpu); + pstmt.setLong(4, requiredRam); + + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + result.add(rs.getLong(1)); + } + return result; + } catch (SQLException e) { + throw new CloudRuntimeException("DB Exception on: " + sql, e); + } catch (Throwable e) { + throw new CloudRuntimeException("Caught: " + sql, e); + } + } } diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index c0f6da1432f..8496142524c 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -177,6 +177,7 @@ import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageUnavailableException; import com.cloud.host.Host; +import com.cloud.host.Host.Type; import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; @@ -238,6 +239,7 @@ import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.SSHKeyPairDao; import com.cloud.user.dao.UserAccountDao; import com.cloud.user.dao.UserDao; +import com.cloud.uservm.UserVm; import com.cloud.utils.EnumUtils; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; @@ -1224,11 +1226,80 @@ public class ManagementServerImpl implements ManagementServer { return searchForServers(cmd.getStartIndex(), cmd.getPageSizeVal(), name, type, state, zone, pod, cluster, id, keyword); } + + @Override + public Pair, List> listHostsForMigrationOfVM(UserVm vm, Long startIndex, Long pageSize) { + //access check - only root admin can migrate VM + Account caller = UserContext.current().getCaller(); + if(caller.getType() != Account.ACCOUNT_TYPE_ADMIN){ + if(s_logger.isDebugEnabled()){ + s_logger.debug("Caller is not a root admin, permission denied to migrate the VM"); + } + throw new PermissionDeniedException("No permission to migrate VM, Only Root Admin can migrate a VM!"); + } + //business logic + if(vm.getState() != State.Running){ + if (s_logger.isDebugEnabled()) { + s_logger.debug("VM is not Running, unable to migrate the vm " + vm); + } + throw new InvalidParameterValueException("VM is not Running, unable to migrate the vm " + vm); + } + + if(!vm.getHypervisorType().equals(HypervisorType.XenServer)){ + if(s_logger.isDebugEnabled()){ + s_logger.debug(vm + " is not XenServer, cannot migrate this VM."); + } + throw new InvalidParameterValueException("Unsupported Hypervisor Type for VM migration, we support XenServer only"); + } + ServiceOfferingVO svcOffering = _offeringsDao.findById(vm.getServiceOfferingId()); + if(svcOffering.getUseLocalStorage()){ + if(s_logger.isDebugEnabled()){ + s_logger.debug(vm + " is using Local Storage, cannot migrate this VM."); + } + throw new InvalidParameterValueException("Unsupported operation, VM uses Local storage, cannot migrate"); + } + long srcHostId = vm.getHostId(); + Host srcHost = _hostDao.findById(srcHostId); + if(srcHost == null){ + if(s_logger.isDebugEnabled()){ + s_logger.debug("Unable to find the host with id: "+srcHostId+" of this VM:" + vm); + } + throw new InvalidParameterValueException("Unable to find the host with id: "+srcHostId+" of this VM:" + vm); + } + Long cluster = srcHost.getClusterId(); + Type hostType = srcHost.getType(); + if(s_logger.isDebugEnabled()){ + s_logger.debug("Searching for all hosts in cluster: " +cluster+ " for migrating VM "+ vm); + } + + List allHostsInCluster = searchForServers(startIndex, pageSize, null, hostType, null, null, null, cluster, null, null); + //filter out the current host + allHostsInCluster.remove(srcHost); + + if(s_logger.isDebugEnabled()){ + s_logger.debug("Other Hosts in this cluster: "+allHostsInCluster); + } + + int requiredCpu = svcOffering.getCpu() * svcOffering.getSpeed(); + long requiredRam = svcOffering.getRamSize() * 1024L * 1024L; + + if(s_logger.isDebugEnabled()){ + s_logger.debug("Searching for hosts in cluster: " +cluster+ " having required CPU: " +requiredCpu+ " and RAM:"+ requiredRam); + } + + List hostsWithCapacity = _capacityDao.listHostsWithEnoughCapacity(requiredCpu, requiredRam, cluster, hostType.name()); + + if(s_logger.isDebugEnabled()){ + s_logger.debug("Hosts having capacity: "+hostsWithCapacity ); + } + + return new Pair, List>(allHostsInCluster, hostsWithCapacity); + } private List searchForServers(Long startIndex, Long pageSize, Object name, Object type, Object state, Object zone, Object pod, Object cluster, Object id, Object keyword) { Filter searchFilter = new Filter(HostVO.class, "id", Boolean.TRUE, startIndex, pageSize); SearchCriteria sc = _hostDao.createSearchCriteria(); - + if (keyword != null) { SearchCriteria ssc = _hostDao.createSearchCriteria(); ssc.addOr("name", SearchCriteria.Op.LIKE, "%" + keyword + "%"); @@ -1260,7 +1331,7 @@ public class ManagementServerImpl implements ManagementServer { if (cluster != null) { sc.addAnd("clusterId", SearchCriteria.Op.EQ, cluster); } - + return _hostDao.search(sc, searchFilter); } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index e95bc68a78f..afdd02b061e 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -95,12 +95,14 @@ import com.cloud.event.dao.UsageEventDao; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ManagementServerException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageUnavailableException; import com.cloud.ha.HighAvailabilityManager; +import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.DetailsDao; import com.cloud.host.dao.HostDao; @@ -197,6 +199,8 @@ import com.cloud.vm.dao.InstanceGroupVMMapDao; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.org.Cluster; + @Local(value={UserVmManager.class, UserVmService.class}) public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager { private static final Logger s_logger = Logger.getLogger(UserVmManagerImpl.class); @@ -2850,4 +2854,50 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager // TODO Auto-generated method stub return null; } + + public UserVm getUserVm(long vmId){ + return _vmDao.findById(vmId); + } + + public UserVm migrateVirtualMachine(UserVm vm, Host destinationHost) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException{ + //access check - only root admin can migrate VM + Account caller = UserContext.current().getCaller(); + if(caller.getType() != Account.ACCOUNT_TYPE_ADMIN){ + if(s_logger.isDebugEnabled()){ + s_logger.debug("Caller is not a root admin, permission denied to migrate the VM"); + } + throw new PermissionDeniedException("No permission to migrate VM, Only Root Admin can migrate a VM!"); + } + //business logic + if(vm.getState() != State.Running){ + if (s_logger.isDebugEnabled()) { + s_logger.debug("VM is not Running, unable to migrate the vm " + vm); + } + throw new InvalidParameterValueException("VM is not Running, unable to migrate the vm " + vm); + } + if(!vm.getHypervisorType().equals(HypervisorType.XenServer)){ + if(s_logger.isDebugEnabled()){ + s_logger.debug(vm + " is not XenServer, cannot migrate this VM."); + } + throw new InvalidParameterValueException("Unsupported Hypervisor Type for VM migration, we support XenServer only"); + } + + ServiceOfferingVO svcOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); + if(svcOffering.getUseLocalStorage()){ + if(s_logger.isDebugEnabled()){ + s_logger.debug(vm + " is using Local Storage, cannot migrate this VM."); + } + throw new InvalidParameterValueException("Unsupported operation, VM uses Local storage, cannot migrate"); + } + + //call to core process + DataCenterVO dcVO = _dcDao.findById(destinationHost.getDataCenterId()); + HostPodVO pod = _podDao.findById(destinationHost.getPodId()); + long srcHostId = vm.getHostId(); + Cluster cluster = _clusterDao.findById(destinationHost.getClusterId()); + DeployDestination dest = new DeployDestination(dcVO, pod,cluster,destinationHost); + + UserVmVO migratedVm = _itMgr.migrate((UserVmVO)vm, srcHostId, dest); + return migratedVm; + } } diff --git a/server/src/com/cloud/vm/VirtualMachineManager.java b/server/src/com/cloud/vm/VirtualMachineManager.java index 0a4c79646cb..2382a4d6b55 100644 --- a/server/src/com/cloud/vm/VirtualMachineManager.java +++ b/server/src/com/cloud/vm/VirtualMachineManager.java @@ -26,6 +26,7 @@ import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.ManagementServerException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.hypervisor.Hypervisor.HypervisorType; @@ -94,7 +95,7 @@ public interface VirtualMachineManager extends Manager { boolean migrateAway(VirtualMachine.Type type, long vmid, long hostId) throws InsufficientServerCapacityException; - T migrate(T vm, long srcHostId, DeployDestination dest) throws ResourceUnavailableException; + T migrate(T vm, long srcHostId, DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException; T reboot(T vm, Map params, User caller, Account account) throws InsufficientCapacityException, ResourceUnavailableException; diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index 695717ca92f..be8a8f2f201 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -83,6 +83,8 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.ConnectionException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ManagementServerException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.ha.HighAvailabilityManager; @@ -900,6 +902,10 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene if (e == Event.OperationSucceeded) { vm.setLastHostId(hostId); } + }else if (oldState == State.Stopping ) { + if (e == Event.OperationSucceeded) { + vm.setLastHostId(vm.getHostId()); + } } return _stateMachine.transitTo(vm, e, hostId, _vmDao); } @@ -948,15 +954,21 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene } @Override - public T migrate(T vm, long srcHostId, DeployDestination dest) throws ResourceUnavailableException { + public T migrate(T vm, long srcHostId, DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException { s_logger.info("Migrating " + vm + " to " + dest); 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); - return null; + throw new ManagementServerException("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 ManagementServerException("Source and destination host are not in same cluster, unable to migrate to host: " + dest.getHost().getId()); + } + VirtualMachineGuru vmGuru = getVmGuru(vm); vm = vmGuru.findById(vm.getId()); @@ -964,7 +976,14 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene if (s_logger.isDebugEnabled()) { s_logger.debug("Unable to find the vm " + vm); } - return null; + throw new ManagementServerException("Unable to find a virtual machine with id " + vm.getId()); + } + + if(vm.getState() != State.Running){ + if (s_logger.isDebugEnabled()) { + s_logger.debug("VM is not Running, unable to migrate the vm " + vm); + } + throw new ManagementServerException("VM is not Running, unable to migrate the vm " + vm); } short alertType = AlertManager.ALERT_TYPE_USERVM_MIGRATE; @@ -1008,7 +1027,7 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene vm.setLastHostId(srcHostId); 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); - return null; + throw new ConcurrentOperationException("Migration cancelled because state has changed: " + vm); } boolean migrated = false; @@ -1142,7 +1161,11 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene vm = migrate(vm, srcHostId, dest); } catch (ResourceUnavailableException e) { s_logger.debug("Unable to migrate to unavailable " + dest); - } + } catch (ConcurrentOperationException e) { + s_logger.debug("Unable to migrate VM due to: " + e.getMessage()); + } catch (ManagementServerException e) { + s_logger.debug("Unable to migrate VM: " + e.getMessage()); + } if (vm != null) { return true; }