/** * Abiquo community edition * cloud management application for hybrid clouds * Copyright (C) 2008-2010 - Abiquo Holdings S.L. * * This application is free software; you can redistribute it and/or * modify it under the terms of the GNU LESSER GENERAL PUBLIC * LICENSE as published by the Free Software Foundation under * version 3 of the License * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * LESSER GENERAL PUBLIC LICENSE v.3 for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package com.abiquo.api.services; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.persistence.EntityManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.abiquo.api.exceptions.APIError; import com.abiquo.api.services.cloud.VirtualMachineService; import com.abiquo.model.enumerator.HypervisorType; import com.abiquo.model.transport.LinksDto; import com.abiquo.model.transport.error.CommonError; import com.abiquo.scheduler.limit.EnterpriseLimitChecker; import com.abiquo.scheduler.limit.LimitExceededException; import com.abiquo.server.core.cloud.VirtualAppliance; import com.abiquo.server.core.cloud.VirtualDatacenter; import com.abiquo.server.core.cloud.VirtualDatacenterRep; import com.abiquo.server.core.cloud.VirtualMachine; import com.abiquo.server.core.cloud.VirtualMachineRep; import com.abiquo.server.core.cloud.VirtualMachineState; import com.abiquo.server.core.enterprise.DatacenterLimits; import com.abiquo.server.core.enterprise.Enterprise; import com.abiquo.server.core.infrastructure.InfrastructureRep; import com.abiquo.server.core.infrastructure.storage.DiskManagement; import com.abiquo.server.core.infrastructure.storage.StorageRep; import com.abiquo.server.core.scheduler.VirtualMachineRequirements; import com.abiquo.tracer.ComponentType; import com.abiquo.tracer.EventType; import com.abiquo.tracer.SeverityType; /** * Implements all the business logic for storage features. * * @author jdevesa */ @Service public class StorageService extends DefaultApiService { private static final Logger LOGGER = LoggerFactory.getLogger(StorageService.class); @Autowired protected InfrastructureRep datacenterRepo; @Autowired protected EnterpriseLimitChecker enterpriseLimitChecker; @Autowired protected StorageRep repo; /** User service for user-specific privileges */ @Autowired protected UserService userService; @Autowired protected VirtualDatacenterRep vdcRepo; @Autowired protected VirtualMachineService vmService; @Autowired protected VirtualMachineRep vmRepo; /** Default constructor. */ public StorageService() { } /** * Auxiliar constructor for test purposes. Haters gonna hate 'bzengine'. And his creator as * well... * * @param em {@link EntityManager} instance with active transaction. */ public StorageService(final EntityManager em) { vdcRepo = new VirtualDatacenterRep(em); userService = new UserService(em); repo = new StorageRep(em); enterpriseLimitChecker = new EnterpriseLimitChecker(em); datacenterRepo = new InfrastructureRep(em); vmService = new VirtualMachineService(em); } /** * Attach a list of disks to a virtual machine. * <p> * If the virtual machine is not deployed, the method simply returns <code>null</code>. If the * virtual machine is deployed, the attachment will run a reconfigure operation and this method * will return the identifier of the task object associated to the reconfigure operation. * * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance * @param vmId identifier of the virtual machine * @param hdRefs list of links to disks to attach. * @return The id of the Tarantino task if the virtual machine is deployed, <code>null</code> * otherwise. */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public Object attachHardDisks(final Integer vdcId, final Integer vappId, final Integer vmId, final LinksDto hdRefs, final VirtualMachineState originalState, final Boolean forceSoftLimits) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); VirtualAppliance vapp = getVirtualAppliance(vdc, vappId); VirtualMachine oldvm = getVirtualMachine(vapp, vmId); VirtualMachine newvm = vmService.duplicateVirtualMachineObject(oldvm); List<DiskManagement> disks = vmService.getHardDisksFromDto(vdc, hdRefs); if (0 == disks.size()) { addValidationErrors(APIError.VIRTUAL_MACHINE_AT_LEAST_ONE_DISK_SHOULD_BE_LINKED); flushErrors(); } // Check if the disk is already attached to this virtual machine // if it is attached in another one error will be raised later, in the // 'reconfigureVirtualMachine' method for (DiskManagement disk : disks) { if (disk.getVirtualMachine() != null && disk.getVirtualMachine().getId().equals(vmId)) { addConflictErrors(APIError.VIRTUAL_MACHINE_DISK_ALREADY_ATTACHED_TO_THIS_VIRTUALMACHINE); flushErrors(); } } newvm.getDisks().addAll(disks); return vmService.reconfigureVirtualMachine(vdc, vapp, oldvm, newvm, originalState, forceSoftLimits); } /** * Set the list of disks from a Virtual Machine. * <p> * If the virtual machine is not deployed, the method simply returns <code>null</code>. If the * virtual machine is deployed, the detachment will run a reconfigure operation and this method * will return the identifier of the task object associated to the reconfigure operation. * * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance * @param vmId identifier of the virtual machine * @return The id of the Tarantino task if the virtual machine is deployed, <code>null</code> * otherwise. */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public Object changeHardDisks(final Integer vdcId, final Integer vappId, final Integer vmId, final LinksDto hdRefs, final VirtualMachineState originalState) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); VirtualAppliance vapp = getVirtualAppliance(vdc, vappId); VirtualMachine vm = getVirtualMachine(vapp, vmId); VirtualMachine newvm = vmService.duplicateVirtualMachineObject(vm); List<DiskManagement> disks = vmService.getHardDisksFromDto(vdc, hdRefs); newvm.setDisks(disks); return vmService.reconfigureVirtualMachine(vdc, vapp, vm, newvm, originalState); } /** * Creates a new resource {@link DiskManagement} associated to a virtual machine. * * @param vdcId identifier of the virtual datacenter. * @param sizeInMb size of the disk. * @return {@link DiskManagement} instance created. */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public DiskManagement createHardDisk(final Integer vdcId, final Long sizeInMb, final Boolean forceSoftLimits) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); if (!vdc.getHypervisorType().equals(HypervisorType.VMX_04)) { LOGGER.debug("Only ESXi is allowed to create hard disks. Found a " + vdc.getHypervisorType().getValue() + " hypervisor"); addConflictErrors(APIError.HD_CREATION_NOT_UNAVAILABLE); flushErrors(); } // The user has the role for manage This. But... is the user from the same enterprise // than Virtual Datacenter? userService.checkCurrentEnterpriseForPostMethods(vdc.getEnterprise()); // check input parameters if (sizeInMb == null || sizeInMb < 1L) { addValidationErrors(APIError.HD_INVALID_DISK_SIZE); flushErrors(); } // check the limits.Already check when deploy/reconfigure VM // checkStorageLimits(vdc, sizeInMb, forceSoftLimits); DiskManagement disk = new DiskManagement(vdc, sizeInMb); validate(disk); repo.insertHardDisk(disk); // Trace if (tracer != null) { tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_DATACENTER, EventType.HARD_DISK_CREATE, "hardDisk.created", sizeInMb, vdc.getName()); } return disk; } /** * Attach a list of disks to a virtual machine. * <p> * If the virtual machine is not deployed, the method simply returns <code>null</code>. If the * virtual machine is deployed, the attachment will run a reconfigure operation and this method * will return the identifier of the task object associated to the reconfigure operation. * * @param volume The volume to attach. * @param vm The virtual machine. * @return The id of the Tarantino task if the virtual machine is deployed, <code>null</code> * otherwise. */ /** * Delete the disk from the virtual datacenter.\ * * @param vdcId identifier of the {@link VirtualDatacenter} * @param diskId identifier of the {@link DiskManagement} */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public void deleteHardDisk(final Integer vdcId, final Integer diskId) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); // The user has the role for manage This. But... is the user from the same enterprise // than Virtual Datacenter? userService.checkCurrentEnterpriseForPostMethods(vdc.getEnterprise()); DiskManagement disk = vdcRepo.findHardDiskByVirtualDatacenter(vdc, diskId); if (disk == null) { addNotFoundErrors(APIError.HD_NON_EXISTENT_HARD_DISK); flushErrors(); } if (disk.getVirtualMachine() != null) { addConflictErrors(APIError.HD_CURRENTLY_ALLOCATED); flushErrors(); } repo.removeHardDisk(disk); // Trace if (tracer != null) { tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_DATACENTER, EventType.HARD_DISK_DELETE, "hardDisk.deleted", disk.getId(), disk.getSizeInMb(), vdc.getName()); } } /** * Detach a hard disk from a virtual machine. * <p> * If the virtual machine is not deployed, the method simply returns <code>null</code>. If the * virtual machine is deployed, the detachment will run a reconfigure operation and this method * will return the identifier of the task object associated to the reconfigure operation. * * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance * @param vmId identifier of the virtual machine * @param diskId identifier of the disk to detach. * @return The id of the Tarantino task if the virtual machine is deployed, <code>null</code> * otherwise. */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public Object detachHardDisk(final Integer vdcId, final Integer vappId, final Integer vmId, final Integer diskId, final VirtualMachineState originalState, final Boolean forceSoftLimits) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); VirtualAppliance vapp = getVirtualAppliance(vdc, vappId); VirtualMachine vm = getVirtualMachine(vapp, vmId); DiskManagement disk = vdcRepo.findHardDiskByVirtualMachine(vm, diskId); if (disk == null) { addNotFoundErrors(APIError.HD_NON_EXISTENT_HARD_DISK); flushErrors(); } VirtualMachine newVm = vmService.duplicateVirtualMachineObject(vm); Iterator<DiskManagement> diskIterator = newVm.getDisks().iterator(); while (diskIterator.hasNext()) { DiskManagement currentDisk = diskIterator.next(); if (currentDisk.getRasd().equals(disk.getRasd())) { diskIterator.remove(); return vmService.reconfigureVirtualMachine(vdc, vapp, vm, newVm, originalState, forceSoftLimits); } } addUnexpectedErrors(APIError.HD_NON_EXISTENT_HARD_DISK); flushErrors(); return null; } /** * Detach all the list of disks from a Virtual Machine. * <p> * If the virtual machine is not deployed, the method simply returns <code>null</code>. If the * virtual machine is deployed, the detachment will run a reconfigure operation and this method * will return the identifier of the task object associated to the reconfigure operation. * * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance * @param vmId identifier of the virtual machine * @return The id of the Tarantino task if the virtual machine is deployed, <code>null</code> * otherwise. */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public Object detachHardDisks(final Integer vdcId, final Integer vappId, final Integer vmId, final VirtualMachineState originalState) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); VirtualAppliance vapp = getVirtualAppliance(vdc, vappId); VirtualMachine vm = getVirtualMachine(vapp, vmId); VirtualMachine newVm = vmService.duplicateVirtualMachineObject(vm); newVm.getDisks().clear(); return vmService.reconfigureVirtualMachine(vdc, vapp, vm, newVm, originalState); } /** * Return a single of {@link DiskManagement} defined into a Virtual datacenter. * * @param vdcId identifier of the {@link VirtualDatacenter} * @param diskId identifier of the {@link DiskManagement} * @return the found {@link DiskManagement} */ @Transactional(readOnly = true, propagation = Propagation.REQUIRED) public DiskManagement getHardDiskByVirtualDatacenter(final Integer vdcId, final Integer diskId) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); DiskManagement disk = vdcRepo.findHardDiskByVirtualDatacenter(vdc, diskId); if (disk == null) { addNotFoundErrors(APIError.HD_NON_EXISTENT_HARD_DISK); flushErrors(); } LOGGER.debug("Returning a single disk created into VirtualDatacenter '" + vdc.getName() + "' identifier by id: " + diskId + "."); return disk; } /** * Returns a single DiskManagement from a Virtual Machine. * * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance. * @param vmId identifier of the virtual machine. * @param diskOrder disk order inside the virtual machine. * @return a single Disk according to its order. */ @Transactional(readOnly = true, propagation = Propagation.REQUIRED) public DiskManagement getHardDiskByVM(final Integer vdcId, final Integer vappId, final Integer vmId, final Integer diskId) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); VirtualAppliance vapp = getVirtualAppliance(vdc, vappId); VirtualMachine vm = getVirtualMachine(vapp, vmId); DiskManagement targetDisk = vdcRepo.findHardDiskByVirtualMachine(vm, diskId); if (targetDisk == null) { addNotFoundErrors(APIError.HD_NON_EXISTENT_HARD_DISK); flushErrors(); } LOGGER.debug("Returning a single disk allocated into VirtualMachine '" + vm.getName() + "' identifier by id: " + diskId + "."); return targetDisk; } /** * Return the list of {@link DiskManagement} defined into a Virtual datacenter. * * @param vdcId identifier of the {@link VirtualDatacenter} * @return the list of found {@link DiskManagement} */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public List<DiskManagement> getListOfHardDisksByVirtualDatacenter(final Integer vdcId) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); List<DiskManagement> disks = vdcRepo.findHardDisksByVirtualDatacenter(vdc); LOGGER.debug("Returning list of disks created into VirtualDatacenter '" + vdc.getName() + "."); return disks; } /** * Return the list of disks a Virtual Machine is using. * * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance. * @param vmId identifier of the virtual machine. * @return the list of Disks. */ @Transactional(readOnly = true, propagation = Propagation.REQUIRED) public List<DiskManagement> getListOfHardDisksByVM(final Integer vdcId, final Integer vappId, final Integer vmId) { List<DiskManagement> disks = new ArrayList<DiskManagement>(); // Check the parameter's correctness VirtualDatacenter vdc = getVirtualDatacenter(vdcId); VirtualAppliance vapp = getVirtualAppliance(vdc, vappId); VirtualMachine vm = getVirtualMachine(vapp, vmId); disks.addAll(vdcRepo.findHardDisksByVirtualMachine(vm)); LOGGER.debug("Returning list of disks allocated into VirtualMachine '" + vdc.getName() + "."); return disks; } /** * Attaches a new Hard Disk inside a Virtual Machine into Database. The disk should already be * attached to the virtual machine. * * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance. * @param vmId identifier of the virtual machine. * @param diskSizeInMb disk size in mega bytes. * @return the created object {@link DiskManagement} */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public DiskManagement registerHardDiskIntoVMInDatabase(final Integer vdcId, final Integer vappId, final Integer vmId, final Integer diskId) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); VirtualAppliance vapp = getVirtualAppliance(vdc, vappId); VirtualMachine vm = getVirtualMachine(vapp, vmId); // Check if the machine is in the correct state to perform the action. if (!vm.getState().equals(VirtualMachineState.NOT_ALLOCATED)) { addConflictErrors(APIError.VIRTUAL_MACHINE_INCOHERENT_STATE); flushErrors(); } // get the disk from the virtualdatacenter's list DiskManagement createdDisk = vdcRepo.findHardDiskByVirtualDatacenter(vdc, diskId); if (createdDisk == null) { addNotFoundErrors(APIError.HD_NON_EXISTENT_HARD_DISK); flushErrors(); } // if the hard disk is already attached to another virtual machine // , raise a conflict error. if (createdDisk.getVirtualMachine() != null) { addConflictErrors(APIError.HD_CURRENTLY_ALLOCATED); flushErrors(); } createdDisk.setVirtualAppliance(vapp); createdDisk.setVirtualMachine(vm); createdDisk.setSequence(repo.findDisksAndVolumesByVirtualMachine(vm).size()); vdcRepo.updateDisk(createdDisk); // Trace if (tracer != null) { tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.HARD_DISK_ASSIGN, "hardDisk.assigned", createdDisk.getId(), createdDisk.getSizeInMb(), vm.getName()); } return createdDisk; } /** * Detach a hard disk. Machine must be stopped and user should have the enough permissions. * * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance. * @param vmId identifier of the virtual machine. * @param diskId identifier of the disk */ @Transactional(readOnly = false, propagation = Propagation.REQUIRED) public void unregisterHardDiskFromVMInDatabase(final Integer vdcId, final Integer vappId, final Integer vmId, final Integer diskId) { VirtualDatacenter vdc = getVirtualDatacenter(vdcId); VirtualAppliance vapp = getVirtualAppliance(vdc, vappId); VirtualMachine vm = getVirtualMachine(vapp, vmId); // The user has the role for manage This. But... is the user from the same enterprise // than Virtual Datacenter? userService.checkCurrentEnterpriseForPostMethods(vdc.getEnterprise()); // Check if the machine is in the correct state to perform the action. if (!vm.getState().equals(VirtualMachineState.NOT_ALLOCATED)) { addConflictErrors(APIError.VIRTUAL_MACHINE_INCOHERENT_STATE); flushErrors(); } // Be sure the disk exists. DiskManagement disk = vdcRepo.findHardDiskByVirtualMachine(vm, diskId); if (disk == null) { addNotFoundErrors(APIError.HD_NON_EXISTENT_HARD_DISK); flushErrors(); } // unregister the disk from the virtual machine disk.setVirtualAppliance(null); disk.setVirtualMachine(null); vdcRepo.updateDisk(disk); // Trace if (tracer != null) { tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.HARD_DISK_UNASSIGN, "hardDisk.released", disk.getId(), disk.getSizeInMb(), vm.getName()); } } /** * Check the limits for a new disk. * * @param vdc object {@link VirtualDatacenter} * @param sizeInMB new size we want to add as a resource. */ protected void checkStorageLimits(final VirtualDatacenter vdc, final long sizeInMB, final Boolean forceSoftLimits) { // Must use the enterprise from VDC. When creating iSCSI volumes will be the cloud admin // creating volumes in other enterprises VDC Enterprise enterprise = vdc.getEnterprise(); LOGGER.debug("Checking limits for enterprise {} to a locate a volume of {}MB", enterprise.getName(), sizeInMB); DatacenterLimits dcLimits = datacenterRepo.findDatacenterLimits(enterprise, vdc.getDatacenter()); if (dcLimits == null) { addForbiddenErrors(APIError.DATACENTER_NOT_ALLOWED); flushErrors(); } VirtualMachineRequirements requirements = new VirtualMachineRequirements(0L, 0L, sizeInMB * 1024 * 1024, 0L, 0L, 0L, 0L); try { enterpriseLimitChecker.checkLimits(enterprise, requirements, forceSoftLimits); } catch (LimitExceededException ex) { addConflictErrors(new CommonError(APIError.LIMIT_EXCEEDED.getCode(), ex.toString())); flushErrors(); } } /** * Gets a Virtual Appliance. Raises a NOT_FOUND exception if it does not exist. * * @param vdc {@link VirtualDatacenter} instance where the vapp should be. * @param vappId identifier of the {@link VirtualAppliance} instance. * @return the found {@link VirtualAppliance} instance. */ protected VirtualAppliance getVirtualAppliance(final VirtualDatacenter vdc, final Integer vappId) { VirtualAppliance vapp = vdcRepo.findVirtualApplianceById(vdc, vappId); if (vapp == null) { addNotFoundErrors(APIError.NON_EXISTENT_VIRTUALAPPLIANCE); flushErrors(); } return vapp; } /** * Gets a VirtualDatacenter. Raises an exception if it does not exist. * * @param vdcId identifier of the virtual datacenter. * @return the found {@link VirtualDatacenter} instance. */ protected VirtualDatacenter getVirtualDatacenter(final Integer vdcId) { VirtualDatacenter vdc = vdcRepo.findById(vdcId); if (vdc == null) { addNotFoundErrors(APIError.NON_EXISTENT_VIRTUAL_DATACENTER); flushErrors(); } return vdc; } /** * Gets a Virtual Machine. Raises a NOT_FOUND exception if it does not exist. * * @param vapp {@link VirtualAppliance} instance where the VirtualMachine should be. * @param vmId identifier of the {@link VirtualMachine} instance. * @return the found {@link VirtualMachine} instance. */ protected VirtualMachine getVirtualMachine(final VirtualAppliance vapp, final Integer vmId) { VirtualMachine vm = vdcRepo.findVirtualMachineById(vapp, vmId); if (vm == null) { addNotFoundErrors(APIError.NON_EXISTENT_VIRTUALMACHINE); flushErrors(); } return vm; } /** * Gets disks of a virtual machine * * @param virtualMachine * @return */ public List<DiskManagement> getListOfHardDisksByVM(final VirtualMachine virtualMachine) { List<DiskManagement> disks = new ArrayList<DiskManagement>(); disks.addAll(vdcRepo.findHardDisksByVirtualMachine(virtualMachine)); LOGGER.debug("Returning list of disks allocated into VirtualMachine '" + virtualMachine.getName() + "."); return disks; } }