/**
* 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.cloud;
import java.util.HashMap;
import java.util.Map;
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.DefaultApiService;
import com.abiquo.model.enumerator.HypervisorType;
import com.abiquo.server.core.cloud.NodeVirtualImage;
import com.abiquo.server.core.cloud.VirtualAppliance;
import com.abiquo.server.core.cloud.VirtualApplianceState;
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.cloud.VirtualMachineStateTransition;
import com.abiquo.tracer.ComponentType;
import com.abiquo.tracer.EventType;
import com.abiquo.tracer.SeverityType;
/**
* {@link VirtualMachine} lock and unlock functionality.
* <p>
* The operation this class executes must be atomic, to ensure that the lock will be effective at
* the moment it is requested. To achieve this every operation must be executed in an isolated
* transaction.
*
* @author Ignasi Barrera
*/
@Service
public class VirtualMachineLock extends DefaultApiService
{
/** The logger. **/
private final static Logger LOGGER = LoggerFactory.getLogger(VirtualMachineLock.class);
@Autowired
private VirtualMachineRep vmRepo;
@Autowired
private VirtualMachineService vmService;
@Autowired
private VirtualApplianceService vappService;
public VirtualMachineLock()
{
}
public VirtualMachineLock(final EntityManager em)
{
this.vmRepo = new VirtualMachineRep(em);
this.vmService = new VirtualMachineService(em);
this.vappService = new VirtualApplianceService(em);
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public VirtualMachineState lockVirtualMachineBeforeDeploying(final Integer vdcId,
final Integer vappId, final Integer vmId)
{
VirtualMachine vm = vmService.getVirtualMachine(vdcId, vappId, vmId);
VirtualMachineState originalState = vm.getState();
if (!VirtualMachineState.NOT_ALLOCATED.equals(originalState))
{
tracer.systemLog(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE,
EventType.VM_DEPLOY, "virtualMachine.deployedOrAllocated.system", vm.getName(),
originalState.name());
tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
"virtualMachine.deployedOrAllocated", vm.getName());
addConflictErrors(APIError.VIRTUAL_MACHINE_INVALID_STATE_DEPLOY);
flushErrors();
}
LOGGER.debug("The virtual machine is in state {} and is valid for deploy. Locking it..."
+ originalState.name());
lock(vm);
return originalState;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public VirtualMachineState lockVirtualMachineBeforeUndeploying(final Integer vdcId,
final Integer vappId, final Integer vmId)
{
VirtualMachine vm = vmService.getVirtualMachine(vdcId, vappId, vmId);
VirtualMachineState originalState = vm.getState();
if (originalState == VirtualMachineState.LOCKED)
{
tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
APIError.VIRTUAL_MACHINE_INVALID_STATE_UNDEPLOY.getMessage());
tracer.systemLog(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE,
EventType.VM_UNDEPLOY, "virtualMachine.cannotUndeployed", vm.getName());
addConflictErrors(APIError.VIRTUAL_MACHINE_INVALID_STATE_UNDEPLOY);
flushErrors();
}
if (originalState.existsInHypervisor())
{
lock(vm);
}
return originalState;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public VirtualMachineState lockVirtualMachineBeforeChangingState(final Integer vdcId,
final Integer vappId, final Integer vmId, final VirtualMachineState newState)
{
VirtualMachine vm = vmService.getVirtualMachine(vdcId, vappId, vmId);
VirtualMachineState originalState = vm.getState();
// Pause is not allowed in XEN
if (vm.getHypervisor().getType() == HypervisorType.XEN_3
&& newState == VirtualMachineState.PAUSED)
{
addConflictErrors(APIError.VIRTUAL_MACHINE_PAUSE_UNSUPPORTED);
flushErrors();
}
// The change state applies on the hypervisor. Now there is a NOT_ALLOCATED to get rid of
// the if(!hypervisor)
if (!originalState.existsInHypervisor())
{
addConflictErrors(APIError.VIRTUAL_MACHINE_UNALLOCATED_STATE);
flushErrors();
}
// Validate the state transition
VirtualMachineStateTransition validTransition =
VirtualMachineStateTransition.getValidVmStateChangeTransition(originalState, newState);
if (validTransition == null)
{
addConflictErrors(APIError.VIRTUAL_MACHINE_STATE_CHANGE_ERROR);
flushErrors();
}
LOGGER.debug(
"The virtual machine is in state {} and is valid for changing its state to {}. "
+ "Locking it..." + originalState.name(), newState.name());
lock(vm);
return originalState;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public VirtualMachineState lockVirtualMachineBeforeResetting(final Integer vdcId,
final Integer vappId, final Integer vmId)
{
VirtualMachine vm = vmService.getVirtualMachine(vdcId, vappId, vmId);
VirtualMachineState originalState = vm.getState();
// The change state applies on the hypervisor. Now there is a NOT_ALLOCATED to get rid of
// the if(!hypervisor)
if (!originalState.existsInHypervisor())
{
addConflictErrors(APIError.VIRTUAL_MACHINE_UNALLOCATED_STATE);
flushErrors();
}
if (originalState != VirtualMachineState.ON)
{
addConflictErrors(APIError.VIRTUAL_MACHINE_INVALID_STATE_RESET);
flushErrors();
}
LOGGER.debug("The virtual machine is in state {} and is valid for resetting. Locking it..."
+ originalState.name());
lock(vm);
return originalState;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public VirtualMachineState lockVirtualMachineBeforeSnapshotting(final Integer vdcId,
final Integer vappId, final Integer vmId)
{
VirtualMachine vm = vmService.getVirtualMachine(vdcId, vappId, vmId);
VirtualMachineState originalState = vm.getState();
if (!originalState.isDeployed())
{
// TODO Add some more specific APIError here
addConflictErrors(APIError.VIRTUAL_MACHINE_NOT_DEPLOYED);
flushErrors();
}
LOGGER
.debug("The virtual machine is in state {} and is valid for snapshotting. Locking it..."
+ originalState.name());
lock(vm);
return originalState;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public VirtualMachineState lockVirtualMachineBeforeReconfiguring(final Integer vdcId,
final Integer vappId, final Integer vmId)
{
VirtualMachine vm = vmService.getVirtualMachine(vdcId, vappId, vmId);
VirtualMachineState originalState = vm.getState();
if (!originalState.reconfigureAllowed())
{
final String current =
String.format("VirtualMachine %s in %s", vm.getUuid(), originalState.name());
tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE,
EventType.VM_RECONFIGURE, APIError.VIRTUAL_MACHINE_INCOHERENT_STATE.getMessage());
tracer.systemLog(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE,
EventType.VM_RECONFIGURE, APIError.VIRTUAL_MACHINE_INCOHERENT_STATE.getMessage()
+ "\n" + current);
addConflictErrors(APIError.VIRTUAL_MACHINE_INCOHERENT_STATE);
flushErrors();
}
if (vm.isImported())
{
tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE,
EventType.VM_RECONFIGURE,
APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE.getMessage());
addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE);
flushErrors();
}
LOGGER
.debug("The virtual machine is in state {} and is valid for reconfiguring. Locking it..."
+ originalState.name());
lock(vm);
return originalState;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public VirtualMachineState lockVirtualMachineBeforeDeleting(final Integer vdcId,
final Integer vappId, final Integer vmId)
{
VirtualMachine vm = vmService.getVirtualMachine(vdcId, vappId, vmId);
VirtualMachineState originalState = vm.getState();
if (VirtualMachineState.LOCKED.equals(originalState))
{
LOGGER.error("Delete virtual machine error, the state is LOCKED");
tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DELETE,
"virtualMachine.deleteFailed", vm.getName(), originalState.name());
addConflictErrors(APIError.VIRTUAL_MACHINE_INVALID_STATE_DELETE);
flushErrors();
}
LOGGER.debug("The virtual machine is in state {} and is valid for deleting. Locking it..."
+ originalState.name());
lock(vm);
return originalState;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public Map<Integer, VirtualMachineState> lockVirtualApplianceBeforeDeploying(
final Integer vdcId, final Integer vappId)
{
VirtualAppliance vapp = vappService.getVirtualAppliance(vdcId, vappId);
Map<Integer, VirtualMachineState> originalStates =
new HashMap<Integer, VirtualMachineState>();
for (NodeVirtualImage node : vapp.getNodes())
{
VirtualMachine vm = node.getVirtualMachine();
originalStates.put(vm.getId(), vm.getState());
lockVirtualMachineBeforeDeploying(vdcId, vappId, vm.getId());
}
return originalStates;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public Map<Integer, VirtualMachineState> lockVirtualApplianceBeforeUndeploying(
final Integer vdcId, final Integer vappId)
{
VirtualAppliance vapp = vappService.getVirtualAppliance(vdcId, vappId);
Map<Integer, VirtualMachineState> originalStates =
new HashMap<Integer, VirtualMachineState>();
for (NodeVirtualImage node : vapp.getNodes())
{
VirtualMachine vm = node.getVirtualMachine();
originalStates.put(vm.getId(), vm.getState());
lockVirtualMachineBeforeUndeploying(vdcId, vappId, vm.getId());
}
return originalStates;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public Map<Integer, VirtualMachineState> lockVirtualApplianceBeforeDeleting(
final Integer vdcId, final Integer vappId)
{
VirtualAppliance vapp = vappService.getVirtualAppliance(vdcId, vappId);
if (vapp.getState() != VirtualApplianceState.UNKNOWN
&& vapp.getState() != VirtualApplianceState.NOT_DEPLOYED)
{
LOGGER.error("Delete virtual appliance error, the state is {}", vapp.getState());
tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_APPLIANCE,
EventType.VAPP_DELETE, "virtualAppliance.deleteFailed", vapp.getName(), vapp
.getState().name());
addConflictErrors(APIError.VIRTUALAPPLIANCE_INVALID_STATE_DELETE);
flushErrors();
}
Map<Integer, VirtualMachineState> originalStates =
new HashMap<Integer, VirtualMachineState>();
for (NodeVirtualImage node : vapp.getNodes())
{
VirtualMachine vm = node.getVirtualMachine();
originalStates.put(vm.getId(), vm.getState());
lockVirtualMachineBeforeDeleting(vdcId, vappId, vm.getId());
}
return originalStates;
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void unlockVirtualMachine(final Integer vmId, final VirtualMachineState originalState)
{
VirtualMachine vm = vmRepo.findVirtualMachineById(vmId);
LOGGER
.debug("Unlocking virtual machine {}. Current state: {}", vm.getName(), vm.getState());
vm.setState(originalState);
vmRepo.update(vm);
LOGGER.debug("Virtual machine {} in state {} after unlock", vm.getName(), vm.getState());
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void unlockVirtualMachines(final Map<Integer, VirtualMachineState> originalStates)
{
for (Integer vmId : originalStates.keySet())
{
unlockVirtualMachine(vmId, originalStates.get(vmId));
}
}
private void lock(final VirtualMachine vm)
{
vm.setState(VirtualMachineState.LOCKED);
LOGGER.debug("The virtual machine is now in state {}." + vm.getState().name());
}
}