| // 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.AgentManager.OnError; |
| import com.cloud.agent.api.*; |
| import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; |
| import com.cloud.agent.api.to.NicTO; |
| import com.cloud.agent.api.to.VirtualMachineTO; |
| import com.cloud.agent.api.to.VolumeTO; |
| import com.cloud.agent.api.PlugNicAnswer; |
| import com.cloud.agent.api.PlugNicCommand; |
| import com.cloud.agent.api.UnPlugNicAnswer; |
| import com.cloud.agent.api.UnPlugNicCommand; |
| import com.cloud.agent.manager.Commands; |
| import com.cloud.alert.AlertManager; |
| import com.cloud.api.ApiDBUtils; |
| import com.cloud.api.query.dao.UserVmJoinDao; |
| import com.cloud.api.query.vo.UserVmJoinVO; |
| import com.cloud.async.AsyncJobExecutor; |
| import com.cloud.async.AsyncJobManager; |
| import com.cloud.async.AsyncJobVO; |
| import com.cloud.async.BaseAsyncJobExecutor; |
| import com.cloud.capacity.CapacityManager; |
| import com.cloud.configuration.Config; |
| import com.cloud.configuration.ConfigurationManager; |
| import com.cloud.configuration.Resource.ResourceType; |
| import com.cloud.configuration.dao.ConfigurationDao; |
| import com.cloud.dc.DataCenter; |
| import com.cloud.dc.DataCenter.NetworkType; |
| import com.cloud.dc.DataCenterVO; |
| import com.cloud.dc.HostPodVO; |
| import com.cloud.dc.dao.ClusterDao; |
| import com.cloud.dc.dao.DataCenterDao; |
| import com.cloud.dc.dao.HostPodDao; |
| import com.cloud.deploy.DataCenterDeployment; |
| import com.cloud.deploy.DeployDestination; |
| import com.cloud.domain.DomainVO; |
| import com.cloud.domain.dao.DomainDao; |
| import com.cloud.event.ActionEvent; |
| import com.cloud.event.EventTypes; |
| import com.cloud.event.UsageEventUtils; |
| import com.cloud.event.dao.UsageEventDao; |
| import com.cloud.exception.*; |
| import com.cloud.ha.HighAvailabilityManager; |
| import com.cloud.host.Host; |
| import com.cloud.host.HostVO; |
| import com.cloud.host.dao.HostDao; |
| import com.cloud.hypervisor.Hypervisor.HypervisorType; |
| import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao; |
| import com.cloud.network.*; |
| import com.cloud.network.Network.IpAddresses; |
| import com.cloud.network.Network.Provider; |
| import com.cloud.network.Network.Service; |
| import com.cloud.network.Networks.TrafficType; |
| import com.cloud.network.dao.*; |
| import com.cloud.network.element.UserDataServiceProvider; |
| import com.cloud.network.lb.LoadBalancingRulesManager; |
| import com.cloud.network.rules.FirewallManager; |
| import com.cloud.network.rules.FirewallRuleVO; |
| import com.cloud.network.rules.PortForwardingRuleVO; |
| import com.cloud.network.rules.RulesManager; |
| import com.cloud.network.rules.dao.PortForwardingRulesDao; |
| import com.cloud.network.security.SecurityGroup; |
| import com.cloud.network.security.SecurityGroupManager; |
| import com.cloud.network.security.dao.SecurityGroupDao; |
| import com.cloud.network.security.dao.SecurityGroupVMMapDao; |
| import com.cloud.network.vpc.VpcManager; |
| import com.cloud.network.vpc.dao.VpcDao; |
| import com.cloud.offering.NetworkOffering; |
| import com.cloud.offering.NetworkOffering.Availability; |
| import com.cloud.offering.ServiceOffering; |
| import com.cloud.offerings.NetworkOfferingVO; |
| import com.cloud.offerings.dao.NetworkOfferingDao; |
| import com.cloud.org.Cluster; |
| import com.cloud.org.Grouping; |
| import com.cloud.projects.Project.ListProjectResourcesCriteria; |
| import com.cloud.projects.ProjectManager; |
| import com.cloud.resource.ResourceManager; |
| import com.cloud.resource.ResourceState; |
| import com.cloud.server.Criteria; |
| import com.cloud.service.ServiceOfferingVO; |
| import com.cloud.service.dao.ServiceOfferingDao; |
| import com.cloud.storage.*; |
| import com.cloud.storage.Storage.ImageFormat; |
| import com.cloud.storage.Storage.StoragePoolType; |
| import com.cloud.storage.Storage.TemplateType; |
| import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; |
| import com.cloud.storage.Volume.Type; |
| import com.cloud.storage.dao.*; |
| import com.cloud.storage.snapshot.SnapshotManager; |
| import com.cloud.tags.dao.ResourceTagDao; |
| import com.cloud.template.VirtualMachineTemplate; |
| import com.cloud.template.VirtualMachineTemplate.BootloaderType; |
| import com.cloud.user.*; |
| import com.cloud.user.dao.AccountDao; |
| import com.cloud.user.dao.SSHKeyPairDao; |
| import com.cloud.user.dao.UserDao; |
| import com.cloud.uservm.UserVm; |
| import com.cloud.utils.NumbersUtil; |
| import com.cloud.utils.Pair; |
| import com.cloud.utils.PasswordGenerator; |
| import com.cloud.utils.component.ComponentLocator; |
| import com.cloud.utils.component.Inject; |
| import com.cloud.utils.component.Manager; |
| import com.cloud.utils.concurrency.NamedThreadFactory; |
| import com.cloud.utils.crypt.RSAHelper; |
| import com.cloud.utils.db.*; |
| import com.cloud.utils.db.SearchCriteria.Func; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.utils.exception.ExecutionException; |
| import com.cloud.utils.fsm.NoTransitionException; |
| import com.cloud.utils.net.NetUtils; |
| import com.cloud.vm.VirtualMachine.State; |
| import com.cloud.vm.dao.*; |
| import org.apache.cloudstack.acl.ControlledEntity.ACLType; |
| import org.apache.cloudstack.acl.SecurityChecker.AccessType; |
| import org.apache.cloudstack.api.BaseCmd; |
| 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.template.CreateTemplateCmd; |
| 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; |
| import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; |
| import org.apache.commons.codec.binary.Base64; |
| import org.apache.log4j.Logger; |
| |
| import javax.ejb.Local; |
| import javax.naming.ConfigurationException; |
| import java.util.*; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeUnit; |
| |
| @Local(value = { UserVmManager.class, UserVmService.class }) |
| public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager { |
| private static final Logger s_logger = Logger.getLogger(UserVmManagerImpl.class); |
| |
| private static final int ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION = 3; // 3 seconds |
| |
| @Inject |
| protected HostDao _hostDao = null; |
| @Inject |
| protected ServiceOfferingDao _offeringDao = null; |
| @Inject |
| protected DiskOfferingDao _diskOfferingDao = null; |
| @Inject |
| protected VMTemplateDao _templateDao = null; |
| @Inject |
| protected VMTemplateDetailsDao _templateDetailsDao = null; |
| @Inject |
| protected VMTemplateHostDao _templateHostDao = null; |
| @Inject |
| protected VMTemplateZoneDao _templateZoneDao = null; |
| @Inject |
| protected DomainDao _domainDao = null; |
| @Inject |
| protected UserVmDao _vmDao = null; |
| @Inject |
| protected UserVmJoinDao _vmJoinDao = null; |
| @Inject |
| protected VolumeDao _volsDao = null; |
| @Inject |
| protected DataCenterDao _dcDao = null; |
| @Inject |
| protected FirewallRulesDao _rulesDao = null; |
| @Inject |
| protected LoadBalancerVMMapDao _loadBalancerVMMapDao = null; |
| @Inject |
| protected PortForwardingRulesDao _portForwardingDao; |
| @Inject |
| protected IPAddressDao _ipAddressDao = null; |
| @Inject |
| protected HostPodDao _podDao = null; |
| @Inject |
| protected NetworkModel _networkModel = null; |
| @Inject |
| protected NetworkManager _networkMgr = null; |
| @Inject |
| protected StorageManager _storageMgr = null; |
| @Inject |
| protected SnapshotManager _snapshotMgr = null; |
| @Inject |
| protected AgentManager _agentMgr = null; |
| @Inject |
| protected ConfigurationManager _configMgr = null; |
| @Inject |
| protected AccountDao _accountDao = null; |
| @Inject |
| protected UserDao _userDao = null; |
| @Inject |
| protected SnapshotDao _snapshotDao = null; |
| @Inject |
| protected GuestOSDao _guestOSDao = null; |
| @Inject |
| protected HighAvailabilityManager _haMgr = null; |
| @Inject |
| protected AlertManager _alertMgr = null; |
| @Inject |
| protected AccountManager _accountMgr; |
| @Inject |
| protected AccountService _accountService; |
| @Inject |
| protected AsyncJobManager _asyncMgr; |
| @Inject |
| protected ClusterDao _clusterDao; |
| @Inject |
| protected StoragePoolDao _storagePoolDao; |
| @Inject |
| protected SecurityGroupManager _securityGroupMgr; |
| @Inject |
| protected ServiceOfferingDao _serviceOfferingDao; |
| @Inject |
| protected NetworkOfferingDao _networkOfferingDao; |
| @Inject |
| protected InstanceGroupDao _vmGroupDao; |
| @Inject |
| protected InstanceGroupVMMapDao _groupVMMapDao; |
| @Inject |
| protected VirtualMachineManager _itMgr; |
| @Inject |
| protected NetworkDao _networkDao; |
| @Inject |
| protected NicDao _nicDao; |
| @Inject |
| protected VpcDao _vpcDao; |
| @Inject |
| protected RulesManager _rulesMgr; |
| @Inject |
| protected LoadBalancingRulesManager _lbMgr; |
| @Inject |
| protected SSHKeyPairDao _sshKeyPairDao; |
| @Inject |
| protected UserVmDetailsDao _vmDetailsDao; |
| @Inject |
| protected HypervisorCapabilitiesDao _hypervisorCapabilitiesDao; |
| @Inject |
| protected SecurityGroupDao _securityGroupDao; |
| @Inject |
| protected CapacityManager _capacityMgr;; |
| @Inject |
| protected VMInstanceDao _vmInstanceDao; |
| @Inject |
| protected ResourceLimitService _resourceLimitMgr; |
| @Inject |
| protected FirewallManager _firewallMgr; |
| @Inject |
| protected ProjectManager _projectMgr; |
| @Inject |
| protected ResourceManager _resourceMgr; |
| @Inject |
| protected NetworkServiceMapDao _ntwkSrvcDao; |
| @Inject |
| SecurityGroupVMMapDao _securityGroupVMMapDao; |
| @Inject |
| protected ItWorkDao _workDao; |
| @Inject |
| protected VolumeHostDao _volumeHostDao; |
| @Inject |
| ResourceTagDao _resourceTagDao; |
| @Inject |
| PhysicalNetworkDao _physicalNetworkDao; |
| @Inject |
| VpcManager _vpcMgr; |
| @Inject |
| UsageEventDao _usageEventDao; |
| |
| protected ScheduledExecutorService _executor = null; |
| protected int _expungeInterval; |
| protected int _expungeDelay; |
| |
| protected String _name; |
| protected String _instance; |
| protected String _zone; |
| |
| private ConfigurationDao _configDao; |
| private int _createprivatetemplatefromvolumewait; |
| private int _createprivatetemplatefromsnapshotwait; |
| @Override |
| public UserVmVO getVirtualMachine(long vmId) { |
| return _vmDao.findById(vmId); |
| } |
| |
| @Override |
| public List<? extends UserVm> getVirtualMachines(long hostId) { |
| return _vmDao.listByHostId(hostId); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_RESETPASSWORD, eventDescription = "resetting Vm password", async = true) |
| public UserVm resetVMPassword(ResetVMPasswordCmd cmd, String password) throws ResourceUnavailableException, InsufficientCapacityException { |
| Account caller = UserContext.current().getCaller(); |
| Long vmId = cmd.getId(); |
| UserVmVO userVm = _vmDao.findById(cmd.getId()); |
| _vmDao.loadDetails(userVm); |
| |
| // Do parameters input validation |
| if (userVm == null) { |
| throw new InvalidParameterValueException("unable to find a virtual machine with id " + cmd.getId()); |
| } |
| |
| VMTemplateVO template = _templateDao.findByIdIncludingRemoved(userVm.getTemplateId()); |
| if (template == null || !template.getEnablePassword()) { |
| throw new InvalidParameterValueException("Fail to reset password for the virtual machine, the template is not password enabled"); |
| } |
| |
| if (userVm.getState() == State.Error || userVm.getState() == State.Expunging) { |
| s_logger.error("vm is not in the right state: " + vmId); |
| throw new InvalidParameterValueException("Vm with id " + vmId + " is not in the right state"); |
| } |
| |
| _accountMgr.checkAccess(caller, null, true, userVm); |
| |
| boolean result = resetVMPasswordInternal(cmd, password); |
| |
| if (result) { |
| userVm.setPassword(password); |
| //update the password in vm_details table too |
| // Check if an SSH key pair was selected for the instance and if so use it to encrypt & save the vm password |
| String sshPublicKey = userVm.getDetail("SSH.PublicKey"); |
| if (sshPublicKey != null && !sshPublicKey.equals("") && password != null && !password.equals("saved_password")) { |
| String encryptedPasswd = RSAHelper.encryptWithSSHPublicKey(sshPublicKey, password); |
| if (encryptedPasswd == null) { |
| throw new CloudRuntimeException("Error encrypting password"); |
| } |
| |
| userVm.setDetail("Encrypted.Password", encryptedPasswd); |
| _vmDao.saveDetails(userVm); |
| } |
| } else { |
| throw new CloudRuntimeException("Failed to reset password for the virtual machine "); |
| } |
| |
| return userVm; |
| } |
| |
| private boolean resetVMPasswordInternal(ResetVMPasswordCmd cmd, String password) throws ResourceUnavailableException, InsufficientCapacityException { |
| Long vmId = cmd.getId(); |
| Long userId = UserContext.current().getCallerUserId(); |
| VMInstanceVO vmInstance = _vmDao.findById(vmId); |
| |
| if (password == null || password.equals("")) { |
| return false; |
| } |
| |
| VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmInstance.getTemplateId()); |
| if (template.getEnablePassword()) { |
| Nic defaultNic = _networkModel.getDefaultNic(vmId); |
| if (defaultNic == null) { |
| s_logger.error("Unable to reset password for vm " + vmInstance + " as the instance doesn't have default nic"); |
| return false; |
| } |
| |
| Network defaultNetwork = _networkDao.findById(defaultNic.getNetworkId()); |
| NicProfile defaultNicProfile = new NicProfile(defaultNic, defaultNetwork, null, null, null, _networkModel.isSecurityGroupSupportedInNetwork(defaultNetwork), _networkModel.getNetworkTag(template.getHypervisorType(), defaultNetwork)); |
| VirtualMachineProfile<VMInstanceVO> vmProfile = new VirtualMachineProfileImpl<VMInstanceVO>(vmInstance); |
| vmProfile.setParameter(VirtualMachineProfile.Param.VmPassword, password); |
| |
| UserDataServiceProvider element = _networkMgr.getPasswordResetProvider(defaultNetwork); |
| if (element == null) { |
| throw new CloudRuntimeException("Can't find network element for " + Service.UserData.getName() + |
| " provider needed for password reset"); |
| } |
| |
| boolean result = element.savePassword(defaultNetwork, defaultNicProfile, vmProfile); |
| |
| // Need to reboot the virtual machine so that the password gets redownloaded from the DomR, and reset on the VM |
| if (!result) { |
| s_logger.debug("Failed to reset password for the virutal machine; no need to reboot the vm"); |
| return false; |
| } else { |
| if (vmInstance.getState() == State.Stopped) { |
| s_logger.debug("Vm " + vmInstance + " is stopped, not rebooting it as a part of password reset"); |
| return true; |
| } |
| |
| if (rebootVirtualMachine(userId, vmId) == null) { |
| s_logger.warn("Failed to reboot the vm " + vmInstance); |
| return false; |
| } else { |
| s_logger.debug("Vm " + vmInstance + " is rebooted successfully as a part of password reset"); |
| return true; |
| } |
| } |
| } else { |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Reset password called for a vm that is not using a password enabled template"); |
| } |
| return false; |
| } |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_RESETSSHKEY, eventDescription = "resetting Vm SSHKey", async = true) |
| public UserVm resetVMSSHKey(ResetVMSSHKeyCmd cmd) |
| throws ResourceUnavailableException, InsufficientCapacityException { |
| |
| Account caller = UserContext.current().getCaller(); |
| Account owner = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId()); |
| Long vmId = cmd.getId(); |
| |
| UserVmVO userVm = _vmDao.findById(cmd.getId()); |
| _vmDao.loadDetails(userVm); |
| VMTemplateVO template = _templateDao.findByIdIncludingRemoved(userVm.getTemplateId()); |
| |
| // Do parameters input validation |
| |
| if (userVm == null) { |
| throw new InvalidParameterValueException("unable to find a virtual machine by id" + cmd.getId()); |
| } |
| |
| if (userVm.getState() == State.Error || userVm.getState() == State.Expunging) { |
| s_logger.error("vm is not in the right state: " + vmId); |
| throw new InvalidParameterValueException("Vm with specified id is not in the right state"); |
| } |
| if (userVm.getState() != State.Stopped) { |
| s_logger.error("vm is not in the right state: " + vmId); |
| throw new InvalidParameterValueException("Vm " + userVm + " should be stopped to do SSH Key reset"); |
| } |
| |
| SSHKeyPairVO s = _sshKeyPairDao.findByName(owner.getAccountId(), owner.getDomainId(), cmd.getName()); |
| if (s == null) { |
| throw new InvalidParameterValueException("A key pair with name '" + cmd.getName() + "' does not exist for account " + owner.getAccountName() + " in specified domain id"); |
| } |
| |
| _accountMgr.checkAccess(caller, null, true, userVm); |
| String password = null; |
| String sshPublicKey = s.getPublicKey(); |
| if (template != null && template.getEnablePassword()) { |
| password = generateRandomPassword(); |
| } |
| |
| boolean result = resetVMSSHKeyInternal(vmId, sshPublicKey, password); |
| |
| if (result) { |
| userVm.setDetail("SSH.PublicKey", sshPublicKey); |
| if (template != null && template.getEnablePassword()) { |
| userVm.setPassword(password); |
| //update the encrypted password in vm_details table too |
| if (sshPublicKey != null && !sshPublicKey.equals("") && password != null && !password.equals("saved_password")) { |
| String encryptedPasswd = RSAHelper.encryptWithSSHPublicKey(sshPublicKey, password); |
| if (encryptedPasswd == null) { |
| throw new CloudRuntimeException("Error encrypting password"); |
| } |
| userVm.setDetail("Encrypted.Password", encryptedPasswd); |
| } |
| } |
| _vmDao.saveDetails(userVm); |
| } else { |
| throw new CloudRuntimeException("Failed to reset SSH Key for the virtual machine "); |
| } |
| return userVm; |
| } |
| |
| private boolean resetVMSSHKeyInternal(Long vmId, String SSHPublicKey, String password) throws ResourceUnavailableException, InsufficientCapacityException { |
| Long userId = UserContext.current().getCallerUserId(); |
| VMInstanceVO vmInstance = _vmDao.findById(vmId); |
| |
| VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmInstance.getTemplateId()); |
| Nic defaultNic = _networkModel.getDefaultNic(vmId); |
| if (defaultNic == null) { |
| s_logger.error("Unable to reset SSH Key for vm " + vmInstance + " as the instance doesn't have default nic"); |
| return false; |
| } |
| |
| Network defaultNetwork = _networkDao.findById(defaultNic.getNetworkId()); |
| NicProfile defaultNicProfile = new NicProfile(defaultNic, defaultNetwork, null, null, null, |
| _networkModel.isSecurityGroupSupportedInNetwork(defaultNetwork), |
| _networkModel.getNetworkTag(template.getHypervisorType(), defaultNetwork)); |
| |
| VirtualMachineProfile<VMInstanceVO> vmProfile = new VirtualMachineProfileImpl<VMInstanceVO>(vmInstance); |
| |
| if (template != null && template.getEnablePassword()) { |
| vmProfile.setParameter(VirtualMachineProfile.Param.VmPassword, password); |
| } |
| |
| UserDataServiceProvider element = _networkMgr.getSSHKeyResetProvider(defaultNetwork); |
| if (element == null) { |
| throw new CloudRuntimeException("Can't find network element for " + Service.UserData.getName() + " provider needed for SSH Key reset"); |
| } |
| boolean result = element.saveSSHKey(defaultNetwork, defaultNicProfile, vmProfile, SSHPublicKey); |
| |
| // Need to reboot the virtual machine so that the password gets redownloaded from the DomR, and reset on the VM |
| if (!result) { |
| s_logger.debug("Failed to reset SSH Key for the virutal machine; no need to reboot the vm"); |
| return false; |
| } else { |
| if (vmInstance.getState() == State.Stopped) { |
| s_logger.debug("Vm " + vmInstance + " is stopped, not rebooting it as a part of SSH Key reset"); |
| return true; |
| } |
| if (rebootVirtualMachine(userId, vmId) == null) { |
| s_logger.warn("Failed to reboot the vm " + vmInstance); |
| return false; |
| } else { |
| s_logger.debug("Vm " + vmInstance + " is rebooted successfully as a part of SSH Key reset"); |
| return true; |
| } |
| } |
| } |
| |
| |
| @Override |
| public boolean stopVirtualMachine(long userId, long vmId) { |
| boolean status = false; |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Stopping vm=" + vmId); |
| } |
| UserVmVO vm = _vmDao.findById(vmId); |
| if (vm == null || vm.getRemoved() != null) { |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("VM is either removed or deleted."); |
| } |
| return true; |
| } |
| |
| User user = _userDao.findById(userId); |
| Account account = _accountDao.findById(user.getAccountId()); |
| |
| try { |
| status = _itMgr.stop(vm, user, account); |
| } catch (ResourceUnavailableException e) { |
| s_logger.debug("Unable to stop due to ", e); |
| status = false; |
| } |
| |
| if (status) { |
| return status; |
| } else { |
| return status; |
| } |
| } |
| |
| private int getMaxDataVolumesSupported(UserVmVO vm) { |
| Long hostId = vm.getHostId(); |
| if (hostId == null) { |
| hostId = vm.getLastHostId(); |
| } |
| HostVO host = _hostDao.findById(hostId); |
| Integer maxDataVolumesSupported = null; |
| if (host != null) { |
| _hostDao.loadDetails(host); |
| maxDataVolumesSupported = _hypervisorCapabilitiesDao.getMaxDataVolumesLimit(host.getHypervisorType(), host.getDetail("product_version")); |
| } |
| if (maxDataVolumesSupported == null) { |
| maxDataVolumesSupported = 6; // 6 data disks by default if nothing is specified in 'hypervisor_capabilities' table |
| } |
| |
| return maxDataVolumesSupported.intValue(); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true) |
| public Volume attachVolumeToVM(AttachVolumeCmd command) { |
| Long vmId = command.getVirtualMachineId(); |
| Long volumeId = command.getId(); |
| Long deviceId = command.getDeviceId(); |
| Account caller = UserContext.current().getCaller(); |
| |
| // Check that the volume ID is valid |
| VolumeVO volume = _volsDao.findById(volumeId); |
| // Check that the volume is a data volume |
| if (volume == null || volume.getVolumeType() != Volume.Type.DATADISK) { |
| throw new InvalidParameterValueException("Please specify a valid data volume."); |
| } |
| |
| // Check that the volume is not currently attached to any VM |
| if (volume.getInstanceId() != null) { |
| throw new InvalidParameterValueException("Please specify a volume that is not attached to any VM."); |
| } |
| |
| // Check that the volume is not destroyed |
| if (volume.getState() == Volume.State.Destroy) { |
| throw new InvalidParameterValueException("Please specify a volume that is not destroyed."); |
| } |
| |
| // Check that the virtual machine ID is valid and it's a user vm |
| UserVmVO vm = _vmDao.findById(vmId); |
| if (vm == null || vm.getType() != VirtualMachine.Type.User) { |
| throw new InvalidParameterValueException("Please specify a valid User VM."); |
| } |
| |
| // Check that the VM is in the correct state |
| if (vm.getState() != State.Running && vm.getState() != State.Stopped) { |
| throw new InvalidParameterValueException("Please specify a VM that is either running or stopped."); |
| } |
| |
| // Check that the device ID is valid |
| if (deviceId != null) { |
| if (deviceId.longValue() == 0) { |
| throw new InvalidParameterValueException("deviceId can't be 0, which is used by Root device"); |
| } |
| } |
| |
| // Check that the number of data volumes attached to VM is less than that supported by hypervisor |
| List<VolumeVO> existingDataVolumes = _volsDao.findByInstanceAndType(vmId, Volume.Type.DATADISK); |
| int maxDataVolumesSupported = getMaxDataVolumesSupported(vm); |
| if (existingDataVolumes.size() >= maxDataVolumesSupported) { |
| throw new InvalidParameterValueException("The specified VM already has the maximum number of data disks (" + maxDataVolumesSupported + "). Please specify another VM."); |
| } |
| |
| // Check that the VM and the volume are in the same zone |
| if (vm.getDataCenterIdToDeployIn() != volume.getDataCenterId()) { |
| throw new InvalidParameterValueException("Please specify a VM that is in the same zone as the volume."); |
| } |
| |
| // If local storage is disabled then attaching a volume with local disk offering not allowed |
| DataCenterVO dataCenter = _dcDao.findById(volume.getDataCenterId()); |
| if (!dataCenter.isLocalStorageEnabled()) { |
| DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); |
| if (diskOffering.getUseLocalStorage()) { |
| throw new InvalidParameterValueException("Zone is not configured to use local storage but volume's disk offering " + diskOffering.getName() + " uses it"); |
| } |
| } |
| |
| //permission check |
| _accountMgr.checkAccess(caller, null, true, volume, vm); |
| |
| //Check if volume is stored on secondary Storage. |
| boolean isVolumeOnSec = false; |
| VolumeHostVO volHostVO = _volumeHostDao.findByVolumeId(volume.getId()); |
| if (volHostVO != null){ |
| isVolumeOnSec = true; |
| if( !(volHostVO.getDownloadState() == Status.DOWNLOADED) ){ |
| throw new InvalidParameterValueException("Volume is not uploaded yet. Please try this operation once the volume is uploaded"); |
| } |
| } |
| |
| if ( !(Volume.State.Allocated.equals(volume.getState()) || Volume.State.Ready.equals(volume.getState()) || Volume.State.UploadOp.equals(volume.getState())) ) { |
| throw new InvalidParameterValueException("Volume state must be in Allocated, Ready or in Uploaded state"); |
| } |
| |
| VolumeVO rootVolumeOfVm = null; |
| List<VolumeVO> rootVolumesOfVm = _volsDao.findByInstanceAndType(vmId, Volume.Type.ROOT); |
| if (rootVolumesOfVm.size() != 1) { |
| throw new CloudRuntimeException("The VM " + vm.getHostName() + " has more than one ROOT volume and is in an invalid state."); |
| } else { |
| rootVolumeOfVm = rootVolumesOfVm.get(0); |
| } |
| |
| HypervisorType rootDiskHyperType = vm.getHypervisorType(); |
| |
| HypervisorType dataDiskHyperType = _volsDao.getHypervisorType(volume.getId()); |
| if (dataDiskHyperType != HypervisorType.None && rootDiskHyperType != dataDiskHyperType) { |
| throw new InvalidParameterValueException("Can't attach a volume created by: " + dataDiskHyperType + " to a " + rootDiskHyperType + " vm"); |
| } |
| |
| //allocate deviceId |
| List<VolumeVO> vols = _volsDao.findByInstance(vmId); |
| if (deviceId != null) { |
| if (deviceId.longValue() > 15 || deviceId.longValue() == 0 || deviceId.longValue() == 3) { |
| throw new RuntimeException("deviceId should be 1,2,4-15"); |
| } |
| for (VolumeVO vol : vols) { |
| if (vol.getDeviceId().equals(deviceId)) { |
| throw new RuntimeException("deviceId " + deviceId + " is used by VM " + vm.getHostName()); |
| } |
| } |
| } else { |
| // allocate deviceId here |
| List<String> devIds = new ArrayList<String>(); |
| for (int i = 1; i < 15; i++) { |
| devIds.add(String.valueOf(i)); |
| } |
| devIds.remove("3"); |
| for (VolumeVO vol : vols) { |
| devIds.remove(vol.getDeviceId().toString().trim()); |
| } |
| deviceId = Long.parseLong(devIds.iterator().next()); |
| } |
| |
| boolean createVolumeOnBackend = true; |
| if (rootVolumeOfVm.getState() == Volume.State.Allocated) { |
| createVolumeOnBackend = false; |
| if(isVolumeOnSec){ |
| throw new CloudRuntimeException("Cant attach uploaded volume to the vm which is not created. Please start it and then retry"); |
| } |
| } |
| |
| //create volume on the backend only when vm's root volume is allocated |
| if (createVolumeOnBackend) { |
| if (volume.getState().equals(Volume.State.Allocated) || isVolumeOnSec) { |
| /* Need to create the volume */ |
| VMTemplateVO rootDiskTmplt = _templateDao.findById(vm.getTemplateId()); |
| DataCenterVO dcVO = _dcDao.findById(vm.getDataCenterIdToDeployIn()); |
| HostPodVO pod = _podDao.findById(vm.getPodIdToDeployIn()); |
| StoragePoolVO rootDiskPool = _storagePoolDao.findById(rootVolumeOfVm.getPoolId()); |
| ServiceOfferingVO svo = _serviceOfferingDao.findById(vm.getServiceOfferingId()); |
| DiskOfferingVO diskVO = _diskOfferingDao.findById(volume.getDiskOfferingId()); |
| Long clusterId = (rootDiskPool == null ? null : rootDiskPool.getClusterId()); |
| |
| if (!isVolumeOnSec){ |
| volume = _storageMgr.createVolume(volume, vm, rootDiskTmplt, dcVO, pod, clusterId, svo, diskVO, new ArrayList<StoragePoolVO>(), volume.getSize(), rootDiskHyperType); |
| }else { |
| try { |
| // Format of data disk should be the same as root disk |
| if( ! volHostVO.getFormat().getFileExtension().equals(_storageMgr.getSupportedImageFormatForCluster(rootDiskPool.getClusterId())) ){ |
| throw new InvalidParameterValueException("Failed to attach volume to VM since volumes format " +volHostVO.getFormat().getFileExtension() + " is not compatible with the vm hypervisor type" ); |
| } |
| |
| // Check that there is some shared storage. |
| StoragePoolVO vmRootVolumePool = _storagePoolDao.findById(rootVolumeOfVm.getPoolId()); |
| List<StoragePoolVO> sharedVMPools = _storagePoolDao.findPoolsByTags(vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), null, true); |
| if (sharedVMPools.size() == 0) { |
| throw new CloudRuntimeException("Cannot attach volume since there are no shared storage pools in the VM's cluster to copy the uploaded volume to."); |
| } |
| |
| volume = _storageMgr.copyVolumeFromSecToPrimary(volume, vm, rootDiskTmplt, dcVO, pod, rootDiskPool.getClusterId(), svo, diskVO, new ArrayList<StoragePoolVO>(), volume.getSize(), rootDiskHyperType); |
| } catch (NoTransitionException e) { |
| throw new CloudRuntimeException("Unable to transition the volume ",e); |
| } |
| } |
| |
| if (volume == null) { |
| throw new CloudRuntimeException("Failed to create volume when attaching it to VM: " + vm.getHostName()); |
| } |
| } |
| |
| StoragePoolVO vmRootVolumePool = _storagePoolDao.findById(rootVolumeOfVm.getPoolId()); |
| DiskOfferingVO volumeDiskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); |
| String[] volumeTags = volumeDiskOffering.getTagsArray(); |
| |
| boolean isVolumeOnSharedPool = !volumeDiskOffering.getUseLocalStorage(); |
| StoragePoolVO sourcePool = _storagePoolDao.findById(volume.getPoolId()); |
| List<StoragePoolVO> matchingVMPools = _storagePoolDao.findPoolsByTags(vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), volumeTags, isVolumeOnSharedPool); |
| boolean moveVolumeNeeded = true; |
| if (matchingVMPools.size() == 0) { |
| String poolType; |
| if (vmRootVolumePool.getClusterId() != null) { |
| poolType = "cluster"; |
| } else if (vmRootVolumePool.getPodId() != null) { |
| poolType = "pod"; |
| } else { |
| poolType = "zone"; |
| } |
| throw new CloudRuntimeException("There are no storage pools in the VM's " + poolType + " with all of the volume's tags (" + volumeDiskOffering.getTags() + ")."); |
| } else { |
| long sourcePoolId = sourcePool.getId(); |
| Long sourcePoolDcId = sourcePool.getDataCenterId(); |
| Long sourcePoolPodId = sourcePool.getPodId(); |
| Long sourcePoolClusterId = sourcePool.getClusterId(); |
| for (StoragePoolVO vmPool : matchingVMPools) { |
| long vmPoolId = vmPool.getId(); |
| Long vmPoolDcId = vmPool.getDataCenterId(); |
| Long vmPoolPodId = vmPool.getPodId(); |
| Long vmPoolClusterId = vmPool.getClusterId(); |
| |
| // Moving a volume is not required if storage pools belongs to same cluster in case of shared volume or |
| // identical storage pool in case of local |
| if (sourcePoolDcId == vmPoolDcId && sourcePoolPodId == vmPoolPodId && sourcePoolClusterId == vmPoolClusterId |
| && (isVolumeOnSharedPool || sourcePoolId == vmPoolId)) { |
| moveVolumeNeeded = false; |
| break; |
| } |
| } |
| } |
| |
| if (moveVolumeNeeded) { |
| if (isVolumeOnSharedPool) { |
| // Move the volume to a storage pool in the VM's zone, pod, or cluster |
| try { |
| volume = _storageMgr.moveVolume(volume, vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(), dataDiskHyperType); |
| } catch (ConcurrentOperationException e) { |
| throw new CloudRuntimeException(e.toString()); |
| } |
| } else { |
| throw new CloudRuntimeException("Failed to attach local data volume " + volume.getName() + " to VM " + vm.getDisplayName() + " as migration of local data volume is not allowed"); |
| } |
| } |
| } |
| |
| AsyncJobExecutor asyncExecutor = BaseAsyncJobExecutor.getCurrentExecutor(); |
| if (asyncExecutor != null) { |
| AsyncJobVO job = asyncExecutor.getJob(); |
| |
| if (s_logger.isInfoEnabled()) { |
| s_logger.info("Trying to attaching volume " + volumeId + " to vm instance:" + vm.getId() + ", update async job-" + job.getId() + " progress status"); |
| } |
| |
| _asyncMgr.updateAsyncJobAttachment(job.getId(), "volume", volumeId); |
| _asyncMgr.updateAsyncJobStatus(job.getId(), BaseCmd.PROGRESS_INSTANCE_CREATED, volumeId); |
| } |
| |
| String errorMsg = "Failed to attach volume: " + volume.getName() + " to VM: " + vm.getHostName(); |
| boolean sendCommand = (vm.getState() == State.Running); |
| AttachVolumeAnswer answer = null; |
| Long hostId = vm.getHostId(); |
| if (hostId == null) { |
| hostId = vm.getLastHostId(); |
| HostVO host = _hostDao.findById(hostId); |
| if (host != null && host.getHypervisorType() == HypervisorType.VMware) { |
| sendCommand = true; |
| } |
| } |
| |
| if (sendCommand) { |
| StoragePoolVO volumePool = _storagePoolDao.findById(volume.getPoolId()); |
| AttachVolumeCommand cmd = new AttachVolumeCommand(true, vm.getInstanceName(), volume.getPoolType(), volume.getFolder(), volume.getPath(), volume.getName(), deviceId, volume.getChainInfo()); |
| cmd.setPoolUuid(volumePool.getUuid()); |
| |
| try { |
| answer = (AttachVolumeAnswer) _agentMgr.send(hostId, cmd); |
| } catch (Exception e) { |
| throw new CloudRuntimeException(errorMsg + " due to: " + e.getMessage()); |
| } |
| } |
| |
| if (!sendCommand || (answer != null && answer.getResult())) { |
| // Mark the volume as attached |
| if (sendCommand) { |
| _volsDao.attachVolume(volume.getId(), vmId, answer.getDeviceId()); |
| } else { |
| _volsDao.attachVolume(volume.getId(), vmId, deviceId); |
| } |
| return _volsDao.findById(volumeId); |
| } else { |
| if (answer != null) { |
| String details = answer.getDetails(); |
| if (details != null && !details.isEmpty()) { |
| errorMsg += "; " + details; |
| } |
| } |
| throw new CloudRuntimeException(errorMsg); |
| } |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DETACH, eventDescription = "detaching volume", async = true) |
| public Volume detachVolumeFromVM(DetachVolumeCmd cmmd) { |
| Account caller = UserContext.current().getCaller(); |
| if ((cmmd.getId() == null && cmmd.getDeviceId() == null && cmmd.getVirtualMachineId() == null) || (cmmd.getId() != null && (cmmd.getDeviceId() != null || cmmd.getVirtualMachineId() != null)) |
| || (cmmd.getId() == null && (cmmd.getDeviceId() == null || cmmd.getVirtualMachineId() == null))) { |
| throw new InvalidParameterValueException("Please provide either a volume id, or a tuple(device id, instance id)"); |
| } |
| |
| Long volumeId = cmmd.getId(); |
| VolumeVO volume = null; |
| |
| if (volumeId != null) { |
| volume = _volsDao.findById(volumeId); |
| } else { |
| volume = _volsDao.findByInstanceAndDeviceId(cmmd.getVirtualMachineId(), cmmd.getDeviceId()).get(0); |
| } |
| |
| Long vmId = null; |
| |
| if (cmmd.getVirtualMachineId() == null) { |
| vmId = volume.getInstanceId(); |
| } else { |
| vmId = cmmd.getVirtualMachineId(); |
| } |
| |
| // Check that the volume ID is valid |
| if (volume == null) { |
| throw new InvalidParameterValueException("Unable to find volume with ID: " + volumeId); |
| } |
| |
| // Permissions check |
| _accountMgr.checkAccess(caller, null, true, volume); |
| |
| // Check that the volume is a data volume |
| if (volume.getVolumeType() != Volume.Type.DATADISK) { |
| throw new InvalidParameterValueException("Please specify a data volume."); |
| } |
| |
| // Check that the volume is currently attached to a VM |
| if (vmId == null) { |
| throw new InvalidParameterValueException("The specified volume is not attached to a VM."); |
| } |
| |
| // Check that the VM is in the correct state |
| UserVmVO vm = _vmDao.findById(vmId); |
| if (vm.getState() != State.Running && vm.getState() != State.Stopped && vm.getState() != State.Destroyed) { |
| throw new InvalidParameterValueException("Please specify a VM that is either running or stopped."); |
| } |
| |
| AsyncJobExecutor asyncExecutor = BaseAsyncJobExecutor.getCurrentExecutor(); |
| if (asyncExecutor != null) { |
| AsyncJobVO job = asyncExecutor.getJob(); |
| |
| if (s_logger.isInfoEnabled()) { |
| s_logger.info("Trying to attaching volume " + volumeId + "to vm instance:" + vm.getId() + ", update async job-" + job.getId() + " progress status"); |
| } |
| |
| _asyncMgr.updateAsyncJobAttachment(job.getId(), "volume", volumeId); |
| _asyncMgr.updateAsyncJobStatus(job.getId(), BaseCmd.PROGRESS_INSTANCE_CREATED, volumeId); |
| } |
| |
| String errorMsg = "Failed to detach volume: " + volume.getName() + " from VM: " + vm.getHostName(); |
| boolean sendCommand = (vm.getState() == State.Running); |
| Answer answer = null; |
| |
| if (sendCommand) { |
| AttachVolumeCommand cmd = new AttachVolumeCommand(false, vm.getInstanceName(), volume.getPoolType(), volume.getFolder(), volume.getPath(), volume.getName(), |
| cmmd.getDeviceId() != null ? cmmd.getDeviceId() : volume.getDeviceId(), volume.getChainInfo()); |
| |
| StoragePoolVO volumePool = _storagePoolDao.findById(volume.getPoolId()); |
| cmd.setPoolUuid(volumePool.getUuid()); |
| |
| try { |
| answer = _agentMgr.send(vm.getHostId(), cmd); |
| } catch (Exception e) { |
| throw new CloudRuntimeException(errorMsg + " due to: " + e.getMessage()); |
| } |
| } |
| |
| if (!sendCommand || (answer != null && answer.getResult())) { |
| // Mark the volume as detached |
| _volsDao.detachVolume(volume.getId()); |
| if (answer != null && answer instanceof AttachVolumeAnswer) { |
| volume.setChainInfo(((AttachVolumeAnswer) answer).getChainInfo()); |
| _volsDao.update(volume.getId(), volume); |
| } |
| |
| return _volsDao.findById(volumeId); |
| } else { |
| |
| if (answer != null) { |
| String details = answer.getDetails(); |
| if (details != null && !details.isEmpty()) { |
| errorMsg += "; " + details; |
| } |
| } |
| |
| throw new CloudRuntimeException(errorMsg); |
| } |
| } |
| |
| @Override |
| public boolean attachISOToVM(long vmId, long isoId, boolean attach) { |
| UserVmVO vm = _vmDao.findById(vmId); |
| |
| if (vm == null) { |
| return false; |
| } else if (vm.getState() != State.Running) { |
| return true; |
| } |
| String isoPath; |
| VMTemplateVO tmplt = _templateDao.findById(isoId); |
| if (tmplt == null) { |
| s_logger.warn("ISO: " + isoId + " does not exist"); |
| return false; |
| } |
| // Get the path of the ISO |
| Pair<String, String> isoPathPair = null; |
| if (tmplt.getTemplateType() == TemplateType.PERHOST) { |
| isoPath = tmplt.getName(); |
| } else { |
| isoPathPair = _storageMgr.getAbsoluteIsoPath(isoId, vm.getDataCenterIdToDeployIn()); |
| if (isoPathPair == null) { |
| s_logger.warn("Couldn't get absolute iso path"); |
| return false; |
| } else { |
| isoPath = isoPathPair.first(); |
| } |
| } |
| |
| String vmName = vm.getInstanceName(); |
| |
| HostVO host = _hostDao.findById(vm.getHostId()); |
| if (host == null) { |
| s_logger.warn("Host: " + vm.getHostId() + " does not exist"); |
| return false; |
| } |
| AttachIsoCommand cmd = new AttachIsoCommand(vmName, isoPath, attach); |
| if (isoPathPair != null) { |
| cmd.setStoreUrl(isoPathPair.second()); |
| } |
| Answer a = _agentMgr.easySend(vm.getHostId(), cmd); |
| |
| return (a != null && a.getResult()); |
| } |
| |
| private UserVm rebootVirtualMachine(long userId, long vmId) throws InsufficientCapacityException, ResourceUnavailableException { |
| UserVmVO vm = _vmDao.findById(vmId); |
| User caller = _accountMgr.getActiveUser(userId); |
| Account owner = _accountMgr.getAccount(vm.getAccountId()); |
| |
| if (vm == null || vm.getState() == State.Destroyed || vm.getState() == State.Expunging || vm.getRemoved() != null) { |
| s_logger.warn("Vm id=" + vmId + " doesn't exist"); |
| return null; |
| } |
| |
| if (vm.getState() == State.Running && vm.getHostId() != null) { |
| return _itMgr.reboot(vm, null, caller, owner); |
| } else { |
| s_logger.error("Vm id=" + vmId + " is not in Running state, failed to reboot"); |
| return null; |
| } |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_UPGRADE, eventDescription = "upgrading Vm") |
| /* |
| * TODO: cleanup eventually - Refactored API call |
| */ |
| public UserVm upgradeVirtualMachine(UpgradeVMCmd cmd) { |
| Long vmId = cmd.getId(); |
| Long svcOffId = cmd.getServiceOfferingId(); |
| Account caller = UserContext.current().getCaller(); |
| |
| // Verify input parameters |
| UserVmVO vmInstance = _vmDao.findById(vmId); |
| if (vmInstance == null) { |
| throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); |
| } |
| |
| _accountMgr.checkAccess(caller, null, true, vmInstance); |
| |
| // Check that the specified service offering ID is valid |
| _itMgr.checkIfCanUpgrade(vmInstance, svcOffId); |
| |
| _itMgr.upgradeVmDb(vmId, svcOffId); |
| |
| return _vmDao.findById(vmInstance.getId()); |
| } |
| |
| @Override |
| public UserVm addNicToVirtualMachine(AddNicToVMCmd cmd) throws InvalidParameterValueException, PermissionDeniedException, CloudRuntimeException { |
| Long vmId = cmd.getVmId(); |
| Long networkId = cmd.getNetworkId(); |
| String ipAddress = cmd.getIpAddress(); |
| Account caller = UserContext.current().getCaller(); |
| |
| UserVmVO vmInstance = _vmDao.findById(vmId); |
| if(vmInstance == null) { |
| throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); |
| } |
| NetworkVO network = _networkDao.findById(networkId); |
| if(network == null) { |
| throw new InvalidParameterValueException("unable to find a network with id " + networkId); |
| } |
| NicProfile profile = new NicProfile(null, null); |
| if(ipAddress != null) { |
| profile = new NicProfile(ipAddress, null); |
| } |
| |
| // Perform permission check on VM |
| _accountMgr.checkAccess(caller, null, true, vmInstance); |
| |
| // Verify that zone is not Basic |
| DataCenterVO dc = _dcDao.findById(vmInstance.getDataCenterIdToDeployIn()); |
| if (dc.getNetworkType() == DataCenter.NetworkType.Basic) { |
| throw new CloudRuntimeException("Zone " + vmInstance.getDataCenterIdToDeployIn() + ", has a NetworkType of Basic. Can't add a new NIC to a VM on a Basic Network"); |
| } |
| |
| // Perform account permission check on network |
| if (network.getGuestType() != Network.GuestType.Shared) { |
| // Check account permissions |
| List<NetworkVO> networkMap = _networkDao.listBy(caller.getId(), network.getId()); |
| if ((networkMap == null || networkMap.isEmpty() ) && caller.getType() != Account.ACCOUNT_TYPE_ADMIN) { |
| throw new PermissionDeniedException("Unable to modify a vm using network with id " + network.getId() + ", permission denied"); |
| } |
| } |
| |
| //ensure network belongs in zone |
| if (network.getDataCenterId() != vmInstance.getDataCenterIdToDeployIn()) { |
| throw new CloudRuntimeException(vmInstance + " is in zone:" + vmInstance.getDataCenterIdToDeployIn() + " but " + network + " is in zone:" + network.getDataCenterId()); |
| } |
| |
| if(_networkModel.getNicInNetwork(vmInstance.getId(),network.getId()) != null){ |
| s_logger.debug(vmInstance + " already in " + network + " going to add another NIC"); |
| } else { |
| //* get all vms hostNames in the network |
| List<String> hostNames = _vmInstanceDao.listDistinctHostNames(network.getId()); |
| //* verify that there are no duplicates |
| if (hostNames.contains(vmInstance.getHostName())) { |
| throw new CloudRuntimeException(network + " already has a vm with host name: '" + vmInstance.getHostName()); |
| } |
| } |
| |
| NicProfile guestNic = null; |
| |
| try { |
| guestNic = _itMgr.addVmToNetwork(vmInstance, network, profile); |
| } catch (ResourceUnavailableException e) { |
| throw new CloudRuntimeException("Unable to add NIC to " + vmInstance + ": " + e); |
| } catch (InsufficientCapacityException e) { |
| throw new CloudRuntimeException("Insufficient capacity when adding NIC to " + vmInstance + ": " + e); |
| } catch (ConcurrentOperationException e) { |
| throw new CloudRuntimeException("Concurrent operations on adding NIC to " + vmInstance + ": " +e); |
| } |
| if (guestNic == null) { |
| throw new CloudRuntimeException("Unable to add NIC to " + vmInstance); |
| } |
| |
| s_logger.debug("Successful addition of " + network + " from " + vmInstance); |
| return _vmDao.findById(vmInstance.getId()); |
| } |
| |
| @Override |
| public UserVm removeNicFromVirtualMachine(RemoveNicFromVMCmd cmd) throws InvalidParameterValueException, PermissionDeniedException, CloudRuntimeException { |
| Long vmId = cmd.getVmId(); |
| Long nicId = cmd.getNicId(); |
| Account caller = UserContext.current().getCaller(); |
| |
| UserVmVO vmInstance = _vmDao.findById(vmId); |
| if(vmInstance == null) { |
| throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); |
| } |
| NicVO nic = _nicDao.findById(nicId); |
| if (nic == null){ |
| throw new InvalidParameterValueException("unable to find a nic with id " + nicId); |
| } |
| NetworkVO network = _networkDao.findById(nic.getNetworkId()); |
| if(network == null) { |
| throw new InvalidParameterValueException("unable to find a network with id " + nic.getNetworkId()); |
| } |
| |
| // Perform permission check on VM |
| _accountMgr.checkAccess(caller, null, true, vmInstance); |
| |
| // Verify that zone is not Basic |
| DataCenterVO dc = _dcDao.findById(vmInstance.getDataCenterIdToDeployIn()); |
| if (dc.getNetworkType() == DataCenter.NetworkType.Basic) { |
| throw new CloudRuntimeException("Zone " + vmInstance.getDataCenterIdToDeployIn() + ", has a NetworkType of Basic. Can't remove a NIC from a VM on a Basic Network"); |
| } |
| |
| //check to see if nic is attached to VM |
| if (nic.getInstanceId() != vmId) { |
| throw new InvalidParameterValueException(nic + " is not a nic on " + vmInstance); |
| } |
| |
| // Perform account permission check on network |
| if (network.getGuestType() != Network.GuestType.Shared) { |
| // Check account permissions |
| List<NetworkVO> networkMap = _networkDao.listBy(caller.getId(), network.getId()); |
| if ((networkMap == null || networkMap.isEmpty() ) && caller.getType() != Account.ACCOUNT_TYPE_ADMIN) { |
| throw new PermissionDeniedException("Unable to modify a vm using network with id " + network.getId() + ", permission denied"); |
| } |
| } |
| |
| boolean nicremoved = false; |
| |
| try { |
| nicremoved = _itMgr.removeNicFromVm(vmInstance, nic); |
| } catch (ResourceUnavailableException e) { |
| throw new CloudRuntimeException("Unable to remove " + network + " from " + vmInstance +": " + e); |
| |
| } catch (ConcurrentOperationException e) { |
| throw new CloudRuntimeException("Concurrent operations on removing " + network + " from " + vmInstance + ": " + e); |
| } |
| |
| if (!nicremoved) { |
| throw new CloudRuntimeException("Unable to remove " + network + " from " + vmInstance ); |
| } |
| |
| s_logger.debug("Successful removal of " + network + " from " + vmInstance); |
| return _vmDao.findById(vmInstance.getId()); |
| |
| |
| } |
| |
| @Override |
| public UserVm updateDefaultNicForVirtualMachine(UpdateDefaultNicForVMCmd cmd) throws InvalidParameterValueException, CloudRuntimeException { |
| Long vmId = cmd.getVmId(); |
| Long nicId = cmd.getNicId(); |
| Account caller = UserContext.current().getCaller(); |
| |
| UserVmVO vmInstance = _vmDao.findById(vmId); |
| if (vmInstance == null){ |
| throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); |
| } |
| NicVO nic = _nicDao.findById(nicId); |
| if (nic == null){ |
| throw new InvalidParameterValueException("unable to find a nic with id " + nicId); |
| } |
| NetworkVO network = _networkDao.findById(nic.getNetworkId()); |
| if (network == null){ |
| throw new InvalidParameterValueException("unable to find a network with id " + nic.getNetworkId()); |
| } |
| |
| // Perform permission check on VM |
| _accountMgr.checkAccess(caller, null, true, vmInstance); |
| |
| // Verify that zone is not Basic |
| DataCenterVO dc = _dcDao.findById(vmInstance.getDataCenterIdToDeployIn()); |
| if (dc.getNetworkType() == DataCenter.NetworkType.Basic) { |
| throw new CloudRuntimeException("Zone " + vmInstance.getDataCenterIdToDeployIn() + ", has a NetworkType of Basic. Can't change default NIC on a Basic Network"); |
| } |
| |
| // no need to check permissions for network, we'll enumerate the ones they already have access to |
| Network existingdefaultnet = _networkModel.getDefaultNetworkForVm(vmId); |
| |
| //check to see if nic is attached to VM |
| if (nic.getInstanceId() != vmId) { |
| throw new InvalidParameterValueException(nic + " is not a nic on " + vmInstance); |
| } |
| // if current default equals chosen new default, Throw an exception |
| if (nic.isDefaultNic()){ |
| throw new CloudRuntimeException("refusing to set default nic because chosen nic is already the default"); |
| } |
| |
| //make sure the VM is Running or Stopped |
| if ((vmInstance.getState() != State.Running) && (vmInstance.getState() != State.Stopped)) { |
| throw new CloudRuntimeException("refusing to set default " + vmInstance + " is not Running or Stopped"); |
| } |
| |
| NicProfile existing = null; |
| List<NicProfile> nicProfiles = _networkMgr.getNicProfiles(vmInstance); |
| for (NicProfile nicProfile : nicProfiles) { |
| if(nicProfile.isDefaultNic() && nicProfile.getNetworkId() == existingdefaultnet.getId()){ |
| existing = nicProfile; |
| continue; |
| } |
| } |
| |
| if (existing == null){ |
| s_logger.warn("Failed to update default nic, no nic profile found for existing default network"); |
| throw new CloudRuntimeException("Failed to find a nic profile for the existing default network. This is bad and probably means some sort of configuration corruption"); |
| } |
| |
| NicVO existingVO = _nicDao.findById(existing.id); |
| Integer chosenID = nic.getDeviceId(); |
| Integer existingID = existing.getDeviceId(); |
| |
| nic.setDefaultNic(true); |
| nic.setDeviceId(existingID); |
| existingVO.setDefaultNic(false); |
| existingVO.setDeviceId(chosenID); |
| |
| nic = _nicDao.persist(nic); |
| existingVO = _nicDao.persist(existingVO); |
| |
| Network newdefault = null; |
| newdefault = _networkModel.getDefaultNetworkForVm(vmId); |
| |
| if (newdefault == null){ |
| nic.setDefaultNic(false); |
| nic.setDeviceId(chosenID); |
| existingVO.setDefaultNic(true); |
| existingVO.setDeviceId(existingID); |
| |
| nic = _nicDao.persist(nic); |
| existingVO = _nicDao.persist(existingVO); |
| |
| newdefault = _networkModel.getDefaultNetworkForVm(vmId); |
| if (newdefault.getId() == existingdefaultnet.getId()) { |
| throw new CloudRuntimeException("Setting a default nic failed, and we had no default nic, but we were able to set it back to the original"); |
| } |
| throw new CloudRuntimeException("Failed to change default nic to " + nic + " and now we have no default"); |
| } else if (newdefault.getId() == nic.getNetworkId()) { |
| s_logger.debug("successfully set default network to " + network + " for " + vmInstance); |
| return _vmDao.findById(vmInstance.getId()); |
| } |
| |
| 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 |
| public HashMap<Long, VmStatsEntry> getVirtualMachineStatistics(long hostId, String hostName, List<Long> vmIds) throws CloudRuntimeException { |
| HashMap<Long, VmStatsEntry> vmStatsById = new HashMap<Long, VmStatsEntry>(); |
| |
| if (vmIds.isEmpty()) { |
| return vmStatsById; |
| } |
| |
| List<String> vmNames = new ArrayList<String>(); |
| |
| for (Long vmId : vmIds) { |
| UserVmVO vm = _vmDao.findById(vmId); |
| vmNames.add(vm.getInstanceName()); |
| } |
| |
| Answer answer = _agentMgr.easySend(hostId, new GetVmStatsCommand(vmNames, _hostDao.findById(hostId).getGuid(), hostName)); |
| if (answer == null || !answer.getResult()) { |
| s_logger.warn("Unable to obtain VM statistics."); |
| return null; |
| } else { |
| HashMap<String, VmStatsEntry> vmStatsByName = ((GetVmStatsAnswer) answer).getVmStatsMap(); |
| |
| if (vmStatsByName == null) { |
| s_logger.warn("Unable to obtain VM statistics."); |
| return null; |
| } |
| |
| for (String vmName : vmStatsByName.keySet()) { |
| vmStatsById.put(vmIds.get(vmNames.indexOf(vmName)), vmStatsByName.get(vmName)); |
| } |
| } |
| |
| return vmStatsById; |
| } |
| |
| @Override |
| @DB |
| public UserVm recoverVirtualMachine(RecoverVMCmd cmd) throws ResourceAllocationException, CloudRuntimeException { |
| |
| Long vmId = cmd.getId(); |
| Account caller = UserContext.current().getCaller(); |
| |
| // Verify input parameters |
| UserVmVO vm = _vmDao.findById(vmId.longValue()); |
| |
| if (vm == null) { |
| throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); |
| } |
| |
| //check permissions |
| _accountMgr.checkAccess(caller, null, true, vm); |
| |
| if (vm.getRemoved() != null) { |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Unable to find vm or vm is removed: " + vmId); |
| } |
| throw new InvalidParameterValueException("Unable to find vm by id " + vmId); |
| } |
| |
| if (vm.getState() != State.Destroyed) { |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("vm is not in the right state: " + vmId); |
| } |
| throw new InvalidParameterValueException("Vm with id " + vmId + " is not in the right state"); |
| } |
| |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Recovering vm " + vmId); |
| } |
| |
| Transaction txn = Transaction.currentTxn(); |
| AccountVO account = null; |
| txn.start(); |
| |
| account = _accountDao.lockRow(vm.getAccountId(), true); |
| |
| // if the account is deleted, throw error |
| if (account.getRemoved() != null) { |
| throw new CloudRuntimeException("Unable to recover VM as the account is deleted"); |
| } |
| |
| // First check that the maximum number of UserVMs for the given accountId will not be exceeded |
| _resourceLimitMgr.checkResourceLimit(account, ResourceType.user_vm); |
| |
| _haMgr.cancelDestroy(vm, vm.getHostId()); |
| |
| try { |
| if (!_itMgr.stateTransitTo(vm, VirtualMachine.Event.RecoveryRequested, null)) { |
| s_logger.debug("Unable to recover the vm because it is not in the correct state: " + vmId); |
| throw new InvalidParameterValueException("Unable to recover the vm because it is not in the correct state: " + vmId); |
| } |
| } catch (NoTransitionException e){ |
| throw new InvalidParameterValueException("Unable to recover the vm because it is not in the correct state: " + vmId); |
| } |
| |
| // Recover the VM's disks |
| List<VolumeVO> volumes = _volsDao.findByInstance(vmId); |
| for (VolumeVO volume : volumes) { |
| if (volume.getVolumeType().equals(Volume.Type.ROOT)) { |
| // Create an event |
| Long templateId = volume.getTemplateId(); |
| Long diskOfferingId = volume.getDiskOfferingId(); |
| Long offeringId = null; |
| if (diskOfferingId != null) { |
| DiskOfferingVO offering = _diskOfferingDao.findById(diskOfferingId); |
| if (offering != null && (offering.getType() == DiskOfferingVO.Type.Disk)) { |
| offeringId = offering.getId(); |
| } |
| } |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), |
| volume.getDataCenterId(), volume.getId(), volume.getName(), offeringId, templateId, |
| volume.getSize(), Volume.class.getName(), volume.getUuid()); |
| } |
| } |
| |
| _resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.volume, new Long(volumes.size())); |
| |
| _resourceLimitMgr.incrementResourceCount(account.getId(), ResourceType.user_vm); |
| |
| txn.commit(); |
| |
| return _vmDao.findById(vmId); |
| } |
| |
| @Override |
| public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { |
| _name = name; |
| |
| ComponentLocator locator = ComponentLocator.getCurrentLocator(); |
| _configDao = locator.getDao(ConfigurationDao.class); |
| if (_configDao == null) { |
| throw new ConfigurationException("Unable to get the configuration dao."); |
| } |
| |
| Map<String, String> configs = _configDao.getConfiguration("AgentManager", params); |
| |
| _instance = configs.get("instance.name"); |
| if (_instance == null) { |
| _instance = "DEFAULT"; |
| } |
| |
| String value = _configDao.getValue(Config.CreatePrivateTemplateFromVolumeWait.toString()); |
| _createprivatetemplatefromvolumewait = NumbersUtil.parseInt(value, Integer.parseInt(Config.CreatePrivateTemplateFromVolumeWait.getDefaultValue())); |
| |
| value = _configDao.getValue(Config.CreatePrivateTemplateFromSnapshotWait.toString()); |
| _createprivatetemplatefromsnapshotwait = NumbersUtil.parseInt(value, Integer.parseInt(Config.CreatePrivateTemplateFromSnapshotWait.getDefaultValue())); |
| |
| String workers = configs.get("expunge.workers"); |
| int wrks = NumbersUtil.parseInt(workers, 10); |
| |
| String time = configs.get("expunge.interval"); |
| _expungeInterval = NumbersUtil.parseInt(time, 86400); |
| |
| time = configs.get("expunge.delay"); |
| _expungeDelay = NumbersUtil.parseInt(time, _expungeInterval); |
| |
| _executor = Executors.newScheduledThreadPool(wrks, new NamedThreadFactory("UserVm-Scavenger")); |
| |
| _itMgr.registerGuru(VirtualMachine.Type.User, this); |
| |
| VirtualMachine.State.getStateMachine().registerListener(new UserVmStateListener(_usageEventDao, _networkDao, _nicDao)); |
| |
| s_logger.info("User VM Manager is configured."); |
| |
| return true; |
| } |
| |
| @Override |
| public String getName() { |
| return _name; |
| } |
| |
| @Override |
| public boolean start() { |
| _executor.scheduleWithFixedDelay(new ExpungeTask(), _expungeInterval, _expungeInterval, TimeUnit.SECONDS); |
| return true; |
| } |
| |
| @Override |
| public boolean stop() { |
| _executor.shutdown(); |
| return true; |
| } |
| |
| protected UserVmManagerImpl() { |
| } |
| |
| public String getRandomPrivateTemplateName() { |
| return UUID.randomUUID().toString(); |
| } |
| |
| @Override |
| public Long convertToId(String vmName) { |
| if (!VirtualMachineName.isValidVmName(vmName, _instance)) { |
| return null; |
| } |
| return VirtualMachineName.getVmId(vmName); |
| } |
| |
| @Override |
| public boolean expunge(UserVmVO vm, long callerUserId, Account caller) { |
| UserContext ctx = UserContext.current(); |
| ctx.setAccountId(vm.getAccountId()); |
| |
| try { |
| //expunge the vm |
| if (!_itMgr.advanceExpunge(vm, _accountMgr.getSystemUser(), caller)) { |
| s_logger.info("Did not expunge " + vm); |
| return false; |
| } |
| |
| // Only if vm is not expunged already, cleanup it's resources |
| if (vm != null && vm.getRemoved() == null) { |
| // Cleanup vm resources - all the PF/LB/StaticNat rules associated with vm |
| s_logger.debug("Starting cleaning up vm " + vm + " resources..."); |
| if (cleanupVmResources(vm.getId())) { |
| s_logger.debug("Successfully cleaned up vm " + vm + " resources as a part of expunge process"); |
| } else { |
| s_logger.warn("Failed to cleanup resources as a part of vm " + vm + " expunge"); |
| return false; |
| } |
| |
| _itMgr.remove(vm, _accountMgr.getSystemUser(), caller); |
| } |
| |
| return true; |
| |
| } catch (ResourceUnavailableException e) { |
| s_logger.warn("Unable to expunge " + vm, e); |
| return false; |
| } catch (OperationTimedoutException e) { |
| s_logger.warn("Operation time out on expunging " + vm, e); |
| return false; |
| } catch (ConcurrentOperationException e) { |
| s_logger.warn("Concurrent operations on expunging " + vm, e); |
| return false; |
| } |
| } |
| |
| private boolean cleanupVmResources(long vmId) { |
| boolean success = true; |
| //Remove vm from security groups |
| _securityGroupMgr.removeInstanceFromGroups(vmId); |
| |
| //Remove vm from instance group |
| removeInstanceFromInstanceGroup(vmId); |
| |
| //cleanup firewall rules |
| if (_firewallMgr.revokeFirewallRulesForVm(vmId)) { |
| s_logger.debug("Firewall rules are removed successfully as a part of vm id=" + vmId + " expunge"); |
| } else { |
| success = false; |
| s_logger.warn("Fail to remove firewall rules as a part of vm id=" + vmId + " expunge"); |
| } |
| |
| //cleanup port forwarding rules |
| if (_rulesMgr.revokePortForwardingRulesForVm(vmId)) { |
| s_logger.debug("Port forwarding rules are removed successfully as a part of vm id=" + vmId + " expunge"); |
| } else { |
| success = false; |
| s_logger.warn("Fail to remove port forwarding rules as a part of vm id=" + vmId + " expunge"); |
| } |
| |
| // cleanup load balancer rules |
| if (_lbMgr.removeVmFromLoadBalancers(vmId)) { |
| s_logger.debug("Removed vm id=" + vmId + " from all load balancers as a part of expunge process"); |
| } else { |
| success = false; |
| s_logger.warn("Fail to remove vm id=" + vmId + " from load balancers as a part of expunge process"); |
| } |
| |
| // If vm is assigned to static nat, disable static nat for the ip address and disassociate ip if elasticIP is enabled |
| IPAddressVO ip = _ipAddressDao.findByAssociatedVmId(vmId); |
| try { |
| if (ip != null) { |
| if (_rulesMgr.disableStaticNat(ip.getId(), _accountMgr.getAccount(Account.ACCOUNT_ID_SYSTEM), User.UID_SYSTEM, true)) { |
| s_logger.debug("Disabled 1-1 nat for ip address " + ip + " as a part of vm id=" + vmId + " expunge"); |
| } else { |
| s_logger.warn("Failed to disable static nat for ip address " + ip + " as a part of vm id=" + vmId + " expunge"); |
| success = false; |
| } |
| } |
| } catch (ResourceUnavailableException e) { |
| success = false; |
| s_logger.warn("Failed to disable static nat for ip address " + ip + " as a part of vm id=" + vmId + " expunge because resource is unavailable", e); |
| } |
| |
| return success; |
| } |
| |
| @Override |
| public void deletePrivateTemplateRecord(Long templateId) { |
| if (templateId != null) { |
| _templateDao.remove(templateId); |
| } |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_TEMPLATE_CREATE, eventDescription = "creating template", create = true) |
| public VMTemplateVO createPrivateTemplateRecord(CreateTemplateCmd cmd, Account templateOwner) throws ResourceAllocationException { |
| Long userId = UserContext.current().getCallerUserId(); |
| |
| Account caller = UserContext.current().getCaller(); |
| boolean isAdmin = (isAdmin(caller.getType())); |
| |
| _accountMgr.checkAccess(caller, null, true, templateOwner); |
| |
| String name = cmd.getTemplateName(); |
| if ((name == null) || (name.length() > 32)) { |
| throw new InvalidParameterValueException("Template name cannot be null and should be less than 32 characters"); |
| } |
| |
| if(cmd.getTemplateTag() != null){ |
| if (!_accountService.isRootAdmin(caller.getType())){ |
| throw new PermissionDeniedException("Parameter templatetag can only be specified by a Root Admin, permission denied"); |
| } |
| } |
| |
| // do some parameter defaulting |
| Integer bits = cmd.getBits(); |
| Boolean requiresHvm = cmd.getRequiresHvm(); |
| Boolean passwordEnabled = cmd.isPasswordEnabled(); |
| Boolean isPublic = cmd.isPublic(); |
| Boolean featured = cmd.isFeatured(); |
| int bitsValue = ((bits == null) ? 64 : bits.intValue()); |
| boolean requiresHvmValue = ((requiresHvm == null) ? true : requiresHvm.booleanValue()); |
| boolean passwordEnabledValue = ((passwordEnabled == null) ? false : passwordEnabled.booleanValue()); |
| if (isPublic == null) { |
| isPublic = Boolean.FALSE; |
| } |
| boolean allowPublicUserTemplates = Boolean.parseBoolean(_configDao.getValue("allow.public.user.templates")); |
| if (!isAdmin && !allowPublicUserTemplates && isPublic) { |
| throw new PermissionDeniedException("Failed to create template " + name + ", only private templates can be created."); |
| } |
| |
| Long volumeId = cmd.getVolumeId(); |
| Long snapshotId = cmd.getSnapshotId(); |
| if ((volumeId == null) && (snapshotId == null)) { |
| throw new InvalidParameterValueException("Failed to create private template record, neither volume ID nor snapshot ID were specified."); |
| } |
| if ((volumeId != null) && (snapshotId != null)) { |
| throw new InvalidParameterValueException("Failed to create private template record, please specify only one of volume ID (" + volumeId + ") and snapshot ID (" + snapshotId + ")"); |
| } |
| |
| HypervisorType hyperType; |
| VolumeVO volume = null; |
| VMTemplateVO privateTemplate = null; |
| if (volumeId != null) { // create template from volume |
| volume = _volsDao.findById(volumeId); |
| if (volume == null) { |
| throw new InvalidParameterValueException("Failed to create private template record, unable to find volume " + volumeId); |
| } |
| //check permissions |
| _accountMgr.checkAccess(caller, null, true, volume); |
| |
| // If private template is created from Volume, check that the volume will not be active when the private template is |
| // created |
| if (!_storageMgr.volumeInactive(volume)) { |
| String msg = "Unable to create private template for volume: " + volume.getName() + "; volume is attached to a non-stopped VM, please stop the VM first"; |
| if (s_logger.isInfoEnabled()) { |
| s_logger.info(msg); |
| } |
| throw new CloudRuntimeException(msg); |
| } |
| hyperType = _volsDao.getHypervisorType(volumeId); |
| } else { // create template from snapshot |
| SnapshotVO snapshot = _snapshotDao.findById(snapshotId); |
| if (snapshot == null) { |
| throw new InvalidParameterValueException("Failed to create private template record, unable to find snapshot " + snapshotId); |
| } |
| |
| volume = _volsDao.findById(snapshot.getVolumeId()); |
| VolumeVO snapshotVolume = _volsDao.findByIdIncludingRemoved(snapshot.getVolumeId()); |
| |
| //check permissions |
| _accountMgr.checkAccess(caller, null, true, snapshot); |
| |
| if (snapshot.getState() != Snapshot.State.BackedUp) { |
| throw new InvalidParameterValueException("Snapshot id=" + snapshotId + " is not in " + Snapshot.State.BackedUp + " state yet and can't be used for template creation"); |
| } |
| |
| /* |
| // bug #11428. Operation not supported if vmware and snapshots parent volume = ROOT |
| if(snapshot.getHypervisorType() == HypervisorType.VMware && snapshotVolume.getVolumeType() == Type.DATADISK){ |
| throw new UnsupportedServiceException("operation not supported, snapshot with id " + snapshotId + " is created from Data Disk"); |
| } |
| */ |
| |
| hyperType = snapshot.getHypervisorType(); |
| } |
| |
| _resourceLimitMgr.checkResourceLimit(templateOwner, ResourceType.template); |
| |
| if (!isAdmin || featured == null) { |
| featured = Boolean.FALSE; |
| } |
| Long guestOSId = cmd.getOsTypeId(); |
| GuestOSVO guestOS = _guestOSDao.findById(guestOSId); |
| if (guestOS == null) { |
| throw new InvalidParameterValueException("GuestOS with ID: " + guestOSId + " does not exist."); |
| } |
| |
| String uniqueName = Long.valueOf((userId == null) ? 1 : userId).toString() + UUID.nameUUIDFromBytes(name.getBytes()).toString(); |
| Long nextTemplateId = _templateDao.getNextInSequence(Long.class, "id"); |
| String description = cmd.getDisplayText(); |
| boolean isExtractable = false; |
| Long sourceTemplateId = null; |
| if (volume != null) { |
| VMTemplateVO template = ApiDBUtils.findTemplateById(volume.getTemplateId()); |
| isExtractable = template != null && template.isExtractable() && template.getTemplateType() != Storage.TemplateType.SYSTEM; |
| if (template != null){ |
| sourceTemplateId = template.getId(); |
| }else if (volume.getVolumeType() == Type.ROOT){ //vm created out of blank template |
| UserVm userVm = ApiDBUtils.findUserVmById(volume.getInstanceId()); |
| sourceTemplateId = userVm.getIsoId(); |
| } |
| } |
| String templateTag = cmd.getTemplateTag(); |
| if(templateTag != null){ |
| if(s_logger.isDebugEnabled()){ |
| s_logger.debug("Adding template tag: "+templateTag); |
| } |
| } |
| privateTemplate = new VMTemplateVO(nextTemplateId, uniqueName, name, ImageFormat.RAW, isPublic, featured, isExtractable, TemplateType.USER, null, null, requiresHvmValue, bitsValue, templateOwner.getId(), |
| null, description, passwordEnabledValue, guestOS.getId(), true, hyperType, templateTag, cmd.getDetails()); |
| if(sourceTemplateId != null){ |
| if(s_logger.isDebugEnabled()){ |
| s_logger.debug("This template is getting created from other template, setting source template Id to: "+sourceTemplateId); |
| } |
| } |
| privateTemplate.setSourceTemplateId(sourceTemplateId); |
| |
| VMTemplateVO template = _templateDao.persist(privateTemplate); |
| // Increment the number of templates |
| if (template != null) { |
| if(cmd.getDetails() != null) { |
| _templateDetailsDao.persist(template.getId(), cmd.getDetails()); |
| } |
| |
| _resourceLimitMgr.incrementResourceCount(templateOwner.getId(), ResourceType.template); |
| } |
| |
| if (template != null){ |
| return template; |
| }else { |
| throw new CloudRuntimeException("Failed to create a template"); |
| } |
| |
| } |
| |
| @Override |
| @DB |
| @ActionEvent(eventType = EventTypes.EVENT_TEMPLATE_CREATE, eventDescription = "creating template", async = true) |
| public VMTemplateVO createPrivateTemplate(CreateTemplateCmd command) throws CloudRuntimeException { |
| Long userId = UserContext.current().getCallerUserId(); |
| if (userId == null) { |
| userId = User.UID_SYSTEM; |
| } |
| long templateId = command.getEntityId(); |
| Long volumeId = command.getVolumeId(); |
| Long snapshotId = command.getSnapshotId(); |
| SnapshotCommand cmd = null; |
| VMTemplateVO privateTemplate = null; |
| |
| String uniqueName = getRandomPrivateTemplateName(); |
| |
| StoragePoolVO pool = null; |
| HostVO secondaryStorageHost = null; |
| Long zoneId = null; |
| Long accountId = null; |
| SnapshotVO snapshot = null; |
| String secondaryStorageURL = null; |
| try { |
| if (snapshotId != null) { // create template from snapshot |
| snapshot = _snapshotDao.findById(snapshotId); |
| if (snapshot == null) { |
| throw new CloudRuntimeException("Unable to find Snapshot for Id " + snapshotId); |
| } |
| zoneId = snapshot.getDataCenterId(); |
| secondaryStorageHost = _snapshotMgr.getSecondaryStorageHost(snapshot); |
| secondaryStorageURL = _snapshotMgr.getSecondaryStorageURL(snapshot); |
| String name = command.getTemplateName(); |
| String backupSnapshotUUID = snapshot.getBackupSnapshotId(); |
| if (backupSnapshotUUID == null) { |
| throw new CloudRuntimeException("Unable to create private template from snapshot " + snapshotId + " due to there is no backupSnapshotUUID for this snapshot"); |
| } |
| |
| Long dcId = snapshot.getDataCenterId(); |
| accountId = snapshot.getAccountId(); |
| volumeId = snapshot.getVolumeId(); |
| |
| String origTemplateInstallPath = null; |
| List<StoragePoolVO> pools = _storageMgr.ListByDataCenterHypervisor(zoneId, snapshot.getHypervisorType()); |
| if (pools == null || pools.size() == 0 ) { |
| throw new CloudRuntimeException("Unable to find storage pools in zone " + zoneId); |
| } |
| pool = pools.get(0); |
| if (snapshot.getVersion() != null && snapshot.getVersion().equalsIgnoreCase("2.1")) { |
| VolumeVO volume = _volsDao.findByIdIncludingRemoved(volumeId); |
| if (volume == null) { |
| throw new CloudRuntimeException("failed to upgrade snapshot " + snapshotId + " due to unable to find orignal volume:" + volumeId + ", try it later "); |
| } |
| if ( volume.getTemplateId() == null ) { |
| _snapshotDao.updateSnapshotVersion(volumeId, "2.1", "2.2"); |
| } else { |
| VMTemplateVO template = _templateDao.findByIdIncludingRemoved(volume.getTemplateId()); |
| if (template == null) { |
| throw new CloudRuntimeException("failed to upgrade snapshot " + snapshotId + " due to unalbe to find orignal template :" + volume.getTemplateId() + ", try it later "); |
| } |
| Long origTemplateId = template.getId(); |
| Long origTmpltAccountId = template.getAccountId(); |
| if (!_volsDao.lockInLockTable(volumeId.toString(), 10)) { |
| throw new CloudRuntimeException("failed to upgrade snapshot " + snapshotId + " due to volume:" + volumeId + " is being used, try it later "); |
| } |
| cmd = new UpgradeSnapshotCommand(null, secondaryStorageURL, dcId, accountId, volumeId, origTemplateId, origTmpltAccountId, null, snapshot.getBackupSnapshotId(), |
| snapshot.getName(), "2.1"); |
| if (!_volsDao.lockInLockTable(volumeId.toString(), 10)) { |
| throw new CloudRuntimeException("Creating template failed due to volume:" + volumeId + " is being used, try it later "); |
| } |
| Answer answer = null; |
| try { |
| answer = _storageMgr.sendToPool(pool, cmd); |
| cmd = null; |
| } catch (StorageUnavailableException e) { |
| } finally { |
| _volsDao.unlockFromLockTable(volumeId.toString()); |
| } |
| if ((answer != null) && answer.getResult()) { |
| _snapshotDao.updateSnapshotVersion(volumeId, "2.1", "2.2"); |
| } else { |
| throw new CloudRuntimeException("Unable to upgrade snapshot"); |
| } |
| } |
| } |
| if( snapshot.getSwiftId() != null && snapshot.getSwiftId() != 0 ) { |
| _snapshotMgr.downloadSnapshotsFromSwift(snapshot); |
| } |
| cmd = new CreatePrivateTemplateFromSnapshotCommand(pool, secondaryStorageURL, dcId, accountId, snapshot.getVolumeId(), backupSnapshotUUID, snapshot.getName(), |
| origTemplateInstallPath, templateId, name, _createprivatetemplatefromsnapshotwait); |
| } else if (volumeId != null) { |
| VolumeVO volume = _volsDao.findById(volumeId); |
| if (volume == null) { |
| throw new CloudRuntimeException("Unable to find volume for Id " + volumeId); |
| } |
| accountId = volume.getAccountId(); |
| |
| if (volume.getPoolId() == null) { |
| _templateDao.remove(templateId); |
| throw new CloudRuntimeException("Volume " + volumeId + " is empty, can't create template on it"); |
| } |
| String vmName = _storageMgr.getVmNameOnVolume(volume); |
| zoneId = volume.getDataCenterId(); |
| secondaryStorageHost = _storageMgr.getSecondaryStorageHost(zoneId); |
| if (secondaryStorageHost == null) { |
| throw new CloudRuntimeException("Can not find the secondary storage for zoneId " + zoneId); |
| } |
| secondaryStorageURL = secondaryStorageHost.getStorageUrl(); |
| |
| pool = _storagePoolDao.findById(volume.getPoolId()); |
| cmd = new CreatePrivateTemplateFromVolumeCommand(pool, secondaryStorageURL, templateId, accountId, command.getTemplateName(), uniqueName, volume.getPath(), vmName, _createprivatetemplatefromvolumewait); |
| |
| } else { |
| throw new CloudRuntimeException("Creating private Template need to specify snapshotId or volumeId"); |
| } |
| // FIXME: before sending the command, check if there's enough capacity |
| // on the storage server to create the template |
| |
| // This can be sent to a KVM host too. |
| CreatePrivateTemplateAnswer answer = null; |
| if (snapshotId != null) { |
| if (!_snapshotDao.lockInLockTable(snapshotId.toString(), 10)) { |
| throw new CloudRuntimeException("Creating template from snapshot failed due to snapshot:" + snapshotId + " is being used, try it later "); |
| } |
| } else { |
| if (!_volsDao.lockInLockTable(volumeId.toString(), 10)) { |
| throw new CloudRuntimeException("Creating template from volume failed due to volume:" + volumeId + " is being used, try it later "); |
| } |
| } |
| try { |
| answer = (CreatePrivateTemplateAnswer) _storageMgr.sendToPool(pool, cmd); |
| } catch (StorageUnavailableException e) { |
| } finally { |
| if (snapshotId != null) { |
| _snapshotDao.unlockFromLockTable(snapshotId.toString()); |
| } else { |
| _volsDao.unlockFromLockTable(volumeId.toString()); |
| } |
| } |
| if ((answer != null) && answer.getResult()) { |
| privateTemplate = _templateDao.findById(templateId); |
| String answerUniqueName = answer.getUniqueName(); |
| if (answerUniqueName != null) { |
| privateTemplate.setUniqueName(answerUniqueName); |
| } else { |
| privateTemplate.setUniqueName(uniqueName); |
| } |
| ImageFormat format = answer.getImageFormat(); |
| if (format != null) { |
| privateTemplate.setFormat(format); |
| } else { |
| // This never occurs. |
| // Specify RAW format makes it unusable for snapshots. |
| privateTemplate.setFormat(ImageFormat.RAW); |
| } |
| |
| String checkSum = getChecksum(secondaryStorageHost.getId(), answer.getPath()); |
| |
| Transaction txn = Transaction.currentTxn(); |
| |
| txn.start(); |
| |
| privateTemplate.setChecksum(checkSum); |
| _templateDao.update(templateId, privateTemplate); |
| |
| // add template zone ref for this template |
| _templateDao.addTemplateToZone(privateTemplate, zoneId); |
| VMTemplateHostVO templateHostVO = new VMTemplateHostVO(secondaryStorageHost.getId(), templateId); |
| templateHostVO.setDownloadPercent(100); |
| templateHostVO.setDownloadState(Status.DOWNLOADED); |
| templateHostVO.setInstallPath(answer.getPath()); |
| templateHostVO.setLastUpdated(new Date()); |
| templateHostVO.setSize(answer.getVirtualSize()); |
| templateHostVO.setPhysicalSize(answer.getphysicalSize()); |
| _templateHostDao.persist(templateHostVO); |
| |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_TEMPLATE_CREATE, privateTemplate.getAccountId(), |
| secondaryStorageHost.getDataCenterId(), privateTemplate.getId(), |
| privateTemplate.getName(), null, privateTemplate.getSourceTemplateId(), |
| templateHostVO.getSize(), VirtualMachineTemplate.class.getName(), privateTemplate.getUuid()); |
| txn.commit(); |
| } |
| } finally { |
| if (snapshot != null && snapshot.getSwiftId() != null && secondaryStorageURL != null && zoneId != null && accountId != null && volumeId != null) { |
| _snapshotMgr.deleteSnapshotsForVolume (secondaryStorageURL, zoneId, accountId, volumeId); |
| } |
| if (privateTemplate == null) { |
| Transaction txn = Transaction.currentTxn(); |
| txn.start(); |
| // Remove the template record |
| _templateDao.expunge(templateId); |
| |
| // decrement resource count |
| if (accountId != null) { |
| _resourceLimitMgr.decrementResourceCount(accountId, ResourceType.template); |
| } |
| txn.commit(); |
| } |
| } |
| |
| if (privateTemplate != null){ |
| return privateTemplate; |
| }else { |
| throw new CloudRuntimeException("Failed to create a template"); |
| } |
| } |
| |
| @Override |
| public String getChecksum(Long hostId, String templatePath){ |
| HostVO ssHost = _hostDao.findById(hostId); |
| Host.Type type = ssHost.getType(); |
| if( type != Host.Type.SecondaryStorage && type != Host.Type.LocalSecondaryStorage ) { |
| return null; |
| } |
| String secUrl = ssHost.getStorageUrl(); |
| Answer answer; |
| answer = _agentMgr.sendToSecStorage(ssHost, new ComputeChecksumCommand(secUrl, templatePath)); |
| if(answer != null && answer.getResult()) { |
| return answer.getDetails(); |
| } |
| return null; |
| } |
| |
| // used for vm transitioning to error state |
| private void updateVmStateForFailedVmCreation(Long vmId, Long hostId) { |
| |
| UserVmVO vm = _vmDao.findById(vmId); |
| |
| |
| if (vm != null) { |
| if (vm.getState().equals(State.Stopped)) { |
| s_logger.debug("Destroying vm " + vm + " as it failed to create on Host with Id:" + hostId); |
| try { |
| _itMgr.stateTransitTo(vm, VirtualMachine.Event.OperationFailedToError, null); |
| } catch (NoTransitionException e1) { |
| s_logger.warn(e1.getMessage()); |
| } |
| // destroy associated volumes for vm in error state |
| // get all volumes in non destroyed state |
| List<VolumeVO> volumesForThisVm = _volsDao.findUsableVolumesForInstance(vm.getId()); |
| for (VolumeVO volume : volumesForThisVm) { |
| try { |
| if (volume.getState() != Volume.State.Destroy) { |
| _storageMgr.destroyVolume(volume); |
| } |
| } catch (ConcurrentOperationException e) { |
| s_logger.warn("Unable to delete volume:" + volume.getId() + " for vm:" + vmId + " whilst transitioning to error state"); |
| } |
| } |
| String msg = "Failed to deploy Vm with Id: " + vmId + ", on Host with Id: " + hostId; |
| _alertMgr.sendAlert(AlertManager.ALERT_TYPE_USERVM, vm.getDataCenterIdToDeployIn(), vm.getPodIdToDeployIn(), msg, msg); |
| |
| _resourceLimitMgr.decrementResourceCount(vm.getAccountId(), ResourceType.user_vm); |
| } |
| } |
| } |
| |
| protected class ExpungeTask implements Runnable { |
| public ExpungeTask() { |
| } |
| |
| @Override |
| public void run() { |
| GlobalLock scanLock = GlobalLock.getInternLock("UserVMExpunge"); |
| try { |
| if (scanLock.lock(ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION)) { |
| try { |
| List<UserVmVO> vms = _vmDao.findDestroyedVms(new Date(System.currentTimeMillis() - ((long) _expungeDelay << 10))); |
| if (s_logger.isInfoEnabled()) { |
| if (vms.size() == 0) { |
| s_logger.trace("Found " + vms.size() + " vms to expunge."); |
| } else { |
| s_logger.info("Found " + vms.size() + " vms to expunge."); |
| } |
| } |
| for (UserVmVO vm : vms) { |
| try { |
| expunge(vm, _accountMgr.getSystemUser().getId(), _accountMgr.getSystemAccount()); |
| } catch (Exception e) { |
| s_logger.warn("Unable to expunge " + vm, e); |
| } |
| } |
| } catch (Exception e) { |
| s_logger.error("Caught the following Exception", e); |
| } finally { |
| scanLock.unlock(); |
| } |
| } |
| } finally { |
| scanLock.releaseRef(); |
| } |
| } |
| } |
| |
| private static boolean isAdmin(short accountType) { |
| return ((accountType == Account.ACCOUNT_TYPE_ADMIN) || (accountType == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN) || (accountType == Account.ACCOUNT_TYPE_DOMAIN_ADMIN) || (accountType == Account.ACCOUNT_TYPE_READ_ONLY_ADMIN)); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_UPDATE, eventDescription = "updating Vm") |
| public UserVm updateVirtualMachine(UpdateVMCmd cmd) |
| throws ResourceUnavailableException, InsufficientCapacityException { |
| String displayName = cmd.getDisplayName(); |
| String group = cmd.getGroup(); |
| Boolean ha = cmd.getHaEnable(); |
| Long id = cmd.getId(); |
| Long osTypeId = cmd.getOsTypeId(); |
| String userData = cmd.getUserData(); |
| |
| // Input validation |
| UserVmVO vmInstance = null; |
| |
| // Verify input parameters |
| vmInstance = _vmDao.findById(id.longValue()); |
| |
| if (vmInstance == null) { |
| throw new InvalidParameterValueException("unable to find virtual machine with id " + id); |
| } |
| |
| ServiceOffering offering = _serviceOfferingDao.findById(vmInstance.getServiceOfferingId()); |
| if (!offering.getOfferHA() && ha != null && ha) { |
| throw new InvalidParameterValueException("Can't enable ha for the vm as it's created from the Service offering having HA disabled"); |
| } |
| |
| _accountMgr.checkAccess(UserContext.current().getCaller(), null, true, vmInstance); |
| |
| if (displayName == null) { |
| displayName = vmInstance.getDisplayName(); |
| } |
| |
| if (ha == null) { |
| ha = vmInstance.isHaEnabled(); |
| } |
| |
| UserVmVO vm = _vmDao.findById(id); |
| if (vm == null) { |
| throw new CloudRuntimeException("Unable to find virual machine with id " + id); |
| } |
| |
| if (vm.getState() == State.Error || vm.getState() == State.Expunging) { |
| s_logger.error("vm is not in the right state: " + id); |
| throw new InvalidParameterValueException("Vm with id " + id + " is not in the right state"); |
| } |
| |
| boolean updateUserdata = false; |
| if (userData != null) { |
| // check and replace newlines |
| userData = userData.replace("\\n", ""); |
| validateUserData(userData); |
| // update userData on domain router. |
| updateUserdata = true; |
| } else { |
| userData = vmInstance.getUserData(); |
| } |
| |
| String description = ""; |
| |
| if (displayName != vmInstance.getDisplayName()) { |
| description += "New display name: " + displayName + ". "; |
| } |
| |
| if (ha != vmInstance.isHaEnabled()) { |
| if (ha) { |
| description += "Enabled HA. "; |
| } else { |
| description += "Disabled HA. "; |
| } |
| } |
| if (osTypeId == null) { |
| osTypeId = vmInstance.getGuestOSId(); |
| } else { |
| description += "Changed Guest OS Type to " + osTypeId + ". "; |
| } |
| |
| if (group != null) { |
| if (addInstanceToGroup(id, group)) { |
| description += "Added to group: " + group + "."; |
| } |
| } |
| |
| _vmDao.updateVM(id, displayName, ha, osTypeId, userData); |
| |
| if (updateUserdata) { |
| boolean result = updateUserDataInternal(_vmDao.findById(id)); |
| if (result) { |
| s_logger.debug("User data successfully updated for vm id="+id); |
| } else { |
| throw new CloudRuntimeException("Failed to reset userdata for the virtual machine "); |
| } |
| } |
| |
| return _vmDao.findById(id); |
| } |
| |
| private boolean updateUserDataInternal(UserVm vm) |
| throws ResourceUnavailableException, InsufficientCapacityException { |
| VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); |
| Nic defaultNic = _networkModel.getDefaultNic(vm.getId()); |
| if (defaultNic == null) { |
| s_logger.error("Unable to update userdata for vm id=" + vm.getId() + " as the instance doesn't have default nic"); |
| return false; |
| } |
| |
| Network defaultNetwork = _networkDao.findById(defaultNic.getNetworkId()); |
| NicProfile defaultNicProfile = new NicProfile(defaultNic, defaultNetwork, null, null, null, |
| _networkModel.isSecurityGroupSupportedInNetwork(defaultNetwork), |
| _networkModel.getNetworkTag(template.getHypervisorType(), defaultNetwork)); |
| |
| VirtualMachineProfile<VMInstanceVO> vmProfile = new VirtualMachineProfileImpl<VMInstanceVO>((VMInstanceVO)vm); |
| |
| UserDataServiceProvider element = _networkModel.getUserDataUpdateProvider(defaultNetwork); |
| if (element == null) { |
| throw new CloudRuntimeException("Can't find network element for " + Service.UserData.getName() + " provider needed for UserData update"); |
| } |
| boolean result = element.saveUserData(defaultNetwork, defaultNicProfile, vmProfile); |
| |
| return true; |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_START, eventDescription = "starting Vm", async = true) |
| public UserVm startVirtualMachine(StartVMCmd cmd) throws ExecutionException, ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { |
| return startVirtualMachine(cmd.getId(), cmd.getHostId(), null).first(); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_REBOOT, eventDescription = "rebooting Vm", async = true) |
| public UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException { |
| Account caller = UserContext.current().getCaller(); |
| Long vmId = cmd.getId(); |
| |
| // Verify input parameters |
| UserVmVO vmInstance = _vmDao.findById(vmId.longValue()); |
| if (vmInstance == null) { |
| throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); |
| } |
| |
| _accountMgr.checkAccess(caller, null, true, vmInstance); |
| |
| return rebootVirtualMachine(UserContext.current().getCallerUserId(), vmId); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_DESTROY, eventDescription = "destroying Vm", async = true) |
| public UserVm destroyVm(DestroyVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException { |
| return destroyVm(cmd.getId()); |
| } |
| |
| @Override |
| @DB |
| public InstanceGroupVO createVmGroup(CreateVMGroupCmd cmd) { |
| Account caller = UserContext.current().getCaller(); |
| Long domainId = cmd.getDomainId(); |
| String accountName = cmd.getAccountName(); |
| String groupName = cmd.getGroupName(); |
| Long projectId = cmd.getProjectId(); |
| |
| Account owner = _accountMgr.finalizeOwner(caller, accountName, domainId, projectId); |
| long accountId = owner.getId(); |
| |
| // Check if name is already in use by this account |
| boolean isNameInUse = _vmGroupDao.isNameInUse(accountId, groupName); |
| |
| if (isNameInUse) { |
| throw new InvalidParameterValueException("Unable to create vm group, a group with name " + groupName + " already exisits for account " + accountId); |
| } |
| |
| return createVmGroup(groupName, accountId); |
| } |
| |
| @DB |
| protected InstanceGroupVO createVmGroup(String groupName, long accountId) { |
| Account account = null; |
| final Transaction txn = Transaction.currentTxn(); |
| txn.start(); |
| try { |
| account = _accountDao.acquireInLockTable(accountId); // to ensure duplicate vm group names are not created. |
| if (account == null) { |
| s_logger.warn("Failed to acquire lock on account"); |
| return null; |
| } |
| InstanceGroupVO group = _vmGroupDao.findByAccountAndName(accountId, groupName); |
| if (group == null) { |
| group = new InstanceGroupVO(groupName, accountId); |
| group = _vmGroupDao.persist(group); |
| } |
| return group; |
| } finally { |
| if (account != null) { |
| _accountDao.releaseFromLockTable(accountId); |
| } |
| txn.commit(); |
| } |
| } |
| |
| @Override |
| public boolean deleteVmGroup(DeleteVMGroupCmd cmd) { |
| Account caller = UserContext.current().getCaller(); |
| Long groupId = cmd.getId(); |
| |
| // Verify input parameters |
| InstanceGroupVO group = _vmGroupDao.findById(groupId); |
| if ((group == null) || (group.getRemoved() != null)) { |
| throw new InvalidParameterValueException("unable to find a vm group with id " + groupId); |
| } |
| |
| _accountMgr.checkAccess(caller, null, true, group); |
| |
| return deleteVmGroup(groupId); |
| } |
| |
| @Override |
| public boolean deleteVmGroup(long groupId) { |
| // delete all the mappings from group_vm_map table |
| List<InstanceGroupVMMapVO> groupVmMaps = _groupVMMapDao.listByGroupId(groupId); |
| for (InstanceGroupVMMapVO groupMap : groupVmMaps) { |
| SearchCriteria<InstanceGroupVMMapVO> sc = _groupVMMapDao.createSearchCriteria(); |
| sc.addAnd("instanceId", SearchCriteria.Op.EQ, groupMap.getInstanceId()); |
| _groupVMMapDao.expunge(sc); |
| } |
| |
| if (_vmGroupDao.remove(groupId)) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| @DB |
| public boolean addInstanceToGroup(long userVmId, String groupName) { |
| UserVmVO vm = _vmDao.findById(userVmId); |
| |
| InstanceGroupVO group = _vmGroupDao.findByAccountAndName(vm.getAccountId(), groupName); |
| // Create vm group if the group doesn't exist for this account |
| if (group == null) { |
| group = createVmGroup(groupName, vm.getAccountId()); |
| } |
| |
| if (group != null) { |
| final Transaction txn = Transaction.currentTxn(); |
| txn.start(); |
| UserVm userVm = _vmDao.acquireInLockTable(userVmId); |
| if (userVm == null) { |
| s_logger.warn("Failed to acquire lock on user vm id=" + userVmId); |
| } |
| try { |
| // don't let the group be deleted when we are assigning vm to it. |
| InstanceGroupVO ngrpLock = _vmGroupDao.lockRow(group.getId(), false); |
| if (ngrpLock == null) { |
| s_logger.warn("Failed to acquire lock on vm group id=" + group.getId() + " name=" + group.getName()); |
| txn.rollback(); |
| return false; |
| } |
| |
| // Currently don't allow to assign a vm to more than one group |
| if (_groupVMMapDao.listByInstanceId(userVmId) != null) { |
| // Delete all mappings from group_vm_map table |
| List<InstanceGroupVMMapVO> groupVmMaps = _groupVMMapDao.listByInstanceId(userVmId); |
| for (InstanceGroupVMMapVO groupMap : groupVmMaps) { |
| SearchCriteria<InstanceGroupVMMapVO> sc = _groupVMMapDao.createSearchCriteria(); |
| sc.addAnd("instanceId", SearchCriteria.Op.EQ, groupMap.getInstanceId()); |
| _groupVMMapDao.expunge(sc); |
| } |
| } |
| InstanceGroupVMMapVO groupVmMapVO = new InstanceGroupVMMapVO(group.getId(), userVmId); |
| _groupVMMapDao.persist(groupVmMapVO); |
| |
| txn.commit(); |
| return true; |
| } finally { |
| if (userVm != null) { |
| _vmDao.releaseFromLockTable(userVmId); |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public InstanceGroupVO getGroupForVm(long vmId) { |
| // TODO - in future releases vm can be assigned to multiple groups; but currently return just one group per vm |
| try { |
| List<InstanceGroupVMMapVO> groupsToVmMap = _groupVMMapDao.listByInstanceId(vmId); |
| |
| if (groupsToVmMap != null && groupsToVmMap.size() != 0) { |
| InstanceGroupVO group = _vmGroupDao.findById(groupsToVmMap.get(0).getGroupId()); |
| return group; |
| } else { |
| return null; |
| } |
| } catch (Exception e) { |
| s_logger.warn("Error trying to get group for a vm: ", e); |
| return null; |
| } |
| } |
| |
| @Override |
| public void removeInstanceFromInstanceGroup(long vmId) { |
| try { |
| List<InstanceGroupVMMapVO> groupVmMaps = _groupVMMapDao.listByInstanceId(vmId); |
| for (InstanceGroupVMMapVO groupMap : groupVmMaps) { |
| SearchCriteria<InstanceGroupVMMapVO> sc = _groupVMMapDao.createSearchCriteria(); |
| sc.addAnd("instanceId", SearchCriteria.Op.EQ, groupMap.getInstanceId()); |
| _groupVMMapDao.expunge(sc); |
| } |
| } catch (Exception e) { |
| s_logger.warn("Error trying to remove vm from group: ", e); |
| } |
| } |
| |
| protected boolean validPassword(String password) { |
| if (password == null || password.length() == 0) { |
| return false; |
| } |
| for (int i = 0; i < password.length(); i++) { |
| if (password.charAt(i) == ' ') { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long> securityGroupIdList, Account owner, |
| String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, String sshKeyPair, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, String keyboard) |
| throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { |
| |
| Account caller = UserContext.current().getCaller(); |
| List<NetworkVO> networkList = new ArrayList<NetworkVO>(); |
| |
| // Verify that caller can perform actions in behalf of vm owner |
| _accountMgr.checkAccess(caller, null, true, owner); |
| |
| // Get default guest network in Basic zone |
| Network defaultNetwork = _networkModel.getExclusiveGuestNetwork(zone.getId()); |
| |
| if (defaultNetwork == null) { |
| throw new InvalidParameterValueException("Unable to find a default network to start a vm"); |
| } else { |
| networkList.add(_networkDao.findById(defaultNetwork.getId())); |
| } |
| |
| boolean isVmWare = (template.getHypervisorType() == HypervisorType.VMware || (hypervisor != null && hypervisor == HypervisorType.VMware)); |
| |
| if (securityGroupIdList != null && isVmWare) { |
| throw new InvalidParameterValueException("Security group feature is not supported for vmWare hypervisor"); |
| } else if (!isVmWare && _networkModel.isSecurityGroupSupportedInNetwork(defaultNetwork) && _networkModel.canAddDefaultSecurityGroup()) { |
| //add the default securityGroup only if no security group is specified |
| if(securityGroupIdList == null || securityGroupIdList.isEmpty()){ |
| if (securityGroupIdList == null) { |
| securityGroupIdList = new ArrayList<Long>(); |
| } |
| SecurityGroup defaultGroup = _securityGroupMgr.getDefaultSecurityGroup(owner.getId()); |
| if (defaultGroup != null) { |
| securityGroupIdList.add(defaultGroup.getId()); |
| } else { |
| //create default security group for the account |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Couldn't find default security group for the account " + owner + " so creating a new one"); |
| } |
| defaultGroup = _securityGroupMgr.createSecurityGroup(SecurityGroupManager.DEFAULT_GROUP_NAME, SecurityGroupManager.DEFAULT_GROUP_DESCRIPTION, owner.getDomainId(), owner.getId(), owner.getAccountName()); |
| securityGroupIdList.add(defaultGroup.getId()); |
| } |
| } |
| } |
| |
| return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, |
| diskSize, networkList, securityGroupIdList, group, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, keyboard); |
| } |
| |
| @Override |
| public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long> networkIdList, |
| List<Long> securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, |
| String sshKeyPair, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, String keyboard) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, |
| ResourceAllocationException { |
| |
| Account caller = UserContext.current().getCaller(); |
| List<NetworkVO> networkList = new ArrayList<NetworkVO>(); |
| boolean isSecurityGroupEnabledNetworkUsed = false; |
| boolean isVmWare = (template.getHypervisorType() == HypervisorType.VMware || (hypervisor != null && hypervisor == HypervisorType.VMware)); |
| |
| //Verify that caller can perform actions in behalf of vm owner |
| _accountMgr.checkAccess(caller, null, true, owner); |
| |
| // If no network is specified, find system security group enabled network |
| if (networkIdList == null || networkIdList.isEmpty()) { |
| Network networkWithSecurityGroup = _networkModel.getNetworkWithSecurityGroupEnabled(zone.getId()); |
| if (networkWithSecurityGroup == null) { |
| throw new InvalidParameterValueException("No network with security enabled is found in zone id=" + zone.getId()); |
| } |
| |
| networkList.add(_networkDao.findById(networkWithSecurityGroup.getId())); |
| isSecurityGroupEnabledNetworkUsed = true; |
| |
| } else if (securityGroupIdList != null && !securityGroupIdList.isEmpty()) { |
| if (isVmWare) { |
| throw new InvalidParameterValueException("Security group feature is not supported for vmWare hypervisor"); |
| } |
| // Only one network can be specified, and it should be security group enabled |
| if (networkIdList.size() > 1) { |
| throw new InvalidParameterValueException("Only support one network per VM if security group enabled"); |
| } |
| |
| NetworkVO network = _networkDao.findById(networkIdList.get(0).longValue()); |
| |
| if (network == null) { |
| throw new InvalidParameterValueException("Unable to find network by id " + networkIdList.get(0).longValue()); |
| } |
| |
| if (!_networkModel.isSecurityGroupSupportedInNetwork(network)) { |
| throw new InvalidParameterValueException("Network is not security group enabled: " + network.getId()); |
| } |
| |
| networkList.add(network); |
| isSecurityGroupEnabledNetworkUsed = true; |
| |
| } else { |
| // Verify that all the networks are Shared/Guest; can't create combination of SG enabled and disabled networks |
| for (Long networkId : networkIdList) { |
| NetworkVO network = _networkDao.findById(networkId); |
| |
| if (network == null) { |
| throw new InvalidParameterValueException("Unable to find network by id " + networkIdList.get(0).longValue()); |
| } |
| |
| boolean isSecurityGroupEnabled = _networkModel.isSecurityGroupSupportedInNetwork(network); |
| if (isSecurityGroupEnabled) { |
| if (networkIdList.size() > 1) { |
| throw new InvalidParameterValueException("Can't create a vm with multiple networks one of" + |
| " which is Security Group enabled"); |
| } |
| |
| isSecurityGroupEnabledNetworkUsed = true; |
| } |
| |
| if (!(network.getTrafficType() == TrafficType.Guest && network.getGuestType() == Network.GuestType.Shared)) { |
| throw new InvalidParameterValueException("Can specify only Shared Guest networks when" + |
| " deploy vm in Advance Security Group enabled zone"); |
| } |
| |
| // Perform account permission check |
| if (network.getAclType() == ACLType.Account) { |
| _accountMgr.checkAccess(caller, AccessType.UseNetwork, false, network); |
| } |
| networkList.add(network); |
| } |
| } |
| |
| // if network is security group enabled, and no security group is specified, then add the default security group automatically |
| if (isSecurityGroupEnabledNetworkUsed && !isVmWare && _networkModel.canAddDefaultSecurityGroup()) { |
| |
| //add the default securityGroup only if no security group is specified |
| if(securityGroupIdList == null || securityGroupIdList.isEmpty()){ |
| if (securityGroupIdList == null) { |
| securityGroupIdList = new ArrayList<Long>(); |
| } |
| |
| SecurityGroup defaultGroup = _securityGroupMgr.getDefaultSecurityGroup(owner.getId()); |
| if (defaultGroup != null) { |
| securityGroupIdList.add(defaultGroup.getId()); |
| } else { |
| //create default security group for the account |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Couldn't find default security group for the account " + owner + " so creating a new one"); |
| } |
| defaultGroup = _securityGroupMgr.createSecurityGroup(SecurityGroupManager.DEFAULT_GROUP_NAME, SecurityGroupManager.DEFAULT_GROUP_DESCRIPTION, owner.getDomainId(), owner.getId(), owner.getAccountName()); |
| securityGroupIdList.add(defaultGroup.getId()); |
| } |
| } |
| } |
| |
| return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, |
| diskSize, networkList, securityGroupIdList, group, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, keyboard); |
| } |
| |
| @Override |
| public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long> networkIdList, Account owner, String hostName, |
| String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, String userData, String sshKeyPair, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, String keyboard) |
| throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { |
| |
| Account caller = UserContext.current().getCaller(); |
| List<NetworkVO> networkList = new ArrayList<NetworkVO>(); |
| |
| // Verify that caller can perform actions in behalf of vm owner |
| _accountMgr.checkAccess(caller, null, true, owner); |
| |
| List<HypervisorType> vpcSupportedHTypes = _vpcMgr.getSupportedVpcHypervisors(); |
| if (networkIdList == null || networkIdList.isEmpty()) { |
| NetworkVO defaultNetwork = null; |
| |
| // if no network is passed in |
| // Check if default virtual network offering has Availability=Required. If it's true, search for corresponding |
| // network |
| // * if network is found, use it. If more than 1 virtual network is found, throw an error |
| // * if network is not found, create a new one and use it |
| |
| List<NetworkOfferingVO> requiredOfferings = _networkOfferingDao.listByAvailability(Availability.Required, false); |
| if (requiredOfferings.size() < 1) { |
| throw new InvalidParameterValueException("Unable to find network offering with availability=" + Availability.Required + " to automatically create the network as a part of vm creation"); |
| } |
| |
| if (requiredOfferings.get(0).getState() == NetworkOffering.State.Enabled) { |
| // get Virtual networks |
| List<? extends Network> virtualNetworks = _networkModel.listNetworksForAccount(owner.getId(), zone.getId(), Network.GuestType.Isolated); |
| if (virtualNetworks.isEmpty()) { |
| long physicalNetworkId = _networkModel.findPhysicalNetworkId(zone.getId(), requiredOfferings.get(0).getTags(), requiredOfferings.get(0).getTrafficType()); |
| // Validate physical network |
| PhysicalNetwork physicalNetwork = _physicalNetworkDao.findById(physicalNetworkId); |
| if (physicalNetwork == null) { |
| throw new InvalidParameterValueException("Unable to find physical network with id: "+physicalNetworkId + " and tag: " +requiredOfferings.get(0).getTags()); |
| } |
| s_logger.debug("Creating network for account " + owner + " from the network offering id=" +requiredOfferings.get(0).getId() + " as a part of deployVM process"); |
| Network newNetwork = _networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(), |
| owner.getAccountName() + "-network", owner.getAccountName() + "-network", null, null, |
| null, null, owner, null, physicalNetwork, zone.getId(), ACLType.Account, null, null, null, null); |
| defaultNetwork = _networkDao.findById(newNetwork.getId()); |
| } else if (virtualNetworks.size() > 1) { |
| throw new InvalidParameterValueException("More than 1 default Isolated networks are found for account " + owner + "; please specify networkIds"); |
| } else { |
| defaultNetwork = _networkDao.findById(virtualNetworks.get(0).getId()); |
| } |
| } else { |
| throw new InvalidParameterValueException("Required network offering id=" + requiredOfferings.get(0).getId() + " is not in " + NetworkOffering.State.Enabled); |
| } |
| |
| networkList.add(defaultNetwork); |
| |
| } else { |
| for (Long networkId : networkIdList) { |
| NetworkVO network = _networkDao.findById(networkId); |
| if (network == null) { |
| throw new InvalidParameterValueException("Unable to find network by id " + networkIdList.get(0).longValue()); |
| } |
| if (network.getVpcId() != null) { |
| //Only ISOs, XenServer, KVM, and VmWare template types are supported for vpc networks |
| if (template.getFormat() != ImageFormat.ISO && !vpcSupportedHTypes.contains(template.getHypervisorType())) { |
| throw new InvalidParameterValueException("Can't create vm from template with hypervisor " |
| + template.getHypervisorType() + " in vpc network " + network); |
| } |
| |
| //Only XenServer, KVM, and VMware hypervisors are supported for vpc networks |
| if (!vpcSupportedHTypes.contains(hypervisor)) { |
| throw new InvalidParameterValueException("Can't create vm of hypervisor type " + hypervisor + " in vpc network"); |
| } |
| |
| } |
| |
| _networkModel.checkNetworkPermissions(owner, network); |
| |
| //don't allow to use system networks |
| NetworkOffering networkOffering = _configMgr.getNetworkOffering(network.getNetworkOfferingId()); |
| if (networkOffering.isSystemOnly()) { |
| throw new InvalidParameterValueException("Network id=" + networkId + " is system only and can't be used for vm deployment"); |
| } |
| networkList.add(network); |
| } |
| } |
| |
| return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, null, group, userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, keyboard); |
| } |
| |
| @DB @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", create = true) |
| protected UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, String hostName, String displayName, Account owner, Long diskOfferingId, |
| Long diskSize, List<NetworkVO> networkList, List<Long> securityGroupIdList, String group, String userData, String sshKeyPair, HypervisorType hypervisor, Account caller, Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, String keyboard) |
| throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, ResourceAllocationException { |
| |
| _accountMgr.checkAccess(caller, null, true, owner); |
| |
| if (owner.getState() == Account.State.disabled) { |
| throw new PermissionDeniedException("The owner of vm to deploy is disabled: " + owner); |
| } |
| |
| long accountId = owner.getId(); |
| |
| assert !(requestedIps != null && (defaultIps.getIp4Address() != null || defaultIps.getIp6Address() != null)) : "requestedIp list and defaultNetworkIp should never be specified together"; |
| |
| if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getType())) { |
| throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zone.getId()); |
| } |
| |
| if (zone.getDomainId() != null) { |
| DomainVO domain = _domainDao.findById(zone.getDomainId()); |
| if (domain == null) { |
| throw new CloudRuntimeException("Unable to find the domain " + zone.getDomainId() + " for the zone: " + zone); |
| } |
| // check that caller can operate with domain |
| _configMgr.checkZoneAccess(caller, zone); |
| // check that vm owner can create vm in the domain |
| _configMgr.checkZoneAccess(owner, zone); |
| } |
| |
| // check if account/domain is with in resource limits to create a new vm |
| boolean isIso = Storage.ImageFormat.ISO == template.getFormat(); |
| _resourceLimitMgr.checkResourceLimit(owner, ResourceType.user_vm); |
| _resourceLimitMgr.checkResourceLimit(owner, ResourceType.volume, (isIso || diskOfferingId == null ? 1 : 2)); |
| |
| //verify security group ids |
| if (securityGroupIdList != null) { |
| for (Long securityGroupId : securityGroupIdList) { |
| SecurityGroup sg = _securityGroupDao.findById(securityGroupId); |
| if (sg == null) { |
| throw new InvalidParameterValueException("Unable to find security group by id " + securityGroupId); |
| } else { |
| //verify permissions |
| _accountMgr.checkAccess(caller, null, true, owner, sg); |
| } |
| } |
| } |
| |
| // check if we have available pools for vm deployment |
| long availablePools = _storagePoolDao.countPoolsByStatus(StoragePoolStatus.Up); |
| if (availablePools < 1) { |
| throw new StorageUnavailableException("There are no available pools in the UP state for vm deployment", -1); |
| } |
| |
| ServiceOfferingVO offering = _serviceOfferingDao.findById(serviceOffering.getId()); |
| |
| if (template.getTemplateType().equals(TemplateType.SYSTEM)) { |
| throw new InvalidParameterValueException("Unable to use system template " + template.getId() + " to deploy a user vm"); |
| } |
| List<VMTemplateZoneVO> listZoneTemplate = _templateZoneDao.listByZoneTemplate(zone.getId(), template.getId()); |
| if (listZoneTemplate == null || listZoneTemplate.isEmpty()) { |
| throw new InvalidParameterValueException("The template " + template.getId() + " is not available for use"); |
| } |
| |
| if (isIso && !template.isBootable()) { |
| throw new InvalidParameterValueException("Installing from ISO requires an ISO that is bootable: " + template.getId()); |
| } |
| |
| // Check templates permissions |
| if (!template.isPublicTemplate()) { |
| Account templateOwner = _accountMgr.getAccount(template.getAccountId()); |
| _accountMgr.checkAccess(owner, null, true, templateOwner); |
| } |
| |
| // If the template represents an ISO, a disk offering must be passed in, and will be used to create the root disk |
| // Else, a disk offering is optional, and if present will be used to create the data disk |
| Pair<DiskOfferingVO, Long> rootDiskOffering = new Pair<DiskOfferingVO, Long>(null, null); |
| List<Pair<DiskOfferingVO, Long>> dataDiskOfferings = new ArrayList<Pair<DiskOfferingVO, Long>>(); |
| |
| if (isIso) { |
| if (diskOfferingId == null) { |
| throw new InvalidParameterValueException("Installing from ISO requires a disk offering to be specified for the root disk."); |
| } |
| DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); |
| if (diskOffering == null) { |
| throw new InvalidParameterValueException("Unable to find disk offering " + diskOfferingId); |
| } |
| Long size = null; |
| if (diskOffering.getDiskSize() == 0) { |
| size = diskSize; |
| if (size == null) { |
| throw new InvalidParameterValueException("Disk offering " + diskOffering + " requires size parameter."); |
| } |
| } |
| rootDiskOffering.first(diskOffering); |
| rootDiskOffering.second(size); |
| } else { |
| rootDiskOffering.first(offering); |
| if (diskOfferingId != null) { |
| DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); |
| if (diskOffering == null) { |
| throw new InvalidParameterValueException("Unable to find disk offering " + diskOfferingId); |
| } |
| Long size = null; |
| if (diskOffering.getDiskSize() == 0) { |
| size = diskSize; |
| if (size == null) { |
| throw new InvalidParameterValueException("Disk offering " + diskOffering + " requires size parameter."); |
| } |
| } |
| dataDiskOfferings.add(new Pair<DiskOfferingVO, Long>(diskOffering, size)); |
| } |
| } |
| |
| //check if the user data is correct |
| validateUserData(userData); |
| |
| // Find an SSH public key corresponding to the key pair name, if one is given |
| String sshPublicKey = null; |
| if (sshKeyPair != null && !sshKeyPair.equals("")) { |
| SSHKeyPair pair = _sshKeyPairDao.findByName(owner.getAccountId(), owner.getDomainId(), sshKeyPair); |
| if (pair == null) { |
| throw new InvalidParameterValueException("A key pair with name '" + sshKeyPair + "' was not found."); |
| } |
| |
| sshPublicKey = pair.getPublicKey(); |
| } |
| |
| List<Pair<NetworkVO, NicProfile>> networks = new ArrayList<Pair<NetworkVO, NicProfile>>(); |
| short defaultNetworkNumber = 0; |
| boolean securityGroupEnabled = false; |
| boolean vpcNetwork = false; |
| for (NetworkVO network : networkList) { |
| if (network.getDataCenterId() != zone.getId()) { |
| throw new InvalidParameterValueException("Network id=" + network.getId() + " doesn't belong to zone " + zone.getId()); |
| } |
| |
| IpAddresses requestedIpPair = null; |
| if (requestedIps != null && !requestedIps.isEmpty()) { |
| requestedIpPair = requestedIps.get(network.getId()); |
| } |
| |
| if (requestedIpPair == null) { |
| requestedIpPair = new IpAddresses(null, null); |
| } else { |
| checkRequestedIpAddresses(requestedIpPair.getIp4Address(), requestedIpPair.getIp6Address()); |
| } |
| |
| NicProfile profile = new NicProfile(requestedIpPair.getIp4Address(), requestedIpPair.getIp6Address()); |
| |
| if (defaultNetworkNumber == 0) { |
| defaultNetworkNumber++; |
| // if user requested specific ip for default network, add it |
| if (defaultIps.getIp4Address() != null || defaultIps.getIp6Address() != null) { |
| checkRequestedIpAddresses(defaultIps.getIp4Address(), defaultIps.getIp6Address()); |
| profile = new NicProfile(defaultIps.getIp4Address(), defaultIps.getIp6Address()); |
| } |
| |
| profile.setDefaultNic(true); |
| } |
| |
| networks.add(new Pair<NetworkVO, NicProfile>(network, profile)); |
| |
| if (_networkModel.isSecurityGroupSupportedInNetwork(network)) { |
| securityGroupEnabled = true; |
| } |
| |
| //vm can't be a part of more than 1 VPC network |
| if (network.getVpcId() != null) { |
| if (vpcNetwork) { |
| throw new InvalidParameterValueException("Vm can't be a part of more than 1 VPC network"); |
| } |
| vpcNetwork = true; |
| } |
| } |
| |
| if (securityGroupIdList != null && !securityGroupIdList.isEmpty() && !securityGroupEnabled) { |
| throw new InvalidParameterValueException("Unable to deploy vm with security groups as SecurityGroup service is not enabled for the vm's network"); |
| } |
| |
| // Verify network information - network default network has to be set; and vm can't have more than one default network |
| // This is a part of business logic because default network is required by Agent Manager in order to configure default |
| // gateway for the vm |
| if (defaultNetworkNumber == 0) { |
| throw new InvalidParameterValueException("At least 1 default network has to be specified for the vm"); |
| } else if (defaultNetworkNumber > 1) { |
| throw new InvalidParameterValueException("Only 1 default network per vm is supported"); |
| } |
| |
| long id = _vmDao.getNextInSequence(Long.class, "id"); |
| |
| String instanceName = VirtualMachineName.getVmName(id, owner.getId(), _instance); |
| |
| String uuidName = UUID.randomUUID().toString(); |
| |
| //verify hostname information |
| if (hostName == null) { |
| hostName = uuidName; |
| } else { |
| //1) check is hostName is RFC complient |
| if (!NetUtils.verifyDomainNameLabel(hostName, true)) { |
| throw new InvalidParameterValueException("Invalid name. Vm name can contain ASCII letters 'a' through 'z', the digits '0' through '9', " |
| + "and the hyphen ('-'), must be between 1 and 63 characters long, and can't start or end with \"-\" and can't start with digit"); |
| } |
| //2) hostName has to be unique in the network domain |
| Map<String, List<Long>> ntwkDomains = new HashMap<String, List<Long>>(); |
| for (NetworkVO network : networkList) { |
| String ntwkDomain = network.getNetworkDomain(); |
| if (!ntwkDomains.containsKey(ntwkDomain)) { |
| List<Long> ntwkIds = new ArrayList<Long>(); |
| ntwkIds.add(network.getId()); |
| ntwkDomains.put(ntwkDomain, ntwkIds); |
| } else { |
| List<Long> ntwkIds = ntwkDomains.get(ntwkDomain); |
| ntwkIds.add(network.getId()); |
| ntwkDomains.put(ntwkDomain, ntwkIds); |
| } |
| } |
| |
| for (String ntwkDomain : ntwkDomains.keySet()) { |
| for (Long ntwkId : ntwkDomains.get(ntwkDomain)) { |
| //* get all vms hostNames in the network |
| List<String> hostNames = _vmInstanceDao.listDistinctHostNames(ntwkId); |
| //* verify that there are no duplicates |
| if (hostNames.contains(hostName)) { |
| throw new InvalidParameterValueException("The vm with hostName " + hostName |
| + " already exists in the network domain: " + ntwkDomain + "; network=" |
| + _networkModel.getNetwork(ntwkId)); |
| } |
| } |
| } |
| } |
| |
| HypervisorType hypervisorType = null; |
| if (template == null || template.getHypervisorType() == null || template.getHypervisorType() == HypervisorType.None) { |
| hypervisorType = hypervisor; |
| } else { |
| hypervisorType = template.getHypervisorType(); |
| } |
| Transaction txn = Transaction.currentTxn(); |
| txn.start(); |
| UserVmVO vm = new UserVmVO(id, instanceName, displayName, template.getId(), hypervisorType, template.getGuestOSId(), offering.getOfferHA(), offering.getLimitCpuUse(), owner.getDomainId(), owner.getId(), |
| offering.getId(), userData, hostName); |
| vm.setUuid(uuidName); |
| |
| if (sshPublicKey != null) { |
| vm.setDetail("SSH.PublicKey", sshPublicKey); |
| } |
| |
| if(keyboard != null && !keyboard.isEmpty()) |
| vm.setDetail(VmDetailConstants.KEYBOARD, keyboard); |
| |
| if (isIso) { |
| vm.setIsoId(template.getId()); |
| } |
| |
| s_logger.debug("Allocating in the DB for vm"); |
| DataCenterDeployment plan = new DataCenterDeployment(zone.getId()); |
| |
| if (_itMgr.allocate(vm, _templateDao.findById(template.getId()), offering, rootDiskOffering, dataDiskOfferings, networks, null, plan, hypervisorType, owner) == null) { |
| return null; |
| } |
| |
| _vmDao.saveDetails(vm); |
| |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Successfully allocated DB entry for " + vm); |
| } |
| UserContext.current().setEventDetails("Vm Id: " + vm.getId()); |
| |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_CREATE, accountId, zone.getId(), vm.getId(), |
| vm.getHostName(), offering.getId(), template.getId(), hypervisorType.toString(), |
| VirtualMachine.class.getName(), vm.getUuid()); |
| |
| _resourceLimitMgr.incrementResourceCount(accountId, ResourceType.user_vm); |
| txn.commit(); |
| // Assign instance to the group |
| try { |
| if (group != null) { |
| boolean addToGroup = addInstanceToGroup(Long.valueOf(id), group); |
| if (!addToGroup) { |
| throw new CloudRuntimeException("Unable to assign Vm to the group " + group); |
| } |
| } |
| } catch (Exception ex) { |
| throw new CloudRuntimeException("Unable to assign Vm to the group " + group); |
| } |
| |
| _securityGroupMgr.addInstanceToGroups(vm.getId(), securityGroupIdList); |
| |
| return vm; |
| } |
| |
| private void checkRequestedIpAddresses(String ip4, String ip6) throws InvalidParameterValueException { |
| if (ip4 != null) { |
| if (!NetUtils.isValidIp(ip4)) { |
| throw new InvalidParameterValueException("Invalid specified IPv4 address " + ip4); |
| } |
| } |
| if (ip6 != null) { |
| if (!NetUtils.isValidIpv6(ip6)) { |
| throw new InvalidParameterValueException("Invalid specified IPv6 address " + ip6); |
| } |
| } |
| } |
| |
| private void validateUserData(String userData) { |
| byte[] decodedUserData = null; |
| if (userData != null) { |
| if (!Base64.isBase64(userData)) { |
| throw new InvalidParameterValueException("User data is not base64 encoded"); |
| } |
| if (userData.length() >= 2 * MAX_USER_DATA_LENGTH_BYTES) { |
| throw new InvalidParameterValueException("User data is too long"); |
| } |
| decodedUserData = Base64.decodeBase64(userData.getBytes()); |
| if (decodedUserData.length > MAX_USER_DATA_LENGTH_BYTES) { |
| throw new InvalidParameterValueException("User data is too long"); |
| } |
| if (decodedUserData.length < 1) { |
| throw new InvalidParameterValueException("User data is too short"); |
| } |
| } |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "starting Vm", async = true) |
| public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException { |
| return startVirtualMachine(cmd, null); |
| } |
| |
| protected UserVm startVirtualMachine(DeployVMCmd cmd, Map<VirtualMachineProfile.Param, Object> additonalParams) throws ResourceUnavailableException, InsufficientCapacityException, |
| ConcurrentOperationException { |
| |
| long vmId = cmd.getEntityId(); |
| Long hostId = cmd.getHostId(); |
| UserVmVO vm = _vmDao.findById(vmId); |
| |
| Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> vmParamPair = null; |
| try { |
| vmParamPair = startVirtualMachine(vmId, hostId, additonalParams); |
| vm = vmParamPair.first(); |
| } finally { |
| updateVmStateForFailedVmCreation(vm.getId(), hostId); |
| } |
| |
| // Check that the password was passed in and is valid |
| VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); |
| if (template.getEnablePassword()) { |
| // this value is not being sent to the backend; need only for api display purposes |
| vm.setPassword((String)vmParamPair.second().get(VirtualMachineProfile.Param.VmPassword)); |
| } |
| |
| return vm; |
| } |
| |
| @Override |
| public boolean finalizeVirtualMachineProfile(VirtualMachineProfile<UserVmVO> profile, DeployDestination dest, ReservationContext context) { |
| UserVmVO vm = profile.getVirtualMachine(); |
| Map<String, String> details = _vmDetailsDao.findDetails(vm.getId()); |
| vm.setDetails(details); |
| |
| if (vm.getIsoId() != null) { |
| String isoPath = null; |
| |
| VirtualMachineTemplate template = _templateDao.findById(vm.getIsoId()); |
| if (template == null || template.getFormat() != ImageFormat.ISO) { |
| throw new CloudRuntimeException("Can not find ISO in vm_template table for id " + vm.getIsoId()); |
| } |
| |
| Pair<String, String> isoPathPair = _storageMgr.getAbsoluteIsoPath(template.getId(), vm.getDataCenterIdToDeployIn()); |
| |
| if (template.getTemplateType() == TemplateType.PERHOST) { |
| isoPath = template.getName(); |
| } else { |
| if (isoPathPair == null) { |
| s_logger.warn("Couldn't get absolute iso path"); |
| return false; |
| } else { |
| isoPath = isoPathPair.first(); |
| } |
| } |
| |
| if (template.isBootable()) { |
| profile.setBootLoaderType(BootloaderType.CD); |
| } |
| GuestOSVO guestOS = _guestOSDao.findById(template.getGuestOSId()); |
| String displayName = null; |
| if (guestOS != null) { |
| displayName = guestOS.getDisplayName(); |
| } |
| VolumeTO iso = new VolumeTO(profile.getId(), Volume.Type.ISO, StoragePoolType.ISO, null, template.getName(), null, isoPath, 0, null, displayName); |
| |
| iso.setDeviceId(3); |
| profile.addDisk(iso); |
| } else { |
| VirtualMachineTemplate template = profile.getTemplate(); |
| /* create a iso placeholder */ |
| VolumeTO iso = new VolumeTO(profile.getId(), Volume.Type.ISO, StoragePoolType.ISO, null, template.getName(), null, null, 0, null); |
| iso.setDeviceId(3); |
| profile.addDisk(iso); |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public boolean finalizeDeployment(Commands cmds, VirtualMachineProfile<UserVmVO> profile, DeployDestination dest, ReservationContext context) { |
| UserVmVO userVm = profile.getVirtualMachine(); |
| List<NicVO> nics = _nicDao.listByVmId(userVm.getId()); |
| for (NicVO nic : nics) { |
| NetworkVO network = _networkDao.findById(nic.getNetworkId()); |
| if (network.getTrafficType() == TrafficType.Guest || network.getTrafficType() == TrafficType.Public) { |
| userVm.setPrivateIpAddress(nic.getIp4Address()); |
| userVm.setPrivateMacAddress(nic.getMacAddress()); |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean finalizeCommandsOnStart(Commands cmds, VirtualMachineProfile<UserVmVO> profile) { |
| return true; |
| } |
| |
| @Override |
| public boolean finalizeStart(VirtualMachineProfile<UserVmVO> profile, long hostId, Commands cmds, ReservationContext context){ |
| UserVmVO vm = profile.getVirtualMachine(); |
| |
| Answer[] answersToCmds = cmds.getAnswers(); |
| if(answersToCmds == null){ |
| if(s_logger.isDebugEnabled()){ |
| s_logger.debug("Returning from finalizeStart() since there are no answers to read"); |
| } |
| return true; |
| } |
| Answer startAnswer = cmds.getAnswer(StartAnswer.class); |
| String returnedIp = null; |
| String originalIp = null; |
| if (startAnswer != null) { |
| StartAnswer startAns = (StartAnswer) startAnswer; |
| VirtualMachineTO vmTO = startAns.getVirtualMachine(); |
| for (NicTO nicTO: vmTO.getNics()) { |
| if (nicTO.getType() == TrafficType.Guest) { |
| returnedIp = nicTO.getIp(); |
| } |
| } |
| } |
| |
| List<NicVO> nics = _nicDao.listByVmId(vm.getId()); |
| NicVO guestNic = null; |
| NetworkVO guestNetwork = null; |
| for (NicVO nic : nics) { |
| NetworkVO network = _networkDao.findById(nic.getNetworkId()); |
| long isDefault = (nic.isDefaultNic()) ? 1 : 0; |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_ASSIGN, vm.getAccountId(), |
| vm.getDataCenterIdToDeployIn(), vm.getId(), vm.getHostName(), network.getNetworkOfferingId(), |
| null, isDefault, VirtualMachine.class.getName(), vm.getUuid()); |
| if (network.getTrafficType() == TrafficType.Guest) { |
| originalIp = nic.getIp4Address(); |
| guestNic = nic; |
| guestNetwork = network; |
| } |
| } |
| boolean ipChanged = false; |
| if (originalIp != null && !originalIp.equalsIgnoreCase(returnedIp)) { |
| if (returnedIp != null && guestNic != null) { |
| guestNic.setIp4Address(returnedIp); |
| ipChanged = true; |
| } |
| } |
| if (returnedIp != null && !returnedIp.equalsIgnoreCase(originalIp)) { |
| if (guestNic != null) { |
| guestNic.setIp4Address(returnedIp); |
| ipChanged = true; |
| } |
| } |
| if (ipChanged) { |
| DataCenterVO dc = _dcDao.findById(vm.getDataCenterIdToDeployIn()); |
| UserVmVO userVm = profile.getVirtualMachine(); |
| //dc.getDhcpProvider().equalsIgnoreCase(Provider.ExternalDhcpServer.getName()) |
| if (_ntwkSrvcDao.canProviderSupportServiceInNetwork(guestNetwork.getId(), Service.Dhcp, Provider.ExternalDhcpServer)){ |
| _nicDao.update(guestNic.getId(), guestNic); |
| userVm.setPrivateIpAddress(guestNic.getIp4Address()); |
| _vmDao.update(userVm.getId(), userVm); |
| |
| s_logger.info("Detected that ip changed in the answer, updated nic in the db with new ip " + returnedIp); |
| } |
| } |
| |
| //get system ip and create static nat rule for the vm |
| try { |
| _rulesMgr.getSystemIpAndEnableStaticNatForVm(profile.getVirtualMachine(), false); |
| } catch (Exception ex) { |
| s_logger.warn("Failed to get system ip and enable static nat for the vm " + profile.getVirtualMachine() + " due to exception ", ex); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public void finalizeExpunge(UserVmVO vm) { |
| } |
| |
| @Override |
| public UserVmVO persist(UserVmVO vm) { |
| return _vmDao.persist(vm); |
| } |
| |
| @Override |
| public UserVmVO findById(long id) { |
| return _vmDao.findById(id); |
| } |
| |
| @Override |
| public UserVmVO findByName(String name) { |
| if (!VirtualMachineName.isValidVmName(name)) { |
| return null; |
| } |
| return findById(VirtualMachineName.getVmId(name)); |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_STOP, eventDescription = "stopping Vm", async = true) |
| public UserVm stopVirtualMachine(long vmId, boolean forced) throws ConcurrentOperationException { |
| // Input validation |
| Account caller = UserContext.current().getCaller(); |
| Long userId = UserContext.current().getCallerUserId(); |
| |
| // if account is removed, return error |
| if (caller != null && caller.getRemoved() != null) { |
| throw new PermissionDeniedException("The account " + caller.getId() + " is removed"); |
| } |
| |
| UserVmVO vm = _vmDao.findById(vmId); |
| if (vm == null) { |
| throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); |
| } |
| |
| UserVO user = _userDao.findById(userId); |
| |
| try { |
| _itMgr.advanceStop(vm, forced, user, caller); |
| } catch (ResourceUnavailableException e) { |
| throw new CloudRuntimeException("Unable to contact the agent to stop the virtual machine " + vm, e); |
| } catch (OperationTimedoutException e) { |
| throw new CloudRuntimeException("Unable to contact the agent to stop the virtual machine " + vm, e); |
| } |
| |
| return _vmDao.findById(vmId); |
| } |
| |
| @Override |
| public void finalizeStop(VirtualMachineProfile<UserVmVO> profile, StopAnswer answer) { |
| //release elastic IP here |
| IPAddressVO ip = _ipAddressDao.findByAssociatedVmId(profile.getId()); |
| if (ip != null && ip.getSystem()) { |
| UserContext ctx = UserContext.current(); |
| try { |
| _rulesMgr.disableStaticNat(ip.getId(), ctx.getCaller(), ctx.getCallerUserId(), true); |
| } catch (Exception ex) { |
| s_logger.warn("Failed to disable static nat and release system ip " + ip + " as a part of vm " + profile.getVirtualMachine() + " stop due to exception ", ex); |
| } |
| } |
| } |
| |
| public String generateRandomPassword() { |
| return PasswordGenerator.generateRandomPassword(6); |
| } |
| |
| @Override |
| public Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> startVirtualMachine(long vmId, Long hostId, Map<VirtualMachineProfile.Param, Object> additionalParams) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { |
| // Input validation |
| Account callerAccount = UserContext.current().getCaller(); |
| UserVO callerUser = _userDao.findById(UserContext.current().getCallerUserId()); |
| |
| // if account is removed, return error |
| if (callerAccount != null && callerAccount.getRemoved() != null) { |
| throw new InvalidParameterValueException("The account " + callerAccount.getId() + " is removed"); |
| } |
| |
| UserVmVO vm = _vmDao.findById(vmId); |
| if (vm == null) { |
| throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId); |
| } |
| |
| _accountMgr.checkAccess(callerAccount, null, true, vm); |
| |
| Account owner = _accountDao.findById(vm.getAccountId()); |
| |
| if (owner == null) { |
| throw new InvalidParameterValueException("The owner of " + vm + " does not exist: " + vm.getAccountId()); |
| } |
| |
| if (owner.getState() == Account.State.disabled) { |
| throw new PermissionDeniedException("The owner of " + vm + " is disabled: " + vm.getAccountId()); |
| } |
| |
| Host destinationHost = null; |
| if(hostId != null){ |
| Account account = UserContext.current().getCaller(); |
| if(!_accountService.isRootAdmin(account.getType())){ |
| throw new PermissionDeniedException("Parameter hostid can only be specified by a Root Admin, permission denied"); |
| } |
| destinationHost = _hostDao.findById(hostId); |
| if (destinationHost == null) { |
| throw new InvalidParameterValueException("Unable to find the host to deploy the VM, host id=" + hostId); |
| } |
| } |
| |
| //check if vm is security group enabled |
| if (_securityGroupMgr.isVmSecurityGroupEnabled(vmId) && _securityGroupMgr.getSecurityGroupsForVm(vmId).isEmpty() && !_securityGroupMgr.isVmMappedToDefaultSecurityGroup(vmId) && _networkModel.canAddDefaultSecurityGroup()) { |
| //if vm is not mapped to security group, create a mapping |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Vm " + vm + " is security group enabled, but not mapped to default security group; creating the mapping automatically"); |
| } |
| |
| SecurityGroup defaultSecurityGroup = _securityGroupMgr.getDefaultSecurityGroup(vm.getAccountId()); |
| if (defaultSecurityGroup != null) { |
| List<Long> groupList = new ArrayList<Long>(); |
| groupList.add(defaultSecurityGroup.getId()); |
| _securityGroupMgr.addInstanceToGroups(vmId, groupList); |
| } |
| } |
| |
| DataCenterDeployment plan = null; |
| if (destinationHost != null) { |
| s_logger.debug("Destination Host to deploy the VM is specified, specifying a deployment plan to deploy the VM"); |
| plan = new DataCenterDeployment(vm.getDataCenterIdToDeployIn(), destinationHost.getPodId(), destinationHost.getClusterId(), destinationHost.getId(), null, null); |
| } |
| |
| //Set parameters |
| Map<VirtualMachineProfile.Param, Object> params = null; |
| VMTemplateVO template = null; |
| if (vm.isUpdateParameters()) { |
| _vmDao.loadDetails(vm); |
| // Check that the password was passed in and is valid |
| template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); |
| |
| String password = "saved_password"; |
| if (template.getEnablePassword()) { |
| password = generateRandomPassword(); |
| } |
| |
| if (!validPassword(password)) { |
| throw new InvalidParameterValueException("A valid password for this virtual machine was not provided."); |
| } |
| |
| // Check if an SSH key pair was selected for the instance and if so use it to encrypt & save the vm password |
| String sshPublicKey = vm.getDetail("SSH.PublicKey"); |
| if (sshPublicKey != null && !sshPublicKey.equals("") && password != null && !password.equals("saved_password")) { |
| String encryptedPasswd = RSAHelper.encryptWithSSHPublicKey(sshPublicKey, password); |
| if (encryptedPasswd == null) { |
| throw new CloudRuntimeException("Error encrypting password"); |
| } |
| |
| vm.setDetail("Encrypted.Password", encryptedPasswd); |
| _vmDao.saveDetails(vm); |
| } |
| |
| params = new HashMap<VirtualMachineProfile.Param, Object>(); |
| if (additionalParams != null) { |
| params.putAll(additionalParams); |
| } |
| params.put(VirtualMachineProfile.Param.VmPassword, password); |
| } |
| |
| vm = _itMgr.start(vm, params, callerUser, callerAccount, plan); |
| |
| Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>> vmParamPair = new Pair(vm, params); |
| if (vm != null && vm.isUpdateParameters()) { |
| // this value is not being sent to the backend; need only for api display purposes |
| if (template.getEnablePassword()) { |
| vm.setPassword((String)vmParamPair.second().get(VirtualMachineProfile.Param.VmPassword)); |
| vm.setUpdateParameters(false); |
| _vmDao.update(vm.getId(), vm); |
| } |
| } |
| |
| return vmParamPair; |
| } |
| |
| @Override |
| public UserVm destroyVm(long vmId) throws ResourceUnavailableException, ConcurrentOperationException { |
| Account caller = UserContext.current().getCaller(); |
| Long userId = UserContext.current().getCallerUserId(); |
| |
| // Verify input parameters |
| UserVmVO vm = _vmDao.findById(vmId); |
| if (vm == null || vm.getRemoved() != null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find a virtual machine with specified vmId"); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| throw ex; |
| } |
| |
| if (vm.getState() == State.Destroyed || vm.getState() == State.Expunging) { |
| s_logger.trace("Vm id=" + vmId + " is already destroyed"); |
| return vm; |
| } |
| |
| _accountMgr.checkAccess(caller, null, true, vm); |
| User userCaller = _userDao.findById(userId); |
| |
| boolean status; |
| State vmState = vm.getState(); |
| |
| try { |
| status = _itMgr.destroy(vm, userCaller, caller); |
| } catch (OperationTimedoutException e) { |
| CloudRuntimeException ex = new CloudRuntimeException("Unable to destroy with specified vmId", e); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| throw ex; |
| } |
| |
| if (status) { |
| // Mark the account's volumes as destroyed |
| List<VolumeVO> volumes = _volsDao.findByInstance(vmId); |
| for (VolumeVO volume : volumes) { |
| if (volume.getVolumeType().equals(Volume.Type.ROOT)) { |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), |
| volume.getDataCenterId(), volume.getId(), volume.getName(), Volume.class.getName(), |
| volume.getUuid()); |
| } |
| } |
| |
| if (vmState != State.Error) { |
| _resourceLimitMgr.decrementResourceCount(vm.getAccountId(), ResourceType.user_vm); |
| } |
| |
| return _vmDao.findById(vmId); |
| } else { |
| CloudRuntimeException ex = new CloudRuntimeException("Failed to destroy vm with specified vmId"); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| throw ex; |
| } |
| } |
| |
| |
| |
| @Override |
| public Pair<List<UserVmJoinVO>, Integer> searchForUserVMs(Criteria c, Account caller, Long domainId, boolean isRecursive, |
| List<Long> permittedAccounts, boolean listAll, ListProjectResourcesCriteria listProjectResourcesCriteria, Map<String, String> tags) { |
| Filter searchFilter = new Filter(UserVmJoinVO.class, c.getOrderBy(), c.getAscending(), c.getOffset(), c.getLimit()); |
| |
| //first search distinct vm id by using query criteria and pagination |
| SearchBuilder<UserVmJoinVO> sb = _vmJoinDao.createSearchBuilder(); |
| sb.select(null, Func.DISTINCT, sb.entity().getId()); // select distinct ids |
| _accountMgr.buildACLViewSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); |
| |
| Object id = c.getCriteria(Criteria.ID); |
| Object name = c.getCriteria(Criteria.NAME); |
| Object state = c.getCriteria(Criteria.STATE); |
| Object notState = c.getCriteria(Criteria.NOTSTATE); |
| Object zone = c.getCriteria(Criteria.DATACENTERID); |
| Object pod = c.getCriteria(Criteria.PODID); |
| Object hostId = c.getCriteria(Criteria.HOSTID); |
| Object hostName = c.getCriteria(Criteria.HOSTNAME); |
| Object keyword = c.getCriteria(Criteria.KEYWORD); |
| Object isAdmin = c.getCriteria(Criteria.ISADMIN); |
| assert c.getCriteria(Criteria.IPADDRESS) == null : "We don't support search by ip address on VM any more. If you see this assert, it means we have to find a different way to search by the nic table."; |
| Object groupId = c.getCriteria(Criteria.GROUPID); |
| Object networkId = c.getCriteria(Criteria.NETWORKID); |
| Object hypervisor = c.getCriteria(Criteria.HYPERVISOR); |
| Object storageId = c.getCriteria(Criteria.STORAGE_ID); |
| Object templateId = c.getCriteria(Criteria.TEMPLATE_ID); |
| Object isoId = c.getCriteria(Criteria.ISO_ID); |
| Object vpcId = c.getCriteria(Criteria.VPC_ID); |
| |
| sb.and("displayName", sb.entity().getDisplayName(), SearchCriteria.Op.LIKE); |
| sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); |
| sb.and("name", sb.entity().getHostName(), SearchCriteria.Op.LIKE); |
| sb.and("stateEQ", sb.entity().getState(), SearchCriteria.Op.EQ); |
| sb.and("stateNEQ", sb.entity().getState(), SearchCriteria.Op.NEQ); |
| sb.and("stateNIN", sb.entity().getState(), SearchCriteria.Op.NIN); |
| sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); |
| sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ); |
| sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ); |
| sb.and("hostIdEQ", sb.entity().getHostId(), SearchCriteria.Op.EQ); |
| sb.and("hostName", sb.entity().getHostName(), SearchCriteria.Op.LIKE); |
| sb.and("templateId", sb.entity().getTemplateId(), SearchCriteria.Op.EQ); |
| sb.and("isoId", sb.entity().getIsoId(), SearchCriteria.Op.EQ); |
| sb.and("instanceGroupId", sb.entity().getInstanceGroupId(), SearchCriteria.Op.EQ); |
| |
| if (groupId != null && (Long) groupId != -1) { |
| sb.and("instanceGroupId", sb.entity().getInstanceGroupId(), SearchCriteria.Op.EQ); |
| } |
| |
| if (tags != null && !tags.isEmpty()) { |
| for (int count=0; count < tags.size(); count++) { |
| sb.or().op("key" + String.valueOf(count), sb.entity().getTagKey(), SearchCriteria.Op.EQ); |
| sb.and("value" + String.valueOf(count), sb.entity().getTagValue(), SearchCriteria.Op.EQ); |
| sb.cp(); |
| } |
| } |
| |
| if (networkId != null) { |
| sb.and("networkId", sb.entity().getNetworkId(), SearchCriteria.Op.EQ); |
| } |
| |
| if(vpcId != null && networkId == null){ |
| sb.and("vpcId", sb.entity().getVpcId(), SearchCriteria.Op.EQ); |
| } |
| |
| if (storageId != null) { |
| sb.and("poolId", sb.entity().getPoolId(), SearchCriteria.Op.EQ); |
| } |
| |
| // populate the search criteria with the values passed in |
| SearchCriteria<UserVmJoinVO> sc = sb.create(); |
| |
| // building ACL condition |
| _accountMgr.buildACLViewSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); |
| |
| if (tags != null && !tags.isEmpty()) { |
| int count = 0; |
| for (String key : tags.keySet()) { |
| sc.setParameters("key" + String.valueOf(count), key); |
| sc.setParameters("value" + String.valueOf(count), tags.get(key)); |
| count++; |
| } |
| } |
| |
| if (groupId != null && (Long)groupId != -1) { |
| sc.setParameters("instanceGroupId", groupId); |
| } |
| |
| if (keyword != null) { |
| SearchCriteria<UserVmJoinVO> ssc = _vmJoinDao.createSearchCriteria(); |
| ssc.addOr("displayName", SearchCriteria.Op.LIKE, "%" + keyword + "%"); |
| ssc.addOr("hostName", SearchCriteria.Op.LIKE, "%" + keyword + "%"); |
| ssc.addOr("instanceName", SearchCriteria.Op.LIKE, "%" + keyword + "%"); |
| ssc.addOr("state", SearchCriteria.Op.EQ, keyword); |
| |
| sc.addAnd("displayName", SearchCriteria.Op.SC, ssc); |
| } |
| |
| if (id != null) { |
| sc.setParameters("id", id); |
| } |
| |
| if (templateId != null) { |
| sc.setParameters("templateId", templateId); |
| } |
| |
| if (isoId != null) { |
| sc.setParameters("isoId", isoId); |
| } |
| |
| if (networkId != null) { |
| sc.setParameters("networkId", networkId); |
| } |
| |
| if(vpcId != null && networkId == null){ |
| sc.setParameters("vpcId", vpcId); |
| } |
| |
| if (name != null) { |
| sc.setParameters("name", "%" + name + "%"); |
| } |
| |
| if (state != null) { |
| if (notState != null && (Boolean) notState == true) { |
| sc.setParameters("stateNEQ", state); |
| } else { |
| sc.setParameters("stateEQ", state); |
| } |
| } |
| |
| if (hypervisor != null) { |
| sc.setParameters("hypervisorType", hypervisor); |
| } |
| |
| // Don't show Destroyed and Expunging vms to the end user |
| if ((isAdmin != null) && ((Boolean) isAdmin != true)) { |
| sc.setParameters("stateNIN", "Destroyed", "Expunging"); |
| } |
| |
| if (zone != null) { |
| sc.setParameters("dataCenterId", zone); |
| } |
| if (pod != null) { |
| sc.setParameters("podId", pod); |
| |
| if (state == null) { |
| sc.setParameters("stateNEQ", "Destroyed"); |
| } |
| } |
| |
| if (hostId != null) { |
| sc.setParameters("hostIdEQ", hostId); |
| } else { |
| if (hostName != null) { |
| sc.setParameters("hostName", hostName); |
| } |
| } |
| |
| if (storageId != null) { |
| sc.setParameters("poolId", storageId); |
| } |
| |
| // search vm details by ids |
| Pair<List<UserVmJoinVO>, Integer> uniqueVmPair = _vmJoinDao.searchAndCount(sc, searchFilter); |
| Integer count = uniqueVmPair.second(); |
| if ( count.intValue() == 0 ){ |
| // handle empty result cases |
| return uniqueVmPair; |
| } |
| List<UserVmJoinVO> uniqueVms = uniqueVmPair.first(); |
| Long[] vmIds = new Long[uniqueVms.size()]; |
| int i = 0; |
| for (UserVmJoinVO v : uniqueVms ){ |
| vmIds[i++] = v.getId(); |
| } |
| List<UserVmJoinVO> vms = _vmJoinDao.searchByIds(vmIds); |
| return new Pair<List<UserVmJoinVO>, Integer>(vms, count); |
| } |
| |
| @Override |
| public HypervisorType getHypervisorTypeOfUserVM(long vmId) { |
| UserVmVO userVm = _vmDao.findById(vmId); |
| if (userVm == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("unable to find a virtual machine with specified id"); |
| ex.addProxyObject(userVm, vmId, "vmId"); |
| throw ex; |
| } |
| |
| return userVm.getHypervisorType(); |
| } |
| |
| @Override |
| public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, |
| ResourceAllocationException { |
| // TODO Auto-generated method stub |
| return null; |
| } |
| |
| @Override |
| public UserVm getUserVm(long vmId) { |
| return _vmDao.findById(vmId); |
| } |
| |
| @Override |
| public VirtualMachine vmStorageMigration(Long vmId, StoragePool destPool) { |
| // 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!"); |
| } |
| |
| VMInstanceVO vm = _vmInstanceDao.findById(vmId); |
| if (vm == null) { |
| throw new InvalidParameterValueException("Unable to find the VM by id=" + vmId); |
| } |
| |
| if (vm.getState() != State.Stopped) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("VM is not Stopped, unable to migrate the vm having the specified id"); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| throw ex; |
| } |
| |
| if (vm.getType() != VirtualMachine.Type.User) { |
| throw new InvalidParameterValueException("can only do storage migration on user vm"); |
| } |
| |
| List<VolumeVO> vols = _volsDao.findByInstance(vm.getId()); |
| if (vols.size() > 1) { |
| throw new InvalidParameterValueException("Data disks attached to the vm, can not migrate. Need to dettach data disks at first"); |
| } |
| |
| HypervisorType destHypervisorType = _clusterDao.findById(destPool.getClusterId()).getHypervisorType(); |
| if (vm.getHypervisorType() != destHypervisorType) { |
| throw new InvalidParameterValueException("hypervisor is not compatible: dest: " + destHypervisorType.toString() + ", vm: " + vm.getHypervisorType().toString()); |
| } |
| VMInstanceVO migratedVm = _itMgr.storageMigration(vm, destPool); |
| return migratedVm; |
| |
| } |
| |
| private boolean isVMUsingLocalStorage(VMInstanceVO vm) |
| { |
| boolean usesLocalStorage = false; |
| ServiceOfferingVO svcOffering = _serviceOfferingDao.findById(vm.getServiceOfferingId()); |
| if (svcOffering.getUseLocalStorage()) { |
| usesLocalStorage = true; |
| } else { |
| List<VolumeVO> volumes = _volsDao.findByInstanceAndType(vm.getId(), Volume.Type.DATADISK); |
| for (VolumeVO vol : volumes) { |
| DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId()); |
| if (diskOffering.getUseLocalStorage()) { |
| usesLocalStorage = true; |
| break; |
| } |
| } |
| } |
| return usesLocalStorage; |
| } |
| |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_MIGRATE, eventDescription = "migrating VM", async = true) |
| public VirtualMachine migrateVirtualMachine(Long vmId, Host destinationHost) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException { |
| // 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!"); |
| } |
| |
| VMInstanceVO vm = _vmInstanceDao.findById(vmId); |
| if (vm == null) { |
| throw new InvalidParameterValueException("Unable to find the VM by id=" + vmId); |
| } |
| // business logic |
| if (vm.getState() != State.Running) { |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("VM is not Running, unable to migrate the vm " + vm); |
| } |
| InvalidParameterValueException ex = new InvalidParameterValueException("VM is not Running, unable to migrate the vm with specified id"); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| throw ex; |
| } |
| if (!vm.getHypervisorType().equals(HypervisorType.XenServer) && !vm.getHypervisorType().equals(HypervisorType.VMware) && !vm.getHypervisorType().equals(HypervisorType.KVM) && !vm.getHypervisorType().equals(HypervisorType.Ovm)) { |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug(vm + " is not XenServer/VMware/KVM/Ovm, cannot migrate this VM."); |
| } |
| throw new InvalidParameterValueException("Unsupported Hypervisor Type for VM migration, we support XenServer/VMware/KVM only"); |
| } |
| |
| if (isVMUsingLocalStorage(vm)) { |
| 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"); |
| } |
| |
| //check if migrating to same host |
| long srcHostId = vm.getHostId(); |
| if(destinationHost.getId() == srcHostId){ |
| throw new InvalidParameterValueException("Cannot migrate VM, VM is already presnt on this host, please specify valid destination host to migrate the VM"); |
| } |
| |
| //check if host is UP |
| if(destinationHost.getStatus() != com.cloud.host.Status.Up || destinationHost.getResourceState() != ResourceState.Enabled){ |
| throw new InvalidParameterValueException("Cannot migrate VM, destination host is not in correct state, has status: "+destinationHost.getStatus() + ", state: " +destinationHost.getResourceState()); |
| } |
| |
| // call to core process |
| DataCenterVO dcVO = _dcDao.findById(destinationHost.getDataCenterId()); |
| HostPodVO pod = _podDao.findById(destinationHost.getPodId()); |
| Cluster cluster = _clusterDao.findById(destinationHost.getClusterId()); |
| DeployDestination dest = new DeployDestination(dcVO, pod, cluster, destinationHost); |
| |
| //check max guest vm limit for the destinationHost |
| HostVO destinationHostVO = _hostDao.findById(destinationHost.getId()); |
| if(_capacityMgr.checkIfHostReachMaxGuestLimit(destinationHostVO)){ |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Host name: " + destinationHost.getName() + ", hostId: "+ destinationHost.getId() +" already has max Running VMs(count includes system VMs), cannot migrate to this host"); |
| } |
| throw new VirtualMachineMigrationException("Destination host, hostId: "+ destinationHost.getId() +" already has max Running VMs(count includes system VMs), cannot migrate to this host"); |
| } |
| |
| VMInstanceVO migratedVm = _itMgr.migrate(vm, srcHostId, dest); |
| return migratedVm; |
| } |
| |
| @DB |
| @Override |
| @ActionEvent(eventType = EventTypes.EVENT_VM_MOVE, eventDescription = "move VM to another user", async = false) |
| public UserVm moveVMToUser(AssignVMCmd cmd) throws ResourceAllocationException, ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { |
| // VERIFICATIONS and VALIDATIONS |
| |
| //VV 1: verify the two users |
| Account caller = UserContext.current().getCaller(); |
| if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN && caller.getType() != Account.ACCOUNT_TYPE_DOMAIN_ADMIN){ // only root admin can assign VMs |
| throw new InvalidParameterValueException("Only domain admins are allowed to assign VMs and not " + caller.getType()); |
| } |
| |
| //get and check the valid VM |
| UserVmVO vm = _vmDao.findById(cmd.getVmId()); |
| if (vm == null){ |
| throw new InvalidParameterValueException("There is no vm by that id " + cmd.getVmId()); |
| } else if (vm.getState() == State.Running) { // VV 3: check if vm is running |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("VM is Running, unable to move the vm " + vm); |
| } |
| InvalidParameterValueException ex = new InvalidParameterValueException("VM is Running, unable to move the vm with specified vmId"); |
| ex.addProxyObject(vm, cmd.getVmId(), "vmId"); |
| throw ex; |
| } |
| |
| Account oldAccount = _accountService.getActiveAccountById(vm.getAccountId()); |
| if (oldAccount == null) { |
| throw new InvalidParameterValueException("Invalid account for VM " + vm.getAccountId() + " in domain."); |
| } |
| //don't allow to move the vm from the project |
| if (oldAccount.getType() == Account.ACCOUNT_TYPE_PROJECT) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Specified Vm id belongs to the project and can't be moved"); |
| ex.addProxyObject(vm, cmd.getVmId(), "vmId"); |
| throw ex; |
| } |
| Account newAccount = _accountService.getActiveAccountByName(cmd.getAccountName(), cmd.getDomainId()); |
| if (newAccount == null || newAccount.getType() == Account.ACCOUNT_TYPE_PROJECT) { |
| throw new InvalidParameterValueException("Invalid accountid=" + cmd.getAccountName() + " in domain " + cmd.getDomainId()); |
| } |
| |
| if (newAccount.getState() == Account.State.disabled) { |
| throw new InvalidParameterValueException("The new account owner " + cmd.getAccountName() + " is disabled."); |
| } |
| |
| // make sure the accounts are under same domain |
| if (oldAccount.getDomainId() != newAccount.getDomainId()){ |
| throw new InvalidParameterValueException("The account should be under same domain for moving VM between two accounts. Old owner domain =" + oldAccount.getDomainId() + |
| " New owner domain=" + newAccount.getDomainId()); |
| } |
| |
| // make sure the accounts are not same |
| if (oldAccount.getAccountId() == newAccount.getAccountId()){ |
| throw new InvalidParameterValueException("The account should be same domain for moving VM between two accounts. Account id =" + oldAccount.getAccountId()); |
| } |
| |
| |
| // don't allow to move the vm if there are existing PF/LB/Static Nat rules, or vm is assigned to static Nat ip |
| List<PortForwardingRuleVO> pfrules = _portForwardingDao.listByVm(cmd.getVmId()); |
| if (pfrules != null && pfrules.size() > 0){ |
| throw new InvalidParameterValueException("Remove the Port forwarding rules for this VM before assigning to another user."); |
| } |
| List<FirewallRuleVO> snrules = _rulesDao.listStaticNatByVmId(vm.getId()); |
| if (snrules != null && snrules.size() > 0){ |
| throw new InvalidParameterValueException("Remove the StaticNat rules for this VM before assigning to another user."); |
| } |
| List<LoadBalancerVMMapVO> maps = _loadBalancerVMMapDao.listByInstanceId(vm.getId()); |
| if (maps != null && maps.size() > 0) { |
| throw new InvalidParameterValueException("Remove the load balancing rules for this VM before assigning to another user."); |
| } |
| // check for one on one nat |
| IPAddressVO ip = _ipAddressDao.findByAssociatedVmId(cmd.getVmId()); |
| if (ip != null){ |
| if (ip.isOneToOneNat()){ |
| throw new InvalidParameterValueException("Remove the one to one nat rule for this VM for ip " + ip.toString()); |
| } |
| } |
| |
| DataCenterVO zone = _dcDao.findById(vm.getDataCenterIdToDeployIn()); |
| |
| //Remove vm from instance group |
| removeInstanceFromInstanceGroup(cmd.getVmId()); |
| |
| //VV 2: check if account/domain is with in resource limits to create a new vm |
| _resourceLimitMgr.checkResourceLimit(newAccount, ResourceType.user_vm); |
| |
| //VV 3: check if volumes are with in resource limits |
| _resourceLimitMgr.checkResourceLimit(newAccount, ResourceType.volume, _volsDao.findByInstance(cmd.getVmId()).size()); |
| |
| // VV 4: Check if new owner can use the vm template |
| VirtualMachineTemplate template = _templateDao.findById(vm.getTemplateId()); |
| if (!template.isPublicTemplate()) { |
| Account templateOwner = _accountMgr.getAccount(template.getAccountId()); |
| _accountMgr.checkAccess(newAccount, null, true, templateOwner); |
| } |
| |
| // VV 5: check the new account can create vm in the domain |
| DomainVO domain = _domainDao.findById(cmd.getDomainId()); |
| _accountMgr.checkAccess(newAccount, domain); |
| |
| Transaction txn = Transaction.currentTxn(); |
| txn.start(); |
| //generate destroy vm event for usage |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_DESTROY, vm.getAccountId(), vm.getDataCenterIdToDeployIn(), vm.getId(), |
| vm.getHostName(), vm.getServiceOfferingId(), vm.getTemplateId(), vm.getHypervisorType().toString(), |
| VirtualMachine.class.getName(), vm.getUuid()); |
| // update resource counts |
| _resourceLimitMgr.decrementResourceCount(oldAccount.getAccountId(), ResourceType.user_vm); |
| |
| // OWNERSHIP STEP 1: update the vm owner |
| vm.setAccountId(newAccount.getAccountId()); |
| vm.setDomainId(cmd.getDomainId()); |
| _vmDao.persist(vm); |
| |
| // OS 2: update volume |
| List<VolumeVO> volumes = _volsDao.findByInstance(cmd.getVmId()); |
| for (VolumeVO volume : volumes) { |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), |
| volume.getDataCenterId(), volume.getId(), volume.getName(), Volume.class.getName(), volume.getUuid()); |
| _resourceLimitMgr.decrementResourceCount(oldAccount.getAccountId(), ResourceType.volume); |
| volume.setAccountId(newAccount.getAccountId()); |
| _volsDao.persist(volume); |
| _resourceLimitMgr.incrementResourceCount(newAccount.getAccountId(), ResourceType.volume); |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), |
| volume.getDataCenterId(), volume.getId(), volume.getName(), |
| volume.getDiskOfferingId(), volume.getTemplateId(), volume.getSize(), Volume.class.getName(), |
| volume.getUuid()); |
| //snapshots: mark these removed in db |
| List<SnapshotVO> snapshots = _snapshotDao.listByVolumeIdIncludingRemoved(volume.getId()); |
| for (SnapshotVO snapshot: snapshots){ |
| _snapshotDao.remove(snapshot.getId()); |
| } |
| } |
| |
| _resourceLimitMgr.incrementResourceCount(newAccount.getAccountId(), ResourceType.user_vm); |
| //generate usage events to account for this change |
| UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_CREATE, vm.getAccountId(), vm.getDataCenterIdToDeployIn(), vm.getId(), |
| vm.getHostName(), vm.getServiceOfferingId(), vm.getTemplateId(), vm.getHypervisorType().toString(), |
| VirtualMachine.class.getName(), vm.getUuid()); |
| |
| txn.commit(); |
| |
| VMInstanceVO vmoi = _itMgr.findByIdAndType(vm.getType(), vm.getId()); |
| VirtualMachineProfileImpl<VMInstanceVO> vmOldProfile = new VirtualMachineProfileImpl<VMInstanceVO>(vmoi); |
| |
| // OS 3: update the network |
| List<Long> networkIdList = cmd.getNetworkIds(); |
| List<Long> securityGroupIdList = cmd.getSecurityGroupIdList(); |
| |
| if (zone.getNetworkType() == NetworkType.Basic) { |
| if (networkIdList != null && !networkIdList.isEmpty()) { |
| throw new InvalidParameterValueException("Can't move vm with network Ids; this is a basic zone VM"); |
| } |
| //cleanup the old security groups |
| _securityGroupMgr.removeInstanceFromGroups(cmd.getVmId()); |
| //cleanup the network for the oldOwner |
| _networkMgr.cleanupNics(vmOldProfile); |
| _networkMgr.expungeNics(vmOldProfile); |
| //security groups will be recreated for the new account, when the VM is started |
| List<NetworkVO> networkList = new ArrayList<NetworkVO>(); |
| |
| // Get default guest network in Basic zone |
| Network defaultNetwork = _networkModel.getExclusiveGuestNetwork(zone.getId()); |
| |
| if (defaultNetwork == null) { |
| throw new InvalidParameterValueException("Unable to find a default network to start a vm"); |
| } else { |
| networkList.add(_networkDao.findById(defaultNetwork.getId())); |
| } |
| |
| boolean isVmWare = (template.getHypervisorType() == HypervisorType.VMware); |
| |
| if (securityGroupIdList != null && isVmWare) { |
| throw new InvalidParameterValueException("Security group feature is not supported for vmWare hypervisor"); |
| } else if (!isVmWare && _networkModel.isSecurityGroupSupportedInNetwork(defaultNetwork) && _networkModel.canAddDefaultSecurityGroup()) { |
| if (securityGroupIdList == null) { |
| securityGroupIdList = new ArrayList<Long>(); |
| } |
| SecurityGroup defaultGroup = _securityGroupMgr.getDefaultSecurityGroup(newAccount.getId()); |
| if (defaultGroup != null) { |
| //check if security group id list already contains Default security group, and if not - add it |
| boolean defaultGroupPresent = false; |
| for (Long securityGroupId : securityGroupIdList) { |
| if (securityGroupId.longValue() == defaultGroup.getId()) { |
| defaultGroupPresent = true; |
| break; |
| } |
| } |
| |
| if (!defaultGroupPresent) { |
| securityGroupIdList.add(defaultGroup.getId()); |
| } |
| |
| } else { |
| //create default security group for the account |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Couldn't find default security group for the account " + newAccount + " so creating a new one"); |
| } |
| defaultGroup = _securityGroupMgr.createSecurityGroup(SecurityGroupManager.DEFAULT_GROUP_NAME, SecurityGroupManager.DEFAULT_GROUP_DESCRIPTION, newAccount.getDomainId(), newAccount.getId(), newAccount.getAccountName()); |
| securityGroupIdList.add(defaultGroup.getId()); |
| } |
| } |
| |
| |
| List<Pair<NetworkVO, NicProfile>> networks = new ArrayList<Pair<NetworkVO, NicProfile>>(); |
| NicProfile profile = new NicProfile(); |
| profile.setDefaultNic(true); |
| networks.add(new Pair<NetworkVO, NicProfile>(networkList.get(0), profile)); |
| |
| VMInstanceVO vmi = _itMgr.findByIdAndType(vm.getType(), vm.getId()); |
| VirtualMachineProfileImpl<VMInstanceVO> vmProfile = new VirtualMachineProfileImpl<VMInstanceVO>(vmi); |
| _networkMgr.allocate(vmProfile, networks); |
| |
| _securityGroupMgr.addInstanceToGroups(vm.getId(), securityGroupIdList); |
| |
| s_logger.debug("AssignVM: Basic zone, adding security groups no " + securityGroupIdList.size() + " to " + vm.getInstanceName() ); |
| } else { |
| if (zone.isSecurityGroupEnabled()) { |
| throw new InvalidParameterValueException("Not yet implemented for SecurityGroupEnabled advanced networks."); |
| } else { |
| if (securityGroupIdList != null && !securityGroupIdList.isEmpty()) { |
| throw new InvalidParameterValueException("Can't move vm with security groups; security group feature is not enabled in this zone"); |
| } |
| //cleanup the network for the oldOwner |
| _networkMgr.cleanupNics(vmOldProfile); |
| _networkMgr.expungeNics(vmOldProfile); |
| |
| Set<NetworkVO> applicableNetworks = new HashSet<NetworkVO>(); |
| |
| if (networkIdList != null && !networkIdList.isEmpty()){ |
| // add any additional networks |
| for (Long networkId : networkIdList) { |
| NetworkVO network = _networkDao.findById(networkId); |
| if (network == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find specified network id"); |
| ex.addProxyObject(network, networkId, "networkId"); |
| throw ex; |
| } |
| |
| _networkModel.checkNetworkPermissions(newAccount, network); |
| |
| //don't allow to use system networks |
| NetworkOffering networkOffering = _configMgr.getNetworkOffering(network.getNetworkOfferingId()); |
| if (networkOffering.isSystemOnly()) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Specified Network id is system only and can't be used for vm deployment"); |
| ex.addProxyObject(network, networkId, "networkId"); |
| throw ex; |
| } |
| applicableNetworks.add(network); |
| } |
| } |
| else { |
| NetworkVO defaultNetwork = null; |
| List<NetworkOfferingVO> requiredOfferings = _networkOfferingDao.listByAvailability(Availability.Required, false); |
| if (requiredOfferings.size() < 1) { |
| throw new InvalidParameterValueException("Unable to find network offering with availability=" |
| + Availability.Required + " to automatically create the network as a part of vm creation"); |
| } |
| if (requiredOfferings.get(0).getState() == NetworkOffering.State.Enabled) { |
| // get Virtual networks |
| List<? extends Network> virtualNetworks = _networkModel.listNetworksForAccount(newAccount.getId(), zone.getId(), Network.GuestType.Isolated); |
| if (virtualNetworks.isEmpty()) { |
| long physicalNetworkId = _networkModel.findPhysicalNetworkId(zone.getId(), requiredOfferings.get(0).getTags(), requiredOfferings.get(0).getTrafficType()); |
| // Validate physical network |
| PhysicalNetwork physicalNetwork = _physicalNetworkDao.findById(physicalNetworkId); |
| if (physicalNetwork == null) { |
| throw new InvalidParameterValueException("Unable to find physical network with id: "+physicalNetworkId + " and tag: " +requiredOfferings.get(0).getTags()); |
| } |
| s_logger.debug("Creating network for account " + newAccount + " from the network offering id=" + |
| requiredOfferings.get(0).getId() + " as a part of deployVM process"); |
| Network newNetwork = _networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(), |
| newAccount.getAccountName() + "-network", newAccount.getAccountName() + "-network", null, null, |
| null, null, newAccount, null, physicalNetwork, zone.getId(), ACLType.Account, null, null, null, null); |
| defaultNetwork = _networkDao.findById(newNetwork.getId()); |
| } else if (virtualNetworks.size() > 1) { |
| throw new InvalidParameterValueException("More than 1 default Isolated networks are found " + |
| "for account " + newAccount + "; please specify networkIds"); |
| } else { |
| defaultNetwork = _networkDao.findById(virtualNetworks.get(0).getId()); |
| } |
| } else { |
| throw new InvalidParameterValueException("Required network offering id=" + requiredOfferings.get(0).getId() + " is not in " + NetworkOffering.State.Enabled); |
| } |
| |
| applicableNetworks.add(defaultNetwork); |
| } |
| |
| // add the new nics |
| List<Pair<NetworkVO, NicProfile>> networks = new ArrayList<Pair<NetworkVO, NicProfile>>(); |
| int toggle=0; |
| for (NetworkVO appNet: applicableNetworks){ |
| NicProfile defaultNic = new NicProfile(); |
| if (toggle==0){ |
| defaultNic.setDefaultNic(true); |
| toggle++; |
| } |
| networks.add(new Pair<NetworkVO, NicProfile>(appNet, defaultNic)); |
| } |
| VMInstanceVO vmi = _itMgr.findByIdAndType(vm.getType(), vm.getId()); |
| VirtualMachineProfileImpl<VMInstanceVO> vmProfile = new VirtualMachineProfileImpl<VMInstanceVO>(vmi); |
| _networkMgr.allocate(vmProfile, networks); |
| s_logger.debug("AssignVM: Advance virtual, adding networks no " + networks.size() + " to " + vm.getInstanceName() ); |
| } //END IF NON SEC GRP ENABLED |
| } // END IF ADVANCED |
| s_logger.info("AssignVM: vm " + vm.getInstanceName() + " now belongs to account " + cmd.getAccountName()); |
| return vm; |
| } |
| |
| @Override |
| public UserVm restoreVM(RestoreVMCmd cmd) { |
| // Input validation |
| Account caller = UserContext.current().getCaller(); |
| Long userId = UserContext.current().getCallerUserId(); |
| UserVO user = _userDao.findById(userId); |
| boolean needRestart = false; |
| |
| long vmId = cmd.getVmId(); |
| UserVmVO vm = _vmDao.findById(vmId); |
| if (vm == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Cann not find VM with ID " + vmId); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| throw ex; |
| } |
| |
| Account owner = _accountDao.findById(vm.getAccountId()); |
| if (owner == null) { |
| throw new InvalidParameterValueException("The owner of " + vm + " does not exist: " + vm.getAccountId()); |
| } |
| |
| if (owner.getState() == Account.State.disabled) { |
| throw new PermissionDeniedException("The owner of " + vm + " is disabled: " + vm.getAccountId()); |
| } |
| |
| if (vm.getState() != VirtualMachine.State.Running && vm.getState() != VirtualMachine.State.Stopped) { |
| throw new CloudRuntimeException("Vm " + vmId + " currently in " + vm.getState() + " state, restore vm can only execute when VM in Running or Stopped"); |
| } |
| |
| if (vm.getState() == VirtualMachine.State.Running) { |
| needRestart = true; |
| } |
| |
| List<VolumeVO> rootVols = _volsDao.findByInstance(vmId); |
| if (rootVols.isEmpty()) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Can not find root volume for VM " + vmId); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| throw ex; |
| } |
| |
| VolumeVO root = rootVols.get(0); |
| long templateId = root.getTemplateId(); |
| VMTemplateVO template = _templateDao.findById(templateId); |
| if (template == null) { |
| InvalidParameterValueException ex = new InvalidParameterValueException("Cannot find template for specified volumeid and vmId"); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| ex.addProxyObject(root, root.getId(), "volumeId"); |
| throw ex; |
| } |
| |
| if (needRestart) { |
| try { |
| _itMgr.stop(vm, user, caller); |
| } catch (ResourceUnavailableException e) { |
| s_logger.debug("Stop vm " + vmId + " failed", e); |
| CloudRuntimeException ex = new CloudRuntimeException("Stop vm failed for specified vmId"); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| throw ex; |
| } |
| } |
| |
| /* allocate a new volume from original template*/ |
| VolumeVO newVol = _storageMgr.allocateDuplicateVolume(root, null); |
| _volsDao.attachVolume(newVol.getId(), vmId, newVol.getDeviceId()); |
| |
| /* Detach and destory the old root volume */ |
| try { |
| _volsDao.detachVolume(root.getId()); |
| _storageMgr.destroyVolume(root); |
| } catch (ConcurrentOperationException e) { |
| s_logger.debug("Unable to delete old root volume " + root.getId() + ", user may manually delete it", e); |
| } |
| |
| if (needRestart) { |
| try { |
| _itMgr.start(vm, null, user, caller); |
| } catch (Exception e) { |
| s_logger.debug("Unable to start VM " + vmId, e); |
| CloudRuntimeException ex = new CloudRuntimeException("Unable to start VM with specified id" + e.getMessage()); |
| ex.addProxyObject(vm, vmId, "vmId"); |
| throw ex; |
| } |
| } |
| |
| s_logger.debug("Restore VM " + vmId + " with template " + root.getTemplateId() + " successfully"); |
| return vm; |
| } |
| |
| @Override |
| public boolean plugNic(Network network, NicTO nic, VirtualMachineTO vm, |
| ReservationContext context, DeployDestination dest) throws ConcurrentOperationException, ResourceUnavailableException, |
| InsufficientCapacityException { |
| UserVmVO vmVO = _vmDao.findById(vm.getId()); |
| if (vmVO.getState() == State.Running) { |
| try { |
| PlugNicCommand plugNicCmd = new PlugNicCommand(nic,vm.getName()); |
| Commands cmds = new Commands(OnError.Stop); |
| cmds.addCommand("plugnic",plugNicCmd); |
| _agentMgr.send(dest.getHost().getId(),cmds); |
| PlugNicAnswer plugNicAnswer = cmds.getAnswer(PlugNicAnswer.class); |
| if (!(plugNicAnswer != null && plugNicAnswer.getResult())) { |
| s_logger.warn("Unable to plug nic for " + vmVO); |
| return false; |
| } |
| } catch (OperationTimedoutException e) { |
| throw new AgentUnavailableException("Unable to plug nic for " + vmVO + " in network " + network, dest.getHost().getId(), e); |
| } |
| } else if (vmVO.getState() == State.Stopped || vmVO.getState() == State.Stopping) { |
| s_logger.warn(vmVO + " is Stopped, not sending PlugNicCommand. Currently " + vmVO.getState()); |
| } else { |
| s_logger.warn("Unable to plug nic, " + vmVO + " is not in the right state " + vmVO.getState()); |
| throw new ResourceUnavailableException("Unable to plug nic on the backend," + |
| vmVO + " is not in the right state", DataCenter.class, vmVO.getDataCenterIdToDeployIn()); |
| } |
| return true; |
| } |
| |
| |
| @Override |
| public boolean unplugNic(Network network, NicTO nic, VirtualMachineTO vm, |
| ReservationContext context, DeployDestination dest) throws ConcurrentOperationException, ResourceUnavailableException { |
| UserVmVO vmVO = _vmDao.findById(vm.getId()); |
| if (vmVO.getState() == State.Running) { |
| try { |
| UnPlugNicCommand unplugNicCmd = new UnPlugNicCommand(nic,vm.getName()); |
| Commands cmds = new Commands(OnError.Stop); |
| cmds.addCommand("unplugnic",unplugNicCmd); |
| _agentMgr.send(dest.getHost().getId(),cmds); |
| UnPlugNicAnswer unplugNicAnswer = cmds.getAnswer(UnPlugNicAnswer.class); |
| if (!(unplugNicAnswer != null && unplugNicAnswer.getResult())) { |
| s_logger.warn("Unable to unplug nic for " + vmVO); |
| return false; |
| } |
| } catch (OperationTimedoutException e) { |
| throw new AgentUnavailableException("Unable to unplug nic for " + vmVO + " in network " + network, dest.getHost().getId(), e); |
| } |
| } else if (vmVO.getState() == State.Stopped || vmVO.getState() == State.Stopping) { |
| s_logger.warn(vmVO + " is Stopped, not sending UnPlugNicCommand. Currently " + vmVO.getState()); |
| } else { |
| s_logger.warn("Unable to unplug nic, " + vmVO + " is not in the right state " + vmVO.getState()); |
| throw new ResourceUnavailableException("Unable to unplug nic on the backend," + |
| vmVO + " is not in the right state", DataCenter.class, vmVO.getDataCenterIdToDeployIn()); |
| } |
| return true; |
| } |
| |
| @Override |
| public void prepareStop(VirtualMachineProfile<UserVmVO> profile) { |
| } |
| |
| } |