com.abiquo.api.services.cloud.VirtualMachineService.java Source code

Java tutorial

Introduction

Here is the source code for com.abiquo.api.services.cloud.VirtualMachineService.java

Source

/**
 * 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 static com.abiquo.api.resources.appslibrary.VirtualMachineTemplateResource.VIRTUAL_MACHINE_TEMPLATE;
import static com.abiquo.api.util.URIResolver.buildPath;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.persistence.EntityManager;
import javax.ws.rs.core.MultivaluedMap;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Hibernate;
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.exceptions.APIException;
import com.abiquo.api.exceptions.BadRequestException;
import com.abiquo.api.resources.EnterpriseResource;
import com.abiquo.api.resources.EnterprisesResource;
import com.abiquo.api.resources.TaskResourceUtils;
import com.abiquo.api.resources.appslibrary.DatacenterRepositoriesResource;
import com.abiquo.api.resources.appslibrary.DatacenterRepositoryResource;
import com.abiquo.api.resources.appslibrary.VirtualMachineTemplateResource;
import com.abiquo.api.resources.appslibrary.VirtualMachineTemplatesResource;
import com.abiquo.api.resources.cloud.DiskResource;
import com.abiquo.api.resources.cloud.DisksResource;
import com.abiquo.api.resources.cloud.IpAddressesResource;
import com.abiquo.api.resources.cloud.PrivateNetworkResource;
import com.abiquo.api.resources.cloud.PrivateNetworksResource;
import com.abiquo.api.resources.cloud.VirtualApplianceResource;
import com.abiquo.api.resources.cloud.VirtualAppliancesResource;
import com.abiquo.api.resources.cloud.VirtualDatacenterResource;
import com.abiquo.api.resources.cloud.VirtualDatacentersResource;
import com.abiquo.api.resources.cloud.VirtualMachineNetworkConfigurationResource;
import com.abiquo.api.resources.cloud.VirtualMachineResource;
import com.abiquo.api.resources.cloud.VirtualMachinesResource;
import com.abiquo.api.services.DefaultApiService;
import com.abiquo.api.services.NetworkService;
import com.abiquo.api.services.RemoteServiceService;
import com.abiquo.api.services.UserService;
import com.abiquo.api.services.VirtualMachineAllocatorService;
import com.abiquo.api.services.stub.AMServiceStub;
import com.abiquo.api.services.stub.TarantinoJobCreator;
import com.abiquo.api.services.stub.TarantinoService;
import com.abiquo.api.tracer.TracerLogger;
import com.abiquo.api.util.URIResolver;
import com.abiquo.api.util.snapshot.SnapshotUtils.SnapshotType;
import com.abiquo.commons.amqp.impl.tarantino.domain.builder.VirtualMachineDescriptionBuilder;
import com.abiquo.model.enumerator.EthernetDriverType;
import com.abiquo.model.enumerator.HypervisorType;
import com.abiquo.model.enumerator.NetworkType;
import com.abiquo.model.rest.RESTLink;
import com.abiquo.model.transport.SingleResourceTransportDto;
import com.abiquo.model.transport.error.CommonError;
import com.abiquo.model.transport.error.ErrorDto;
import com.abiquo.model.transport.error.ErrorsDto;
import com.abiquo.model.util.ModelTransformer;
import com.abiquo.scheduler.SchedulerLock;
import com.abiquo.scheduler.VirtualMachineRequirementsFactory;
import com.abiquo.server.core.appslibrary.AppsLibraryRep;
import com.abiquo.server.core.appslibrary.VirtualMachineTemplate;
import com.abiquo.server.core.cloud.Hypervisor;
import com.abiquo.server.core.cloud.NodeVirtualImage;
import com.abiquo.server.core.cloud.VirtualAppliance;
import com.abiquo.server.core.cloud.VirtualApplianceRep;
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.VirtualMachine.OrderByEnum;
import com.abiquo.server.core.cloud.VirtualMachineDto;
import com.abiquo.server.core.cloud.VirtualMachineRep;
import com.abiquo.server.core.cloud.VirtualMachineState;
import com.abiquo.server.core.cloud.VirtualMachineStateTransition;
import com.abiquo.server.core.cloud.VirtualMachineWithNodeDto;
import com.abiquo.server.core.enterprise.DatacenterLimits;
import com.abiquo.server.core.enterprise.Enterprise;
import com.abiquo.server.core.enterprise.EnterpriseRep;
import com.abiquo.server.core.enterprise.User;
import com.abiquo.server.core.infrastructure.Datacenter;
import com.abiquo.server.core.infrastructure.InfrastructureRep;
import com.abiquo.server.core.infrastructure.RemoteService;
import com.abiquo.server.core.infrastructure.management.Rasd;
import com.abiquo.server.core.infrastructure.management.RasdDAO;
import com.abiquo.server.core.infrastructure.management.RasdManagement;
import com.abiquo.server.core.infrastructure.management.RasdManagementDAO;
import com.abiquo.server.core.infrastructure.network.IpPoolManagement;
import com.abiquo.server.core.infrastructure.network.IpPoolManagementDAO;
import com.abiquo.server.core.infrastructure.network.NetworkAssignment;
import com.abiquo.server.core.infrastructure.network.NetworkAssignmentDAO;
import com.abiquo.server.core.infrastructure.network.NetworkConfiguration;
import com.abiquo.server.core.infrastructure.network.VLANNetwork;
import com.abiquo.server.core.infrastructure.network.VLANNetworkDAO;
import com.abiquo.server.core.infrastructure.storage.DiskManagement;
import com.abiquo.server.core.infrastructure.storage.StorageRep;
import com.abiquo.server.core.infrastructure.storage.VolumeManagement;
import com.abiquo.server.core.scheduler.VirtualMachineRequirements;
import com.abiquo.server.core.task.Task;
import com.abiquo.server.core.util.network.IPNetworkRang;
import com.abiquo.tracer.ComponentType;
import com.abiquo.tracer.EventType;
import com.abiquo.tracer.SeverityType;

@Service
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class VirtualMachineService extends DefaultApiService {
    /** The logger object **/
    private final static Logger LOGGER = LoggerFactory.getLogger(VirtualMachineService.class);

    @Autowired
    protected VirtualMachineRep repo;

    @Autowired
    protected RasdManagementDAO rasdDao;

    @Autowired
    protected RasdDAO rasdRawRao;

    @Autowired
    protected VirtualApplianceRep vappRep;

    @Autowired
    protected VirtualDatacenterRep vdcRep;

    @Autowired
    private RemoteServiceService remoteServiceService;

    @Autowired
    private UserService userService;

    @Autowired
    protected EnterpriseRep enterpriseRep;

    @Autowired
    private StorageRep storageRep;

    @Autowired
    private VirtualMachineAllocatorService vmAllocatorService;

    @Autowired
    private VirtualMachineRequirementsFactory vmRequirements;

    @Autowired
    private VirtualDatacenterService vdcService;

    @Autowired
    private InfrastructureRep infRep;

    @Autowired
    protected AppsLibraryRep appsLibRep;

    @Autowired
    protected TarantinoService tarantino;

    @Autowired
    private TarantinoJobCreator jobCreator;

    @Autowired
    private NetworkService ipService;

    @Autowired
    private AMServiceStub amService;

    @Autowired
    private IpPoolManagementDAO ipPoolManDao;

    @Autowired
    private NetworkAssignmentDAO netAssignDao;

    @Autowired
    private VLANNetworkDAO vlanNetworkDao;

    public VirtualMachineService() {

    }

    public VirtualMachineService(final EntityManager em) {
        this.repo = new VirtualMachineRep(em);
        this.rasdDao = new RasdManagementDAO(em);
        this.vappRep = new VirtualApplianceRep(em);
        this.vdcRep = new VirtualDatacenterRep(em);
        this.remoteServiceService = new RemoteServiceService(em);
        this.userService = new UserService(em);
        this.enterpriseRep = new EnterpriseRep(em);
        this.vmAllocatorService = new VirtualMachineAllocatorService(em);
        this.vmRequirements = new VirtualMachineRequirementsFactory(); // XXX
        this.vdcService = new VirtualDatacenterService(em);
        this.infRep = new InfrastructureRep(em);
        this.appsLibRep = new AppsLibraryRep(em);
        this.tarantino = new TarantinoService(em);
        this.storageRep = new StorageRep(em);
        this.jobCreator = new TarantinoJobCreator(em);
        this.ipService = new NetworkService(em);
        this.ipPoolManDao = new IpPoolManagementDAO(em);
        this.vlanNetworkDao = new VLANNetworkDAO(em);
        this.tracer = new TracerLogger();
    }

    public Collection<VirtualMachine> findByHypervisor(final Hypervisor hypervisor) {
        return repo.findByHypervisor(hypervisor);
    }

    public Collection<VirtualMachine> findNotAllocated(final Hypervisor hypervisor) {
        assert hypervisor != null;
        return repo.findVirtualMachinesNotAllocatedCompatibleHypervisor(hypervisor);
    }

    public Collection<VirtualMachine> findByEnterprise(final Enterprise enterprise) {
        assert enterprise != null;
        return repo.findByEnterprise(enterprise);
    }

    public List<VirtualMachine> findVirtualMachinesByUser(final Enterprise enterprise, final User user) {
        return repo.findVirtualMachinesByUser(enterprise, user);
    }

    public List<VirtualMachine> findByVirtualAppliance(final VirtualAppliance vapp, final Integer startwith,
            final String orderBy, final String filter, final Integer limit, final Boolean descOrAsc) {
        // Check if the orderBy element is actually one of the available ones
        VirtualMachine.OrderByEnum orderByEnum = VirtualMachine.OrderByEnum.fromValue(orderBy);
        if (orderByEnum == null) {
            LOGGER.info("Bad parameter 'by' in request to get the virtual machines by virtual appliance.");
            addValidationErrors(APIError.QUERY_INVALID_PARAMETER);
            flushErrors();
        }
        return repo.findVirtualMachinesByVirtualAppliance(vapp.getId(), startwith, limit, filter, orderByEnum,
                descOrAsc);
    }

    public VirtualMachine findByUUID(final String uuid) {
        return repo.findByUUID(uuid);
    }

    public VirtualMachine findByName(final String name) {
        return repo.findByName(name);
    }

    @Transactional(readOnly = true, propagation = Propagation.REQUIRED)
    public VirtualMachine getVirtualMachine(final Integer vdcId, final Integer vappId, final Integer vmId) {
        VirtualMachine vm = repo.findVirtualMachineById(vmId);

        VirtualAppliance vapp = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);
        VirtualDatacenter vdc = vdcRep.findById(vdcId);

        if (vm == null || !isAssignedTo(vmId, vapp.getId())) {
            LOGGER.error("Error retrieving the virtual machine: {} does not exist", vmId);
            addNotFoundErrors(APIError.NON_EXISTENT_VIRTUALMACHINE);
            flushErrors();
        }
        LOGGER.debug("Virtual machine {} found", vmId);

        // if the ips are external, we need to set the limitID in order to return the
        // proper info.
        for (IpPoolManagement ip : vm.getIps()) {
            if (ip.getVlanNetwork().getEnterprise() != null) {
                // needed for REST links.
                DatacenterLimits dl = infRep.findDatacenterLimits(ip.getVlanNetwork().getEnterprise(),
                        vdc.getDatacenter());
                ip.getVlanNetwork().setLimitId(dl.getId());
            }
        }

        return vm;
    }

    /**
     * This method is semi-duplicated from VirtualApplianceService, but bean can not be used due
     * cross references
     */
    private VirtualAppliance getVirtualApplianceAndCheckVirtualDatacenter(final Integer vdcId,
            final Integer vappId) {
        // checks vdc exist
        vdcService.getVirtualDatacenter(vdcId);

        VirtualAppliance vapp = vappRep.findById(vappId);
        if (vapp == null || !vapp.getVirtualDatacenter().getId().equals(vdcId)) {
            addNotFoundErrors(APIError.NON_EXISTENT_VIRTUALAPPLIANCE);
            flushErrors();
        }
        return vapp;
    }

    public VirtualMachine getVirtualMachine(final Integer vmId) {
        return repo.findVirtualMachineById(vmId);
    }

    public VirtualMachine getVirtualMachineByHypervisor(final Hypervisor hyp, final Integer vmId) {
        VirtualMachine vm = repo.findVirtualMachineByHypervisor(hyp, vmId);
        if (vm == null) {
            addNotFoundErrors(APIError.NON_EXISTENT_VIRTUALMACHINE);
            flushErrors();
        }
        return vm;
    }

    @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
    public void addImportedVirtualMachine(final VirtualMachine virtualMachine) {
        validate(virtualMachine);
        repo.insert(virtualMachine);
    }

    /**
         * Gets the DTO object and validates all of its parameters. Prepares the {@link VirtualMachine} object
         * and sends the object to the method {@link VirtualMachineService#reconfigureVirtualMachine(VirtualDatacenter, VirtualAppliance, VirtualMachine, VirtualMachine).
         * 
         * @param vdcId identifier of the {@link VirtualDatacenter}
         * @param vappId identifier of the {@link VirtualAppliance}
         * @param vmId identifier of the {@link VirtualMachine}
         * @param forceSoftLimits
         * @param dto input {@link VirtualMachineDto} object with all its links.
         * @return the link to the asnyncronous task.
         */
    @Transactional(propagation = Propagation.REQUIRED)
    public String reconfigureVirtualMachine(final Integer vdcId, final Integer vappId, final Integer vmId,
            final VirtualMachineDto dto, final VirtualMachineState originalState, final Boolean forceSoftLimits) {
        VirtualDatacenter vdc = getVirtualDatacenter(vdcId);
        // We need to operate with concrete and this also check that the VirtualMachine belongs to
        // those VirtualAppliance and VirtualDatacenter
        VirtualMachine virtualMachine = getVirtualMachine(vdcId, vappId, vmId);
        VirtualAppliance virtualAppliance = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

        VirtualMachine newvm = buildVirtualMachineFromDto(vdc, virtualAppliance, dto);

        // the provided VM Dto doesn't have the conversion link (is itn't published already)
        // so the created object form Dto needs to set the same conversion
        newvm.setVirtualImageConversion(virtualMachine.getVirtualImageConversion());

        newvm.setTemporal(virtualMachine.getId()); // we set the id to temporal since we are trying

        // we need to get the configuration value ALWAYS after to set the ips of the new virtual
        // machine
        // since it depends to it to check if the configuration of the network is valid
        // And also we need to set AFTER to set the 'temporal' value from this DTO.
        newvm.setNetworkConfiguration(getNetworkConfigurationFromDto(virtualAppliance, newvm, dto));

        // to update the virtualMachine.

        // allocated resources not present in the requested reconfiguration
        newvm.setDatastore(virtualMachine.getDatastore());
        newvm.setHypervisor(virtualMachine.getHypervisor());

        return reconfigureVirtualMachine(vdc, virtualAppliance, virtualMachine, newvm, originalState,
                forceSoftLimits);
    }

    /**
     * updates the {@link NodeVirtualImage} name, y and x (those setted in the virtual appliance
     * builder. <br>
     * This method must persist the changes even if the reconfigure of the {@link VirtualMachine}
     * fails.
     * 
     * @param vdcId identifier of the {@link VirtualDatacenter}
     * @param vappId identifier of the {@link VirtualAppliance}
     * @param vmId identifier of the {@link VirtualMachine}
     * @param dto input {@link VirtualMachineDto} object with all its links.
     * @return the link to the asnyncronous task.
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateNodeVirtualImageInfo(final Integer vdcId, final Integer vappId, final Integer vmId,
            final VirtualMachineWithNodeDto dto) {
        NodeVirtualImage nodeVirtualImage = getNodeVirtualImage(vdcId, vappId, vmId);

        nodeVirtualImage.setName(dto.getNodeName());
        nodeVirtualImage.setY(dto.getY());
        nodeVirtualImage.setX(dto.getX());
    }

    /**
     * <pre>
     * Prepare the machine to reconfigure. That means:
     * - Check the new allocation requirements
     * - Create the temporal register in database with the old values of the virtual machine for rollback purposes.
     * - Prepares and send the tarantino job.
     * - Returns the link to the asynchronous task to query in order to see the progress.
     * </pre>
     * 
     * This method assumes: - Any of the input params is null. - The 'isAssigned' checks are already
     * done: the virtual machine actually belongs to virtual appliance and the virtual appliance
     * actually belongs to virtual datacenter.
     * 
     * @param vdc {@link VirtualDatacenter} object where the virtual machine to reconfigure belongs
     *            to.
     * @param vapp {@link VirtualAppliance} object where the virtual machine to reconfigure belongs
     *            to.
     * @param newValues {@link VirtualMachine} exactly as we want to be after the reconfigure.
     * @param forceSoftLimits param if true does not trace the soft limit
     * @return a String containing the URI where to check the progress.
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public String reconfigureVirtualMachine(final VirtualDatacenter vdc, final VirtualAppliance vapp,
            final VirtualMachine vm, final VirtualMachine newValues, final VirtualMachineState originalState) {
        return reconfigureVirtualMachine(vdc, vapp, vm, newValues, originalState, false);
    }

    public String reconfigureVirtualMachine(final VirtualDatacenter vdc, final VirtualAppliance vapp,
            final VirtualMachine vm, final VirtualMachine newValues, final VirtualMachineState originalState,
            final Boolean forceSoftLimits) {

        if (checkReconfigureTemplate(vm.getVirtualMachineTemplate(), newValues.getVirtualMachineTemplate())) {
            if (vm.isCaptured()) {
                // don't allow to change the template if the machine is capture
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE);
                flushErrors();
            }

            LOGGER.debug("Will reconfigure the vm template");

            if (originalState.existsInHypervisor()) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_RECONFIGURE_TEMPLATE_IN_THE_HYPERVISOR);
                flushErrors();
            }

            // already checked is not attached
            if (newValues.getVirtualMachineTemplate().isStateful()) {
                LOGGER.debug("Attaching virtual machine template volume");
                newValues.getVirtualMachineTemplate().getVolume().attach(0, vm);
                newValues.getVirtualMachineTemplate().getVolume().setVirtualAppliance(vapp);
                // primary disk sequence == 0
            }
        }

        LOGGER.debug("Starting the reconfigure of the virtual machine {}", vm.getId());

        LOGGER.debug("Check for permissions");
        // The user must have the proper permission
        userService.checkCurrentEnterpriseForPostMethods(vm.getEnterprise());
        newValues.setEnterprise(vm.getEnterprise());

        LOGGER.debug("Permission granted");

        VirtualMachine backUpVm = null;
        VirtualMachineDescriptionBuilder virtualMachineTarantino = null;

        // if NOT_ALLOCATED isn't necessary to check the resource limits and
        // insert the 'backup' resources
        try {
            if (originalState == VirtualMachineState.OFF) {
                // There might be different hardware needs. This call also recalculate.
                LOGGER.debug("Updating the hardware needs in DB for virtual machine {}", vm.getId());
                VirtualMachineRequirements requirements = vmRequirements.createVirtualMachineRequirements(vm,
                        newValues);
                vmAllocatorService.checkAllocate(vapp.getId(), newValues, requirements, forceSoftLimits);

                LOGGER.debug("Creating the temporary register in Virtual Machine for rollback purposes");
                backUpVm = createBackUpMachine(vm);
                repo.insert(backUpVm);
                createBackUpResources(vm, backUpVm);
                insertBackUpResources(backUpVm);
                LOGGER.debug("Rollback register has id {}" + vm.getId());

                // Before to update the virtualmachine to new values, create the tarantino
                // descriptor
                // (only if the VM is deployed and OFF, othwerwise it won't have a datastore)
                virtualMachineTarantino = jobCreator.toTarantinoDto(vm, vapp);
            }

            // update the old virtual machine with the new virtual machine values.
            // and set the ID of the backupmachine (which has the old values) for recovery purposes.
            LOGGER.debug("Updating the virtual machine in the DB with id {}", vm.getId());
            updateVirtualMachineToNewValues(vapp, vm, newValues);
            LOGGER.debug("Updated virtual machine {}", vm.getId());

            // it is required a tarantino Task ?
            if (originalState == VirtualMachineState.NOT_ALLOCATED) {
                return null;
            }

            LOGGER.debug("Checking requires add initiatorMappings");
            initiatorMappings(vm);

            // refresh the virtualmachine object with the new values to get the
            // correct resources.
            VirtualMachineDescriptionBuilder newVirtualMachineTarantino = jobCreator.toTarantinoDto(newValues,
                    vapp);

            // A datacenter task is a set of jobs and datacenter task. This is, the deploy of a
            // VirtualMachine is the definition of the VirtualMachine and the job, power on
            return tarantino.reconfigureVirtualMachine(vm, virtualMachineTarantino, newVirtualMachineTarantino);
        } catch (APIException e) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_RECONFIGURE,
                    "virtualMachine.reconfigureError", vm.getName());

            tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_RECONFIGURE, e,
                    "virtualMachine.reconfigureError", vm.getName());

            throw e;
        } catch (Exception ex) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_RECONFIGURE,
                    "virtualMachine.reconfigureError", vm.getName());

            tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_RECONFIGURE, ex,
                    "virtualMachine.reconfigureError", vm.getName());

            addUnexpectedErrors(APIError.STATUS_INTERNAL_SERVER_ERROR);
            flushErrors();

            return null;
        }
    }

    /**
     * Checks if the {@link VirtualMachineTemplate} is being changed, if so checks the new template
     * is an instance or a persistent of the original template (if not reports a conflict
     * {@link APIError}).
     * 
     * @return true if the {@link VirtualMachineTemplate} is being reconfigured.
     */
    protected boolean checkReconfigureTemplate(final VirtualMachineTemplate original,
            final VirtualMachineTemplate requested) {
        if (original.getId().equals(requested.getId())) {
            return false;
        } else if (!original.isManaged()) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_RECONFIGURE_NOT_MANAGED);
            flushErrors();
        } else if (!requested.isManaged()) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_RECONFIGURE_TEMPLATE_NOT_MANAGED);
            flushErrors();
        } else if (requested.isStateful() && requested.getVolume().isAttached()) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_RECONFIGURE_TEMPLATE_ATTACHED_PRESISTENT);
            flushErrors();
        } else if (original.isMaster() && !requested.isMaster()
                && !requested.getMaster().getId().equals(original.getId())) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_RECONFIGURE_TEMPLATE_NOT_SAME_MASTER);
            flushErrors();
        } else if (!original.isMaster() && !requested.isMaster()
                && !requested.getMaster().getId().equals(original.getMaster().getId())) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_RECONFIGURE_TEMPLATE_NOT_SAME_MASTER);
            flushErrors();
        } else if (requested.isMaster() && !original.isMaster()
                && !requested.getId().equals(original.getMaster().getId())) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_RECONFIGURE_TEMPLATE_NOT_SAME_MASTER);
            flushErrors();
        } else if (original.isMaster() && requested.isMaster()) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_RECONFIGURE_TEMPLATE_NOT_SAME_MASTER);
            flushErrors();
        }

        return true;
    }

    /**
     * Insert the backup resources in database.
     * 
     * @param backUpVm
     */
    private void insertBackUpResources(final VirtualMachine backUpVm) {
        for (IpPoolManagement ip : backUpVm.getIps()) {
            vdcRep.insertTemporalIpManagement(ip);
        }
        for (VolumeManagement vol : backUpVm.getVolumes()) {
            storageRep.insertTemporalVolume(vol);
        }
        for (DiskManagement disk : backUpVm.getDisks()) {
            storageRep.insertTemporalHardDisk(disk);
        }

        // XXX this a kind of magic !!!
        backUpVm.setIps(null);
        backUpVm.setVolumes(null);
        backUpVm.setDisks(null);

        repo.update(backUpVm);
    }

    /**
     * Just assign the new virtual machine values to the new ones.
     * 
     * @param old old virtual machine instance
     * @param vmnew new virtual machine values
     */
    private void updateVirtualMachineToNewValues(final VirtualAppliance vapp, final VirtualMachine old,
            final VirtualMachine vmnew) {
        // if client changes cpu or ram, can be changed in captured machines
        // only if hypervisor is ESXi
        if (old.getCpu() != vmnew.getCpu() || old.getRam() != vmnew.getRam()) {
            if (old.isCaptured()
                    && !vapp.getVirtualDatacenter().getHypervisorType().equals(HypervisorType.VMX_04)) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE);
                flushErrors();

            }
        }

        // the rest of values never can be changed when reconfigure.
        if (differentDescription(old, vmnew) || differentNetworkConfiguration(old, vmnew)
                || differentPassword(old, vmnew)) {
            if (old.isCaptured()
                    && !vapp.getVirtualDatacenter().getHypervisorType().equals(HypervisorType.VMX_04)) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE);
                flushErrors();
            } else if (old.isCaptured()
                    && vapp.getVirtualDatacenter().getHypervisorType().equals(HypervisorType.VMX_04)) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE_FULLY);
                flushErrors();
            }
        }

        old.setCpu(vmnew.getCpu());
        old.setDescription(vmnew.getDescription());
        old.setRam(vmnew.getRam());
        old.setNetworkConfiguration(vmnew.getNetworkConfiguration());
        old.setPassword(vmnew.getPassword());
        old.setVirtualMachineTemplate(vmnew.getVirtualMachineTemplate());

        List<Integer> usedNICslots = dellocateOldNICs(old, vmnew);
        // if the number of old nics still used is different from
        // the number of usedNICslots, OR the number of old nics is different
        // from the new ones, it means some NICs has changed.
        if (usedNICslots.size() != old.getIps().size() || old.getIps().size() != vmnew.getIps().size()) {
            if (old.isCaptured()
                    && !vapp.getVirtualDatacenter().getHypervisorType().equals(HypervisorType.VMX_04)) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE);
                flushErrors();
            } else if (old.isCaptured()
                    && vapp.getVirtualDatacenter().getHypervisorType().equals(HypervisorType.VMX_04)) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE_FULLY);
                flushErrors();
            }
        }
        allocateNewNICs(vapp, old, vmnew.getIps(), usedNICslots);

        List<Integer> usedVolumeSlots = dellocateOldVolumes(old, vmnew);
        // if the number of old volumes still used is different from
        // the number of usedvolume Slots, OR the number of old volumes is different
        // from the new ones, it means some Volumes has changed.
        if (usedVolumeSlots.size() != old.getVolumes().size()
                || old.getVolumes().size() != vmnew.getVolumes().size()) {
            if (old.isCaptured()
                    && !vapp.getVirtualDatacenter().getHypervisorType().equals(HypervisorType.VMX_04)) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE);
                flushErrors();
            } else if (old.isCaptured()
                    && vapp.getVirtualDatacenter().getHypervisorType().equals(HypervisorType.VMX_04)) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE_FULLY);
                flushErrors();
            }
        }

        // never use the slot 0 for storage since it is the virtual image
        List<Integer> usedStorageSlots = dellocateOldDisks(old, vmnew);

        // if the number of old hard disks still used is different from
        // the number of usedhard Slots, OR the number of old storage is different
        // from the new ones, it means some hard disk has changed.
        if (usedStorageSlots.size() != old.getDisks().size() || old.getDisks().size() != vmnew.getDisks().size()) {
            if (old.isCaptured()
                    && !vapp.getVirtualDatacenter().getHypervisorType().equals(HypervisorType.VMX_04)) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE);
                flushErrors();
            } else if (old.isCaptured()
                    && vapp.getVirtualDatacenter().getHypervisorType().equals(HypervisorType.VMX_04)) {
                addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_CAN_NOT_RECONFIGURE_FULLY);
                flushErrors();
            }
        }

        usedStorageSlots.addAll(usedVolumeSlots);

        List<RasdManagement> storageResources = new ArrayList<RasdManagement>();
        storageResources.addAll(vmnew.getDisks());
        storageResources.addAll(vmnew.getVolumes());
        allocateNewStorages(vapp, old, storageResources, usedStorageSlots);

        repo.update(old);

        // FIXME: improvement related ABICLOUDPREMIUM-2925
        updateNodeVirtualImage(old, vmnew.getVirtualMachineTemplate());
    }

    private boolean differentNetworkConfiguration(final VirtualMachine old, final VirtualMachine vmnew) {
        if (old.getNetworkConfiguration() == null && vmnew.getNetworkConfiguration() == null) {
            return false;
        } else if (old.getNetworkConfiguration() == null && vmnew.getNetworkConfiguration() != null) {
            return true;
        } else if (old.getNetworkConfiguration() != null && vmnew.getNetworkConfiguration() == null) {
            return true;
        } else {
            return !old.getNetworkConfiguration().getId().equals(vmnew.getNetworkConfiguration().getId());
        }
    }

    /**
     * Check if the password has changed.
     * 
     * @param old
     * @param vmnew
     * @return
     */
    private boolean differentPassword(final VirtualMachine old, final VirtualMachine vmnew) {
        if (vmnew.getPassword() != null && vmnew.getPassword().isEmpty()) {
            vmnew.setPassword(null);
        }

        if (old.getPassword() == null && vmnew.getPassword() == null) {
            return false;
        } else if (old.getPassword() == null && vmnew.getPassword() != null) {
            return true;
        } else if (old.getPassword() != null && vmnew.getPassword() == null) {
            return true;
        } else {
            return !vmnew.getPassword().equals(old.getPassword());
        }
    }

    /**
     * Check if the description has changed.
     * 
     * @param old
     * @param vmnew
     * @return
     */
    private boolean differentDescription(final VirtualMachine old, final VirtualMachine vmnew) {
        if (old.getDescription() == null && vmnew.getDescription() == null) {
            return false;
        } else if (old.getDescription() == null && vmnew.getDescription() != null) {
            return true;
        } else if (old.getDescription() != null && vmnew.getDescription() == null) {
            return true;
        } else {
            return !old.getDescription().equals(vmnew.getDescription());
        }
    }

    /**
     * updates the virtual machine template from node virtual image with the template given by the
     * {@link VirtualMachineTemplate} param. <br>
     * This method must persist the changes even if the reconfigure of the {@link VirtualMachine}
     * fails.
     * 
     * @param vm {@link VirtualMachine} Virtual machine where obtains the related
     *            {@link NodeVirtualImage}
     * @parem template {@link VirtualMachineTemplate} Virtual Machine Template to set
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void updateNodeVirtualImage(final VirtualMachine vm, final VirtualMachineTemplate template) {
        NodeVirtualImage nvi = repo.findNodeVirtualImageByVm(vm);
        nvi.setVirtualImage(template);
        repo.updateNodeVirtualImage(nvi);
    }

    /**
     * Check if the resource is into the list of new resources.
     * 
     * @param resource {@link RasdManagement} resource to check
     * @param resources list of new Resources of the machine
     * @return true if the resource is into the new list.
     */
    private boolean resourceIntoNewList(final RasdManagement resource,
            final List<? extends RasdManagement> newResources) {
        for (RasdManagement newResource : newResources) {
            // Since the values point to the same rasd, the id should be the same
            if (resource.getRasd().getId().equals(newResource.getRasd().getId())) {
                return true;
            }
        }

        return false;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateVirtualMachine(final VirtualMachine vm) {
        userService.checkCurrentEnterpriseForPostMethods(vm.getEnterprise());
        repo.update(vm);
    }

    /**
     * Check if a virtual machine is defined into a virtual appliance.
     * 
     * @param vmId identifier of the virtual machine
     * @param vappId identifier of the virtual appliance
     * @return True if it is, false otherwise.
     */
    public boolean isAssignedTo(final Integer vmId, final Integer vappId) {
        List<VirtualMachine> vms = repo.findVirtualMachinesByVirtualAppliance(vappId, 0, 0, "", OrderByEnum.NAME,
                true);
        for (VirtualMachine vm : vms) {
            if (vm.getId().equals(vmId)) {
                return true;
            }
        }
        return false;
    }

    public VirtualMachineStateTransition validMachineStateChange(final VirtualMachine virtualMachine,
            final VirtualMachineState newState) {
        VirtualMachineStateTransition validTransition = VirtualMachineStateTransition
                .getValidVmStateChangeTransition(virtualMachine.getState(), newState);
        if (validTransition == null) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_STATE_CHANGE_ERROR);
            flushErrors();
        }
        return validTransition;
    }

    /**
     * Compare the state of vm with the state passed through parameter
     * 
     * @param vm VirtualMachine to which compare the state
     * @param state a valid VirtualMachine state
     * @return true if its the same state, false otherwise
     */
    public Boolean sameState(final VirtualMachine vm, final VirtualMachineState state) {
        String actual = vm.getState().toOVF();// OVFGeneratorService.getActualState(vm);
        return state.toOVF().equalsIgnoreCase(actual);
    }

    /**
     * Delete a {@link VirtualMachine}. And the {@link VirtualMachineNode}.
     * 
     * @param virtualMachine to delete. void
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void deleteVirtualMachine(final Integer vmId, final Integer vappId, final Integer vdcId,
            final VirtualMachineState originalState) {
        // We need to operate with concrete and this also check that the VirtualMachine belongs
        // to those VirtualAppliance and VirtualDatacenter
        VirtualMachine virtualMachine = getVirtualMachine(vdcId, vappId, vmId);

        // The user must have the proper permission
        userService.checkCurrentEnterpriseForPostMethods(virtualMachine.getEnterprise());

        if (VirtualMachineState.ALLOCATED.equals(originalState)) {
            final String lockMsg = "Deallocate " + virtualMachine.getId();
            try {
                LOGGER.warn("Delete of the ALLOCATED virtualMachine that has resources allocated. Deallocating");
                SchedulerLock.acquire(lockMsg);
                vmAllocatorService.deallocateVirtualMachine(virtualMachine);
            } finally {
                SchedulerLock.release(lockMsg);
                LOGGER.warn(
                        "Delete of the ALLOCATED virtualMachine that has resources allocated. Deallocating successful");
            }
        } else if (originalState.existsInHypervisor()) {
            LOGGER.debug("Delete of the virtualMachine that exists in hypervisor");
            undeployVirtualMachineAndDelete(virtualMachine, vappId, vdcId, originalState);
            return;
        }

        LOGGER.debug("Deleting the virtual machine with UUID {}", virtualMachine.getUuid());
        NodeVirtualImage nodeVirtualImage = findNodeVirtualImage(virtualMachine);
        LOGGER.trace("Deleting the node virtual image with id {}", nodeVirtualImage.getId());
        repo.deleteNodeVirtualImage(nodeVirtualImage);
        LOGGER.trace("Deleted node virtual image!");

        repo.deleteVirtualMachine(virtualMachine);

        // Does it has volumes? PREMIUM
        detachVolumesFromVirtualMachine(virtualMachine);
        LOGGER.debug("Detached the virtual machine's volumes with UUID {}", virtualMachine.getUuid());

        detachVirtualMachineIPs(virtualMachine);

        tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_DELETE, "virtualMachine.delete");
    }

    /**
     * Deletes the {@link Rasd} of an {@link IpPoolManagement}.
     * 
     * @param virtualMachine void
     */
    public void detachVirtualMachineIPs(final VirtualMachine virtualMachine) {
        for (IpPoolManagement ip : virtualMachine.getIps()) {
            vdcRep.deleteRasd(ip.getRasd());
            switch (ip.getType()) {
            case UNMANAGED:
                rasdDao.remove(ip);
                break;
            case EXTERNAL:
                ip.setVirtualDatacenter(null);
                ip.setMac(null);
                ip.setName(null);
            default:
                ip.detach();

            }
        }
    }

    /**
     * Delete a {@link VirtualMachine}. And the {@link VirtualMachineNode}. Without account for
     * permission.
     * 
     * @param virtualMachine to delete. void
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void deleteVirtualMachineBySystem(final VirtualMachine virtualMachine) {

        LOGGER.debug("Deleting the virtual machine with UUID {}", virtualMachine.getUuid());
        NodeVirtualImage nodeVirtualImage = findNodeVirtualImage(virtualMachine);
        LOGGER.trace("Deleting the node virtual image with id {}", nodeVirtualImage.getId());
        repo.deleteNodeVirtualImage(nodeVirtualImage);
        LOGGER.trace("Deleted node virtual image!");

        repo.deleteVirtualMachine(virtualMachine);

        // Does it has volumes? PREMIUM
        detachVolumesFromVirtualMachine(virtualMachine);
        LOGGER.debug("Detached the virtual machine's volumes with UUID {}", virtualMachine.getUuid());

        detachVirtualMachineIPs(virtualMachine);

        tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_DELETE, "virtualMachine.delete");
    }

    /**
     * This method is properly documented in the premium edition.
     * 
     * @param virtualMachine void
     */
    public void detachVolumesFromVirtualMachine(final VirtualMachine virtualMachine) {
        // PREMIUM
    }

    /**
     * Persists a {@link VirtualMachine}. If the preconditions are met.
     * 
     * @param virtualMachine to create. void
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public VirtualMachine createVirtualMachine(final Integer vdcId, final Integer vappId,
            final VirtualMachineDto dto) {
        VirtualDatacenter vdc = getVirtualDatacenter(vdcId);
        // We need to operate with concrete and this also check that the VirtualMachine belongs to
        // those VirtualAppliance and VirtualDatacenter
        VirtualAppliance virtualAppliance = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

        // First we get from dto. All the values wi
        VirtualMachine virtualMachine = buildVirtualMachineFromDto(vdc, virtualAppliance, dto);
        virtualMachine.setUuid(UUID.randomUUID().toString());
        virtualMachine.setIdType(VirtualMachine.MANAGED);
        String nodeName = virtualMachine.getName();
        if (dto instanceof VirtualMachineWithNodeDto) {
            nodeName = ((VirtualMachineWithNodeDto) dto).getNodeName();// we use the name to create
            // the node
        }
        virtualMachine.setName("ABQ_" + virtualMachine.getUuid());

        // Set the user and enterprise
        virtualMachine.setUser(userService.getCurrentUser());
        virtualMachine.setEnterprise(userService.getCurrentUser().getEnterprise());

        // We check for a suitable conversion (PREMIUM)
        attachVirtualMachineTemplateConversion(virtualAppliance.getVirtualDatacenter(), virtualMachine);

        // Attach the matching stateful volume if the template is a saved persistent template
        if (virtualMachine.getVirtualMachineTemplate().isStateful()) {
            LOGGER.debug("Attaching virtual machine template volume");
            virtualMachine.getVirtualMachineTemplate().getVolume().attach(0, virtualMachine, virtualAppliance);
            virtualMachine.getVirtualMachineTemplate().getVolume().setVirtualAppliance(virtualAppliance);
            virtualMachine.getVirtualMachineTemplate().getVolume().setVirtualMachine(virtualMachine);
        }

        // At this stage the virtual machine is not associated with any hypervisor
        virtualMachine.setState(VirtualMachineState.NOT_ALLOCATED);

        // A user can only create virtual machine
        validate(virtualMachine);
        repo.createVirtualMachine(virtualMachine);

        // The entity that defines the relation between a virtual machine, virtual applicance and
        // virtual machine template is VirtualImageNode
        createNodeVirtualImage(virtualMachine, virtualAppliance,
                StringUtils.isBlank(nodeName) ? virtualMachine.getVirtualMachineTemplate().getName() : nodeName);

        // We must add the default NIC. This is the very next free IP in the virtual datacenter's
        // default VLAN
        ipService.assignDefaultNICToVirtualMachine(virtualMachine.getId());
        tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_CREATE, "virtualMachine.create");
        return virtualMachine;
    }

    /**
     * @param vdcId
     * @param vappId
     * @param dto
     * @return
     */
    public VirtualMachine modifyVirtualMachine(final Integer vdcId, final Integer vappId, final Integer vmId) {

        // XXX
        // TODO: Implement this modifier!
        return getVirtualMachine(vmId);
    }

    /**
     * Sets the virtual machine HD requirements based on the {@link VirtualMachineTemplate}
     * <p>
     * It also set the required CPU and RAM if it wasn't specified in the requested
     * {@link VirtualMachineDto}.
     * <p>
     * Specify the {@link EthernetDriverType} if the
     */
    protected void setVirtualMachineTemplateRequirementsIfNotAlreadyDefined(final VirtualMachine vmachine,
            final VirtualMachineTemplate vmtemplate) {
        if (vmtemplate.getEthernetDriverType() != null) {
            vmachine.setEthernetDriverType(vmtemplate.getEthernetDriverType());

            LOGGER.debug("VirtualMachine {} will use specific EthernetDriver {}", vmachine.getName(),
                    vmtemplate.getEthernetDriverType().name());
        }

        if (vmachine.getCpu() == 0) {
            vmachine.setCpu(vmtemplate.getCpuRequired());
        }
        if (vmachine.getRam() == 0) {
            vmachine.setRam(vmtemplate.getRamRequired());
        }

        if (vmtemplate.isStateful()) {
            vmachine.setHdInBytes(0);
        } else {
            vmachine.setHdInBytes(vmtemplate.getHdRequiredInBytes());
        }
    }

    /**
     * This code is semiduplicated from VirtualMachineTemplateService but can't be used due cross
     * refrerence dep
     */
    private VirtualMachineTemplate getVirtualMachineTemplateAndValidateEnterpriseAndDatacenter(
            final Integer enterpriseId, final Integer datacenterId, final Integer virtualMachineTemplateId) {

        Datacenter datacenter = infRep.findById(datacenterId);
        Enterprise enterprise = enterpriseRep.findById(enterpriseId);

        if (datacenter == null) {
            addNotFoundErrors(APIError.NON_EXISTENT_DATACENTER);
        }
        if (enterprise == null) {
            addNotFoundErrors(APIError.NON_EXISTENT_ENTERPRISE);
        }
        flushErrors();

        DatacenterLimits limits = infRep.findDatacenterLimits(enterprise, datacenter);
        if (limits == null) {
            addConflictErrors(APIError.ENTERPRISE_NOT_ALLOWED_DATACENTER);
            flushErrors();
        }

        VirtualMachineTemplate virtualMachineTemplate = appsLibRep
                .findVirtualMachineTemplateById(virtualMachineTemplateId);
        if (virtualMachineTemplate == null) {
            addNotFoundErrors(APIError.NON_EXISTENT_VIRTUAL_MACHINE_TEMPLATE);
            flushErrors();
        }

        return virtualMachineTemplate;
    }

    /**
     * Creates the {@link NodeVirtualImage} that is the relation of {@link VirtualMachine}
     * {@link VirtualAppliance} and {@link VirtualMachineTemplate}.
     * 
     * @param virtualMachine virtual machine to be associated with the virtual appliance. It must
     *            contain the virtual machine template.
     * @param virtualAppliance void where the virtual machine exists.
     */
    protected void createNodeVirtualImage(final VirtualMachine virtualMachine,
            final VirtualAppliance virtualAppliance, final String name) {
        LOGGER.debug("Create node virtual image with name virtual machine: {}", virtualMachine.getName());
        NodeVirtualImage nodeVirtualImage = new NodeVirtualImage(name, virtualAppliance,
                virtualMachine.getVirtualMachineTemplate(), virtualMachine);
        repo.insertNodeVirtualImage(nodeVirtualImage);
        LOGGER.debug("Node virtual image created!");
    }

    /**
     * Prepares the virtual image, in premium it sets the conversion. Attachs the conversion if
     * premium to the {@link VirtualMachine}.
     * 
     * @param virtualDatacenter from where we retrieve the hypervisor type.
     * @param virtualMachine virtual machine to persist.
     * @return VirtualImage in premium the conversion.
     */
    public void attachVirtualMachineTemplateConversion(final VirtualDatacenter virtualDatacenter,
            final VirtualMachine virtualMachine) {
        // COMMUNITY does nothing.
        LOGGER.debug("attachVirtualImageConversion community edition");
    }

    /**
     * Deploys a {@link VirtualMachine}. This involves some steps. <br>
     * <ol>
     * <li>Select a machine to allocate the virtual machine</li>
     * <li>Check limits</li>
     * <li>Check resources</li>
     * <li>Check remote services</li>
     * <li>In premium call initiator</li>
     * <li>Subscribe to VSM</li>
     * <li>Build the Task DTO</li>
     * <li>Build the Configure DTO</li>
     * <li>Build the Power On DTO</li>
     * <li>Enqueue in tarantino</li>
     * <li>Register in redis</li>
     * <li>Add Task DTO to rabbitmq</li>
     * <li>Enable the resource <code>Progress<code></li>
     * </ol>
     * 
     * @param vdcId VirtualDatacenter id
     * @param vappId VirtualAppliance id
     * @param vmId VirtualMachine id
     * @param forceEnterpriseSoftLimits Do we should take care of the soft limits?
     * @param restBuilder injected restbuilder context parameter
     * @throws Exception
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public String deployVirtualMachine(final Integer vmId, final Integer vappId, final Integer vdcId,
            final Boolean foreceEnterpriseSoftLimits) {
        VirtualAppliance vapp = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);
        VirtualMachine vmachine = getVirtualMachine(vdcId, vappId, vmId);

        allocate(vmachine, vapp, foreceEnterpriseSoftLimits);

        return sendDeploy(vmachine, vapp);
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void allocate(final VirtualMachine virtualMachine, final VirtualAppliance vapp,
            final Boolean foreceEnterpriseSoftLimits) {
        LOGGER.debug("Starting the deploy of the virtual machine {}", virtualMachine.getId());
        // We need to operate with concrete and this also check that the VirtualMachine belongs to
        // those VirtualAppliance and VirtualDatacenter

        LOGGER.debug("Check for permissions");
        // The user must have the proper permission
        userService.checkCurrentEnterpriseForPostMethods(virtualMachine.getEnterprise());
        LOGGER.debug("Permission granted");

        LOGGER.debug("Checking the virtual machine state. It must be in NOT_ALLOCATED");

        // The remote services must be up for this Datacenter if we are to deploy
        // LOGGER.debug("Check remote services");
        // FIXME checkRemoteServicesByVirtualDatacenter(vdcId);
        // LOGGER.debug("Remote services are ok!");

        // Tasks needs the definition of the virtual machine
        // VirtualAppliance virtualAppliance =
        // getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

        try {
            LOGGER.debug("Allocating with force enterpise  soft limits : " + foreceEnterpriseSoftLimits);

            /*
             * Select a machine to allocate the virtual machine, Check limits, Check resources If
             * one of the above fail we cannot allocate the VirtualMachine
             */
            vmAllocatorService.allocateVirtualMachine(virtualMachine, vapp, foreceEnterpriseSoftLimits);
            LOGGER.debug("Allocated!");

            LOGGER.debug("Mapping the external volumes");
            // We need to map all attached volumes if any
            initiatorMappings(virtualMachine);
            LOGGER.debug("Mapping done!");
        } catch (APIException e) {
            traceApiExceptionVm(e, virtualMachine.getName());

            /*
             * Select a machine to allocate the virtual machine, Check limits, Check resources If
             * one of the above fail we cannot allocate the VirtualMachine It also perform the
             * resource recompute
             */
            if (virtualMachine.getHypervisor() != null) {
                vmAllocatorService.deallocateVirtualMachine(virtualMachine);
            }

            throw e;
        } catch (Exception ex) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
                    "virtualMachine.deploy", virtualMachine.getName());

            tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY, ex,
                    "virtualMachine.deploy", virtualMachine.getName());

            if (virtualMachine.getHypervisor() != null) {
                vmAllocatorService.deallocateVirtualMachine(virtualMachine);
            }

            addUnexpectedErrors(APIError.STATUS_INTERNAL_SERVER_ERROR);
            flushErrors();
        }
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public String sendDeploy(final VirtualMachine virtualMachine, final VirtualAppliance virtualAppliance) {

        try {
            VirtualMachineDescriptionBuilder vmDesc = jobCreator.toTarantinoDto(virtualMachine, virtualAppliance);

            LOGGER.info("Generating the link to the status! {}", virtualMachine.getId());
            return tarantino.deployVirtualMachine(virtualMachine, vmDesc);
        } catch (APIException e) {
            traceApiExceptionVm(e, virtualMachine.getName());

            /*
             * Select a machine to allocate the virtual machine, Check limits, Check resources If
             * one of the above fail we cannot allocate the VirtualMachine It also perform the
             * resource recompute
             */
            if (virtualMachine.getHypervisor() != null) {
                vmAllocatorService.deallocateVirtualMachine(virtualMachine);
            }

            throw e;
        } catch (Exception ex) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
                    "virtualMachine.deploy", virtualMachine.getName());

            tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY, ex,
                    "virtualMachine.deploy", virtualMachine.getName());

            vmAllocatorService.deallocateVirtualMachine(virtualMachine);

            addUnexpectedErrors(APIError.STATUS_INTERNAL_SERVER_ERROR);
            flushErrors();

            return null;
        }
    }

    private void traceApiExceptionVm(final APIException exception, final String vmName) {
        if (exception.getErrors().isEmpty()) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
                    exception.getMessage(), vmName);

            tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY, exception,
                    exception.getMessage(), vmName);
        } else {
            for (CommonError e : exception.getErrors()) {
                String msg = e.getCode() + " " + e.getMessage();
                tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
                        "virtualMachine.deploy.notEnoughResources", e.getCode(), vmName);

                tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
                        exception, msg, vmName);
            }

        }
    }

    /**
     * Checks the {@link RemoteService} of the {@link VirtualDatacenter} and logs if any error.
     * 
     * @param vdcId void
     */
    private void checkRemoteServicesByVirtualDatacenter(final Integer vdcId) {
        LOGGER.debug("Checking remote services for virtual datacenter {}", vdcId);
        VirtualDatacenter virtualDatacenter = vdcService.getVirtualDatacenter(vdcId);
        ErrorsDto rsErrors = checkRemoteServiceStatusByDatacenter(virtualDatacenter.getDatacenter().getId());
        if (!rsErrors.isEmpty()) {
            LOGGER.error("Some errors found while cheking remote services");
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
                    APIError.GENERIC_OPERATION_ERROR.getMessage());
            // For the Admin to know all errors
            traceAdminErrors(rsErrors, SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
                    "remoteServices.down", true);

            // There is no point in continue
            addNotFoundErrors(APIError.GENERIC_OPERATION_ERROR);
            flushErrors();
        }
        LOGGER.debug("Remote services Ok!");
    }

    /**
     * Properly documented in Premium.
     * 
     * @param virtualMachine void
     */
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void initiatorMappings(final VirtualMachine virtualMachine) {
        // PREMIUM
        LOGGER.debug("initiatorMappings community edition");
    }

    /**
     * /** Trace the Errors from a {@link ErrorsDto} to promote encapsulation.
     * 
     * @param rsErrors void
     * @param severityType severity.
     * @param componentType component.
     * @param eventType type.
     * @param msg message.
     * @param appendExceptionMsg should we append the exception message? the format would be
     *            <code>: error message</code> void
     */
    private void traceAdminErrors(final ErrorsDto rsErrors, final SeverityType severityType,
            final ComponentType componentType, final EventType eventType, final String msg,
            final boolean appendExceptionMsg) {
        for (ErrorDto e : rsErrors.getCollection()) {
            tracer.systemLog(severityType, componentType, eventType,
                    msg + (appendExceptionMsg ? ": " + e.getMessage() : ""));
        }
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public String undeployVirtualMachineHA(final Integer vmId, final Integer vappId, final Integer vdcId,
            final Boolean forceUndeploy, final VirtualMachineState originalState,
            final Hypervisor originalHypervisor) {
        LOGGER.debug("Starting the undeploy of the virtual machine {}", vmId);
        // We need to operate with concrete and this also check that the VirtualMachine belongs to
        // those VirtualAppliance and VirtualDatacenter
        VirtualMachine virtualMachine = getVirtualMachine(vdcId, vappId, vmId);

        if (!originalState.existsInHypervisor()) {
            return TaskResourceUtils.UNTRACEABLE_TASK;
        }
        LOGGER.debug("Check for permissions");
        // The user must have the proper permission
        userService.checkCurrentEnterpriseForPostMethods(virtualMachine.getEnterprise());
        LOGGER.debug("Permission granted");

        LOGGER.debug("Check remote services");
        // The remote services must be up for this Datacenter if we are to deploy
        checkRemoteServicesByVirtualDatacenter(vdcId);
        LOGGER.debug("Remote services are ok!");

        VirtualAppliance virtualAppliance = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

        if (!forceUndeploy && virtualMachine.isCaptured()) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_WILL_BE_DELETED);
            flushErrors();
        }

        try {
            LOGGER.debug("Check remote services");
            // The remote services must be up for this Datacenter if we are to deploy
            checkRemoteServicesByVirtualDatacenter(vdcId);
            LOGGER.debug("Remote services are ok!");

            // Tasks needs the definition of the virtual machine
            VirtualMachineDescriptionBuilder vmDesc = jobCreator.toTarantinoDto(virtualMachine, virtualAppliance,
                    true);

            String idAsyncTask = tarantino.undeployVirtualMachineHA(virtualMachine, vmDesc, originalState,
                    originalHypervisor);
            LOGGER.info("Undeploying of the virtual machine id {} in the virtual factory!", virtualMachine.getId());
            tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    "virtualMachine.enqueued", virtualMachine.getName());
            // For the Admin to know all errors
            tracer.systemLog(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    "virtualMachine.enqueuedTarantino", virtualMachine.getName());

            return idAsyncTask;

        } catch (Exception e) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    APIError.GENERIC_OPERATION_ERROR.getMessage());

            // For the Admin to know all errors
            tracer.systemLog(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    "virtualMachine.undeployError", e.toString(), virtualMachine.getName(), e.getMessage());
            LOGGER.error("Error undeploying setting the virtual machine to UNKNOWN virtual machine name {}: {}",
                    virtualMachine.getUuid(), e.toString());

            addUnexpectedErrors(APIError.STATUS_INTERNAL_SERVER_ERROR);
            flushErrors();
        }

        return null;
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public String undeployVirtualMachine(final Integer vmId, final Integer vappId, final Integer vdcId,
            final Boolean forceUndeploy, final VirtualMachineState originalState) {
        LOGGER.debug("Starting the undeploy of the virtual machine {}", vmId);
        // We need to operate with concrete and this also check that the VirtualMachine belongs to
        // those VirtualAppliance and VirtualDatacenter
        VirtualMachine virtualMachine = getVirtualMachine(vdcId, vappId, vmId);

        if (!originalState.existsInHypervisor()) {
            return TaskResourceUtils.UNTRACEABLE_TASK;
        }
        LOGGER.debug("Check for permissions");
        // The user must have the proper permission
        userService.checkCurrentEnterpriseForPostMethods(virtualMachine.getEnterprise());
        LOGGER.debug("Permission granted");

        LOGGER.debug("Check remote services");
        // The remote services must be up for this Datacenter if we are to deploy
        checkRemoteServicesByVirtualDatacenter(vdcId);
        LOGGER.debug("Remote services are ok!");

        VirtualAppliance virtualAppliance = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

        if (!forceUndeploy && virtualMachine.isCaptured()) {
            addConflictErrors(APIError.VIRTUAL_MACHINE_IMPORTED_WILL_BE_DELETED);
            flushErrors();
        }

        try {
            // Tasks needs the definition of the virtual machine
            VirtualMachineDescriptionBuilder vmDesc = jobCreator.toTarantinoDto(virtualMachine, virtualAppliance);

            String idAsyncTask = tarantino.undeployVirtualMachine(virtualMachine, vmDesc, originalState);
            LOGGER.info("Undeploying of the virtual machine id {} in the virtual factory!", virtualMachine.getId());
            tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    "virtualMachine.enqueued", virtualMachine.getName());
            // For the Admin to know all errors
            tracer.systemLog(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    "virtualMachine.enqueuedTarantino", virtualMachine.getName());

            return idAsyncTask;

        } catch (Exception e) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    APIError.GENERIC_OPERATION_ERROR.getMessage());

            // For the Admin to know all errors
            tracer.systemLog(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    "virtualMachine.undeployError", e.toString(), virtualMachine.getName(), e.getMessage());
            LOGGER.error("Error undeploying setting the virtual machine to UNKNOWN virtual machine name {}: {}",
                    virtualMachine.getUuid(), e.toString());

            addUnexpectedErrors(APIError.STATUS_INTERNAL_SERVER_ERROR);
            flushErrors();
        }

        return null;
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    private String undeployVirtualMachineAndDelete(final VirtualMachine virtualMachine, final Integer vappId,
            final Integer vdcId, final VirtualMachineState originalState) {
        LOGGER.debug("Check for permissions");
        // The user must have the proper permission
        userService.checkCurrentEnterpriseForPostMethods(virtualMachine.getEnterprise());
        LOGGER.debug("Permission granted");

        LOGGER.debug("Check remote services");
        // The remote services must be up for this Datacenter if we are to deploy
        checkRemoteServicesByVirtualDatacenter(vdcId);
        LOGGER.debug("Remote services are ok!");

        VirtualAppliance virtualAppliance = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

        try {

            // Tasks needs the definition of the virtual machine
            VirtualMachineDescriptionBuilder vmDesc = jobCreator.toTarantinoDto(virtualMachine, virtualAppliance);

            String idAsyncTask = tarantino.undeployVirtualMachineAndDelete(virtualMachine, vmDesc, originalState);
            LOGGER.info("Undeploying of the virtual machine id {} in the virtual factory!", virtualMachine.getId());
            tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    "virtualMachine.enqueued", virtualMachine.getName());
            // For the Admin to know all errors
            tracer.systemLog(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    "virtualMachine.enqueuedTarantino", virtualMachine.getName());

            return idAsyncTask;

        } catch (Exception e) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    APIError.GENERIC_OPERATION_ERROR.getMessage());

            // For the Admin to know all errors
            tracer.systemLog(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_UNDEPLOY,
                    "virtualMachine.undeployError", e.toString(), virtualMachine.getName(), e.getMessage());
            LOGGER.error("Error undeploying setting the virtual machine to UNKNOWN virtual machine name {}: {}",
                    virtualMachine.getUuid(), e.toString());

            addUnexpectedErrors(APIError.STATUS_INTERNAL_SERVER_ERROR);
            flushErrors();
        }

        return null;
    }

    /**
     * Instance a {@link VirtualMachine}, handles all instance types {@link SnapshotType}.
     * 
     * @param vmId {@link VirtualMachine} Id
     * @param vappId {@link VirtualAppliance} Id
     * @param vdcId {@link VirtualDatacenter} Id
     * @return The {@link Task} UUID
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public String instanceVirtualMachine(final Integer vmId, final Integer vappId, final Integer vdcId,
            final String instanceName, final VirtualMachineState originalState) {
        // Retrieve entities
        VirtualMachine virtualMachine = getVirtualMachine(vdcId, vappId, vmId);
        VirtualAppliance virtualApp = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

        LOGGER.debug("Starting the instance of the virtual machine {}", virtualMachine.getName());

        // Check if the operation is allowed and lock the virtual machine
        LOGGER.debug("Check for permissions");
        userService.checkCurrentEnterpriseForPostMethods(virtualMachine.getEnterprise());
        LOGGER.debug("Permission granted");

        try {
            SnapshotType type = SnapshotType.getSnapshotType(virtualMachine);
            String taskId = null;

            LOGGER.debug("Instance type for virtual machine {} is {}", virtualMachine.getName(), type.name());

            switch (type) {
            case FROM_ORIGINAL_DISK:
            case FROM_DISK_CONVERSION:
                taskId = tarantino.snapshotVirtualMachine(virtualApp, virtualMachine, originalState, instanceName);
                break;

            case FROM_IMPORTED_VIRTUALMACHINE:
                taskId = instanceImportedVirtualMachine(virtualApp, virtualMachine, originalState, instanceName);
                break;

            case FROM_STATEFUL_DISK:
                taskId = tarantino.instanceStatefulVirtualMachine(virtualApp, virtualMachine, originalState,
                        instanceName);
                break;
            }

            if (taskId != null) {
                LOGGER.debug("Instance of virtual machine {} enqueued!", virtualMachine.getName());
            }

            return taskId;
        } catch (APIException e) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_INSTANCE,
                    "virtualMachine.instanceFailed", virtualMachine.getName());

            tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_INSTANCE, e,
                    "virtualMachine.instanceFailed", virtualMachine.getName());

            throw e;
        } catch (Exception e) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_INSTANCE,
                    "virtualMachine.instanceFailed", virtualMachine.getName());

            tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_INSTANCE, e,
                    "virtualMachine.instanceFailed", virtualMachine.getName());

            addUnexpectedErrors(APIError.STATUS_INTERNAL_SERVER_ERROR);
            flushErrors();

            return null;
        }
    }

    /**
     * Performs an instance of type {@link SnapshotType#FROM_IMPORTED_VIRTUALMACHINE}
     * 
     * @param virtualAppliance {@link VirtualAppliance} where the {@link VirtualMachine} is
     *            contained.
     * @param virtualMachine The {@link VirtualMachine} to instance.
     * @param originalState The original {@link VirtualMachineState}.
     * @param instanceName The final name of the {@link VirtualMachineTemplate}
     * @return The {@link Task} UUID for progress tracking
     */
    private String instanceImportedVirtualMachine(final VirtualAppliance virtualAppliance,
            final VirtualMachine virtualMachine, final VirtualMachineState originalState,
            final String instanceName) {
        Datacenter datacenter = virtualMachine.getHypervisor().getMachine().getDatacenter();

        // Create the folder structure in the destination repository
        String ovfPath = amService.preBundleTemplate(datacenter.getId(), virtualAppliance.getEnterprise().getId(),
                instanceName);

        // Do the instance
        String snapshotPath = FilenameUtils.getFullPath(ovfPath);
        String snapshotFilename = FilenameUtils.getName(virtualMachine.getVirtualMachineTemplate().getPath());

        return tarantino.snapshotVirtualMachine(virtualAppliance, virtualMachine, originalState, instanceName,
                snapshotPath, snapshotFilename);
    }

    /**
     * Changes the state of the VirtualMachine to the state passed
     * 
     * @param vappId Virtual Appliance Id
     * @param vdcId VirtualDatacenter Id
     * @param state The state to which change
     * @throws Exception
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public String applyVirtualMachineState(final Integer vmId, final Integer vappId, final Integer vdcId,
            final VirtualMachineStateTransition stateTransition) {
        VirtualMachine virtualMachine = getVirtualMachine(vdcId, vappId, vmId);
        userService.checkCurrentEnterpriseForPostMethods(virtualMachine.getEnterprise());

        try {
            VirtualAppliance virtualAppliance = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

            VirtualMachineDescriptionBuilder machineDescriptionBuilder = jobCreator.toTarantinoDto(virtualMachine,
                    virtualAppliance);

            String location = tarantino.applyVirtualMachineState(virtualMachine, machineDescriptionBuilder,
                    stateTransition);
            LOGGER.info("Applying the new state of the virtual machine id {} in the virtual factory!",
                    virtualMachine.getId());
            tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_STATE,
                    "virtualMachine.applyVirtualMachineEnqueued", virtualMachine.getName());
            // For the Admin to know all errors
            tracer.systemLog(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_STATE,
                    "virtualMachine.applyVirtualMachineTarantinoEnqueued", virtualMachine.getName());

            // tasksService.
            // Here we add the url which contains the status
            return location;
        } catch (Exception ex) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
                    "virtualMachine.applyStateError", stateTransition.getEndState().name(),
                    virtualMachine.getName());

            tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY, ex,
                    "virtualMachine.applyStateError", stateTransition.getEndState().name(),
                    virtualMachine.getName());

            addUnexpectedErrors(APIError.STATUS_INTERNAL_SERVER_ERROR);
            flushErrors();

            return null;
        }
    }

    /**
     * Reset the VirtualMachine to the state passed
     * 
     * @param vappId Virtual Appliance Id
     * @param vdcId VirtualDatacenter Id
     * @param state The state to which change
     * @throws Exception
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public String resetVirtualMachine(final Integer vmId, final Integer vappId, final Integer vdcId,
            final VirtualMachineStateTransition state) {
        VirtualMachine virtualMachine = getVirtualMachine(vdcId, vappId, vmId);
        userService.checkCurrentEnterpriseForPostMethods(virtualMachine.getEnterprise());

        VirtualAppliance virtualAppliance = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

        try {
            VirtualMachineDescriptionBuilder machineDescriptionBuilder = jobCreator.toTarantinoDto(virtualMachine,
                    virtualAppliance);

            String location = tarantino.applyVirtualMachineState(virtualMachine, machineDescriptionBuilder, state);
            LOGGER.info("Applying the reset of the virtual machine id {} in the virtual factory!",
                    virtualMachine.getId());
            tracer.log(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_STATE,
                    "virtualMachine.resetVirtualMachineEnqueued", virtualMachine.getName());
            // For the Admin to know all errors
            tracer.systemLog(SeverityType.INFO, ComponentType.VIRTUAL_MACHINE, EventType.VM_STATE,
                    "virtualMachine.resetVirtualMachineTarantinoEnqueued");

            // tasksService.
            // Here we add the url which contains the status
            return location;
        } catch (Exception ex) {
            tracer.log(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY,
                    "virtualMachine.resetVirtualMachineError", virtualMachine.getName());

            tracer.systemError(SeverityType.CRITICAL, ComponentType.VIRTUAL_MACHINE, EventType.VM_DEPLOY, ex,
                    "virtualMachine.resetVirtualMachineError", virtualMachine.getName());

            addUnexpectedErrors(APIError.STATUS_INTERNAL_SERVER_ERROR);
            flushErrors();

            return null;
        }
    }

    /**
     * Checks one by one all {@link RemoteService} associated with the @{link Datacenter}.
     * 
     * @param datacenterId
     * @return ErrorsDto
     */
    public ErrorsDto checkRemoteServiceStatusByDatacenter(final Integer datacenterId) {

        List<RemoteService> remoteServicesByDatacenter = infRep
                .findRemoteServicesByDatacenter(infRep.findById(datacenterId));

        ErrorsDto errors = new ErrorsDto();
        for (RemoteService r : remoteServicesByDatacenter) {
            ErrorsDto checkRemoteServiceStatus = remoteServiceService.checkRemoteServiceStatus(r.getDatacenter(),
                    r.getType(), r.getUri());
            errors.addAll(checkRemoteServiceStatus);
        }

        return errors;
    }

    @Transactional(readOnly = true, propagation = Propagation.REQUIRED)
    public NodeVirtualImage getNodeVirtualImage(final Integer vdcId, final Integer vappId, final Integer vmId) {
        VirtualMachine vm = repo.findVirtualMachineById(vmId);
        VirtualAppliance vapp = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);
        VirtualDatacenter vdc = vdcRep.findById(vdcId);

        if (vm == null || !isAssignedTo(vmId, vapp.getId())) {
            LOGGER.error("Error retrieving the virtual machine: {} does not exist", vmId);
            addNotFoundErrors(APIError.NON_EXISTENT_VIRTUALMACHINE);
            flushErrors();
        }
        LOGGER.debug("virtual machine {} found", vmId);

        NodeVirtualImage nodeVirtualImage = findNodeVirtualImage(vm);
        if (nodeVirtualImage == null) {
            LOGGER.error("Error retrieving the node virtual image of machine: {} does not exist", vmId);
            addNotFoundErrors(APIError.NODE_VIRTUAL_MACHINE_IMAGE_NOT_EXISTS);
            flushErrors();
        }

        // if the ips are external, we need to set the limitID in order to return the
        // proper info.
        for (IpPoolManagement ip : vm.getIps()) {
            if (ip.getVlanNetwork().getEnterprise() != null) {
                // needed for REST links.
                DatacenterLimits dl = infRep.findDatacenterLimits(ip.getVlanNetwork().getEnterprise(),
                        vdc.getDatacenter());
                ip.getVlanNetwork().setLimitId(dl.getId());
            }
        }

        return nodeVirtualImage;
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void deleteNodeVirtualImage(final NodeVirtualImage nvi) {
        repo.deleteNodeVirtualImage(nvi);
    }

    @Transactional(readOnly = true, propagation = Propagation.REQUIRED)
    public NodeVirtualImage findNodeVirtualImage(final VirtualMachine vm) {
        return repo.findNodeVirtualImageByVm(vm);
    }

    @Transactional(readOnly = true, propagation = Propagation.REQUIRED)
    public List<NodeVirtualImage> getNodeVirtualImages(final Integer vdcId, final Integer vappId) {
        VirtualAppliance vapp = getVirtualApplianceAndCheckVirtualDatacenter(vdcId, vappId);

        if (vapp == null) {
            LOGGER.error("Error retrieving the virtual appliance: {} does not exist", vappId);
            addNotFoundErrors(APIError.NON_EXISTENT_VIRTUALAPPLIANCE);
            flushErrors();
        }
        LOGGER.debug("virtual appliance {} found", vappId);

        return vapp.getNodes();
    }

    /**
     * Builds a {@link VirtualMachine} object from {@link VirtualMachineDto} object.
     * <p>
     * Allocated attributes (hypervisor/datastore) not set
     * 
     * @param dto transfer input object
     * @return output pojo object.
     */
    protected VirtualMachine buildVirtualMachineFromDto(final VirtualDatacenter vdc, final VirtualAppliance vapp,
            final VirtualMachineDto dto) {
        VirtualMachine vm = null;
        try {
            vm = ModelTransformer.persistenceFromTransport(VirtualMachine.class, dto);
        } catch (Exception e) {
            addUnexpectedErrors(APIError.STATUS_BAD_REQUEST);
            flushErrors();
        }

        // get the machine template and set the values of the virtual machine
        // according with the template and its overriden values.
        vm.setVirtualMachineTemplate(getVirtualMachineTemplateFromDto(dto));
        setVirtualMachineTemplateRequirementsIfNotAlreadyDefined(vm, vm.getVirtualMachineTemplate());

        // Get the resources from dto
        List<IpPoolManagement> ips = getNICsFromDto(vdc, dto);
        List<DiskManagement> disks = getHardDisksFromDto(vdc, dto);

        // Set the values for the virtualmachine
        vm.setIps(ips);
        vm.setDisks(disks);

        vm.setPassword(dto.getPassword());
        return vm;
    }

    /**
     * Allocate the POJOs of {@link DiskManagement} and {@link VolumeManagement} for the current
     * virtual datacenter, virtual appliance, virtual machine and the attachment order.
     * 
     * @param vapp {@link VirtualAppliance} object where the resource will be allocated.
     * @param vm {@link VirtualMachine} object where the resource will be allocated.
     * @param resources the list of resources that will be allocated.
     */
    protected void allocateNewStorages(final VirtualAppliance vapp, final VirtualMachine vm,
            final List<? extends RasdManagement> resources, final List<Integer> blackList) {
        // When we allocate a resource, we need to set a unique attachment order for each one.
        // The function #getStorageFreeAttachmentSlot do the work. However, it only takes
        // the information from database, and we need to have a list of integers of the
        // already assigned slots before in the loop. 'blackList' stores them.

        for (RasdManagement resource : resources) {
            // Stateful volumes should not be trated as additional VM resources
            if (!isStatefulVolume(resource)) {
                boolean allocated = allocateResource(vm, vapp, resource, getFreeAttachmentSlot(blackList));
                if (allocated) {
                    // In Hyper-v only 2 attached volumes are allowed
                    if (vm.getHypervisor() != null && vm.getHypervisor().getType() == HypervisorType.HYPERV_301
                            && blackList.size() >= 2) {
                        addConflictErrors(APIError.VOLUME_TOO_MUCH_ATTACHMENTS);
                        flushErrors();
                    }

                    // if it is new allocated, we set the integer into the 'blacklisted' list.
                    Integer blacklisted = resource.getSequence();
                    blackList.add(blacklisted);

                    if (resource instanceof DiskManagement) {
                        vdcRep.updateDisk((DiskManagement) resource);
                        tracer.log(SeverityType.INFO, ComponentType.STORAGE_DEVICE, EventType.HARD_DISK_ASSIGN,
                                "hardDisk.assigned", resource.getRasd().getLimit(), vm.getName());
                    } else {
                        storageRep.updateVolume((VolumeManagement) resource);
                        tracer.log(SeverityType.INFO, ComponentType.VOLUME, EventType.VOLUME_ATTACH,
                                "volume.attached", resource.getRasd().getElementName(),
                                resource.getRasd().getLimit(), vm.getName());
                    }
                }
            }
        }
    }

    private boolean isStatefulVolume(final RasdManagement resource) {
        return resource instanceof VolumeManagement && ((VolumeManagement) resource).isStateful();
    }

    /**
     * Allocate the POJOs of {@link IpPoolManagement} for the current virtual datacenter, virtual
     * appliance, virtual machine and the attachment order.
     * 
     * @param vapp {@link VirtualAppliance} object where the resource will be allocated.
     * @param vm {@link VirtualMachine} object where the resource will be allocated.
     * @param resources the list of resources that will be allocated.
     */
    protected void allocateNewNICs(final VirtualAppliance vapp, final VirtualMachine vm,
            final List<IpPoolManagement> resources, final List<Integer> blackList) {
        // When we allocate a resource, we need to set a unique attachment order for each one.
        // The function #getStorageFreeAttachmentSlot do the work. However, it only takes
        // the information from database, and we need to have a list of integers of the
        // already assigned slots before in the loop. 'blackList' stores them.
        List<IpPoolManagement> ipPoolList = removeRepetedResources(resources);

        for (IpPoolManagement ip : resources) {
            boolean allocated = allocateResource(vm, vapp, ip, getFreeAttachmentSlot(blackList));
            if (allocated) {
                if (ip.getVlanNetwork().getType().equals(NetworkType.EXTERNAL)
                        || ip.getVlanNetwork().getType().equals(NetworkType.UNMANAGED)) {
                    String mac = IPNetworkRang
                            .requestRandomMacAddress(vapp.getVirtualDatacenter().getHypervisorType());
                    String name = mac.replace(":", "") + "_host";
                    ip.setMac(mac);
                    ip.setName(name);
                    ip.setVirtualDatacenter(vapp.getVirtualDatacenter());
                }

                Rasd rasd = NetworkService.createRasdEntity(vm, ip);
                vdcRep.insertRasd(rasd);

                ip.setRasd(rasd);
                vdcRep.updateIpManagement(ip);

                // if it is new allocated, we set the integer into the 'blacklisted' list.
                blackList.add(ip.getSequence());
                tracer.log(SeverityType.INFO, ComponentType.NETWORK, EventType.NIC_ASSIGNED_VIRTUAL_MACHINE,
                        "nic.attached", vm.getName(), ip.getIp(), ip.getVlanNetwork().getName());
            }
        }
    }

    private List<IpPoolManagement> removeRepetedResources(final List<IpPoolManagement> resources) {
        Map<String, IpPoolManagement> ipMap = new HashMap<String, IpPoolManagement>();

        int numberOfNewUnmanagedNICs = 0;
        for (IpPoolManagement ip : resources) {
            String mac = ip.getMac();
            if (mac.equalsIgnoreCase("?")) {
                mac = mac + numberOfNewUnmanagedNICs;
                numberOfNewUnmanagedNICs++;
            }
            ipMap.put(mac, ip);
        }

        if (resources.size() > ipMap.size()) {
            String errorCode = APIError.RESOURCES_ALREADY_ASSIGNED.getCode();
            String message = APIError.RESOURCES_ALREADY_ASSIGNED.getMessage();
            CommonError error = new CommonError(errorCode, message);
            addNotFoundErrors(error);
            flushErrors();
        }

        return new ArrayList<IpPoolManagement>(ipMap.values());
    }

    /**
     * Dellocate the NICs of the {@link VirtualMachine} 'oldVm' parameter that are not anymore in
     * the new configuration 'newVm' parameter. Return the list of 'attachment orders' needed to
     * allocate
     * 
     * @param oldVm {@link VirtualMachine} with the 'old' configuration.
     * @param newVm {@link VirtualMachine} with the 'new' configuration.
     * @return the list of attachment order still in oldVm
     */
    protected List<Integer> dellocateOldNICs(final VirtualMachine oldVm, final VirtualMachine newVm) {
        List<Integer> oldNicsAttachments = new ArrayList<Integer>();

        // dellocate the old ips that are not in the new virtual machine.
        for (IpPoolManagement ip : oldVm.getIps()) {
            if (!resourceIntoNewList(ip, newVm.getIps())) {
                // if the machine is NOT_ALLOCATED, the values here are definitive,
                // otherwise, it will be deleted in the handler
                if (oldVm.getState() == VirtualMachineState.NOT_ALLOCATED) {
                    if (ip.getVlanNetwork().getType().equals(NetworkType.UNMANAGED)) {
                        vdcRep.deleteRasd(ip.getRasd());
                        vdcRep.deleteIpPoolManagement(ip);
                    } else if (ip.getVlanNetwork().getType().equals(NetworkType.EXTERNAL)) {
                        ip.setMac(null);
                        ip.setName(null);
                        ip.setVirtualDatacenter(null);
                        ip.detach();
                        vdcRep.deleteRasd(ip.getRasd());
                        vdcRep.updateIpManagement(ip);
                    } else {
                        ip.detach();
                        vdcRep.deleteRasd(ip.getRasd());
                        vdcRep.updateIpManagement(ip);
                    }
                } else {
                    ip.detach();
                    if (ip.getVlanNetwork().getType().equals(NetworkType.EXTERNAL)) {
                        ip.setMac(null);
                        ip.setName(null);
                        ip.setVirtualDatacenter(null);
                    }
                    vdcRep.updateIpManagement(ip);
                    tracer.log(SeverityType.INFO, ComponentType.NETWORK, EventType.NIC_ASSIGNED_VIRTUAL_MACHINE,
                            "nic.released", oldVm.getName(), ip.getIp(), ip.getVlanNetwork().getName());
                }

                // if the dellocated ip is the one with the default configuration,
                // and it is the last one, set the default configuration to null
                if (ip.itHasTheDefaultConfiguration(oldVm)
                        && vdcRep.findIpsWithConfigurationIdInVirtualMachine(oldVm).size() == 0) {
                    oldVm.setNetworkConfiguration(null);
                }

            } else {
                oldNicsAttachments.add(ip.getSequence());
            }
        }
        return oldNicsAttachments;
    }

    /**
     * Dellocate the Disks of the {@link VirtualMachine} 'oldVm' parameter that are not anymore in
     * the new configuration 'newVm' parameter. Return the list of 'attachment orders' needed to
     * allocate after that.
     * 
     * @param oldVm {@link VirtualMachine} with the 'old' configuration.
     * @param newVm {@link VirtualMachine} with the 'new' configuration.
     * @return the list of attachment order still in oldVm
     */
    protected List<Integer> dellocateOldDisks(final VirtualMachine oldVm, final VirtualMachine newVm) {
        List<Integer> oldDisksAttachments = new ArrayList<Integer>();

        // dellocate the old disks that are not in the new virtual machine.
        for (DiskManagement disk : oldVm.getDisks()) {
            if (!resourceIntoNewList(disk, newVm.getDisks())) {
                // if the machine is NOT_ALLOCATED, the values here are definitive,
                // otherwise, it will be deleted in the handler
                if (oldVm.getState() == VirtualMachineState.NOT_ALLOCATED) {
                    vdcRep.deleteRasd(disk.getRasd());
                    rasdDao.remove(disk);
                    tracer.log(SeverityType.INFO, ComponentType.STORAGE_DEVICE, EventType.HARD_DISK_ASSIGN,
                            "hardDisk.released", disk.getSizeInMb(), oldVm.getName());
                } else {
                    disk.detach();
                    vdcRep.updateDisk(disk);
                }
            } else {
                oldDisksAttachments.add(disk.getSequence());
            }
        }
        return oldDisksAttachments;
    }

    /**
     * Dellocate the Volumes of the {@link VirtualMachine} 'oldVm' parameter that are not anymore in
     * the new configuration 'newVm' parameter. Return the list of 'attachment orders' needed to
     * allocate after that.
     * 
     * @param oldVm {@link VirtualMachine} with the 'old' configuration.
     * @param newVm {@link VirtualMachine} with the 'new' configuration.
     * @return the list of attachment order still in oldVm
     */
    protected List<Integer> dellocateOldVolumes(final VirtualMachine oldVm, final VirtualMachine newVm) {
        List<Integer> oldVolumesAttachments = new ArrayList<Integer>();

        // dellocate the old disks that are not in the new virtual machine.
        for (VolumeManagement vol : oldVm.getVolumes()) {
            // Stateful volumes should not be trated as additional VM resources
            if (!vol.isStateful()) {
                if (!resourceIntoNewList(vol, newVm.getVolumes())) {
                    if (!vol.isAttached()) {
                        addConflictErrors(APIError.VOLUME_NOT_ATTACHED);
                        flushErrors();
                    }
                    vol.detach();
                    storageRep.updateVolume(vol);
                    tracer.log(SeverityType.INFO, ComponentType.VOLUME, EventType.VOLUME_DETACH, "volume.detached",
                            vol.getName(), vol.getSizeInMB(), oldVm.getName());
                } else {
                    oldVolumesAttachments.add(vol.getSequence());
                }
            }
        }
        return oldVolumesAttachments;
    }

    /**
     * Get the next free attachment slot to be used to attach a disk, volume, or nic to the virtual
     * machine.
     * 
     * @param vm The virtual machine where the disk will be attached.
     * @return The free slot to use.
     */
    protected int getFreeAttachmentSlot(final List<Integer> blackList) {
        // Find the first free slot
        int i = RasdManagement.FIRST_ATTACHMENT_SEQUENCE;
        while (true) {
            if (!blackList.contains(i)) {
                return i; // Found gap
            }

            i++;
        }
    }

    /**
     * Validates the given object with links to a hard disk and returns the referenced hard disk.
     * 
     * @param links The links to validate the hard disk.
     * @param expectedVirtualDatacenter The expected virtual datacenter to be found in the link.
     * @return The list of {@link DiskManagement} referenced by the link.
     * @throws Exception If the link is not valid.
     */
    public List<DiskManagement> getHardDisksFromDto(final VirtualDatacenter vdc,
            final SingleResourceTransportDto dto) {
        List<DiskManagement> disks = new LinkedList<DiskManagement>();

        // Validate and load each volume from the link list
        for (RESTLink link : dto.searchLinks(DiskResource.DISK)) {
            String path = buildPath(VirtualDatacentersResource.VIRTUAL_DATACENTERS_PATH,
                    VirtualDatacenterResource.VIRTUAL_DATACENTER_PARAM, DisksResource.DISKS_PATH,
                    DiskResource.DISK_PARAM);

            MultivaluedMap<String, String> pathValues = URIResolver.resolveFromURI(path, link.getHref());

            // URI needs to have an identifier to a VDC, and another one to the volume
            if (pathValues == null || !pathValues.containsKey(VirtualDatacenterResource.VIRTUAL_DATACENTER)
                    || !pathValues.containsKey(DiskResource.DISK)) {
                throw new BadRequestException(APIError.HD_ATTACH_INVALID_LINK);
            }

            // Volume provided in link must belong to the same virtual datacenter
            Integer vdcId = Integer.parseInt(pathValues.getFirst(VirtualDatacenterResource.VIRTUAL_DATACENTER));
            if (!vdcId.equals(vdc.getId())) {
                throw new BadRequestException(APIError.HD_ATTACH_INVALID_VDC_LINK);
            }

            Integer diskId = Integer.parseInt(pathValues.getFirst(DiskResource.DISK));

            DiskManagement disk = vdcRep.findHardDiskByVirtualDatacenter(vdc, diskId);
            if (disk == null) {
                String errorCode = APIError.HD_NON_EXISTENT_HARD_DISK.getCode();
                String message = APIError.HD_NON_EXISTENT_HARD_DISK.getMessage() + ": Hard Disk id " + diskId;
                CommonError error = new CommonError(errorCode, message);
                addNotFoundErrors(error);
            } else {
                disks.add(disk);
            }
        }

        // Throw the exception with all the disks we have not found.
        flushErrors();

        return disks;
    }

    /**
     * Validates the given object with links to a NIC and returns the referenced list of
     * {@link IpPoolManagement}.
     * 
     * @param links The links to validate the hard disk.
     * @param expectedVirtualDatacenter The expected virtual datacenter to be found in the link.
     * @return The list of {@link IpPoolManagement} referenced by the link.
     * @throws Exception If the link is not valid.
     */
    public List<IpPoolManagement> getNICsFromDto(final VirtualDatacenter vdc,
            final SingleResourceTransportDto dto) {
        List<IpPoolManagement> ips = new LinkedList<IpPoolManagement>();

        // Validate and load each volume from the link list
        for (RESTLink link : dto.searchLinks(PrivateNetworkResource.PRIVATE_IP)) {
            // Parse the URI with the expected parameters and extract the identifier values.
            String buildPath = buildPath(VirtualDatacentersResource.VIRTUAL_DATACENTERS_PATH,
                    VirtualDatacenterResource.VIRTUAL_DATACENTER_PARAM,
                    PrivateNetworksResource.PRIVATE_NETWORKS_PATH, PrivateNetworkResource.PRIVATE_NETWORK_PARAM,
                    IpAddressesResource.IP_ADDRESSES, IpAddressesResource.IP_ADDRESS_PARAM);
            MultivaluedMap<String, String> ipsValues = URIResolver.resolveFromURI(buildPath, link.getHref());

            // URI needs to have an identifier to a VDC, another one to a Private Network
            // and another one to Private IP
            if (ipsValues == null || !ipsValues.containsKey(VirtualDatacenterResource.VIRTUAL_DATACENTER)
                    || !ipsValues.containsKey(PrivateNetworkResource.PRIVATE_NETWORK)
                    || !ipsValues.containsKey(IpAddressesResource.IP_ADDRESS)) {
                throw new BadRequestException(APIError.VLANS_PRIVATE_IP_INVALID_LINK);
            }

            // Private IP must belong to the same Virtual Datacenter where the Virtual Machine
            // belongs to.
            Integer idVdc = Integer.parseInt(ipsValues.getFirst(VirtualDatacenterResource.VIRTUAL_DATACENTER));
            if (!idVdc.equals(vdc.getId())) {
                throw new BadRequestException(APIError.VLANS_IP_LINK_INVALID_VDC);
            }

            // Extract the vlanId and ipId values to execute the association.
            Integer vlanId = Integer.parseInt(ipsValues.getFirst(PrivateNetworkResource.PRIVATE_NETWORK));
            Integer ipId = Integer.parseInt(ipsValues.getFirst(IpAddressesResource.IP_ADDRESS));
            VLANNetwork vlan = vdcRep.findVlanByVirtualDatacenterId(vdc, vlanId);
            if (vlan == null) {
                String errorCode = APIError.VLANS_NON_EXISTENT_VIRTUAL_NETWORK.getCode();
                String message = APIError.VLANS_NON_EXISTENT_VIRTUAL_NETWORK.getMessage() + ": Vlan id " + vlanId;
                CommonError error = new CommonError(errorCode, message);
                addNotFoundErrors(error);
                continue;
            }
            IpPoolManagement ip = vdcRep.findIp(vlan, ipId);
            if (ip == null) {
                String errorCode = APIError.NON_EXISTENT_IP.getCode();
                String message = APIError.NON_EXISTENT_IP.getMessage() + ": Vlan id " + vlan.getId();
                CommonError error = new CommonError(errorCode, message);
                addNotFoundErrors(error);
                continue;
            }

            ips.add(ip);
        }

        // Throw the exception with all the disks we have not found.
        flushErrors();

        return ips;
    }

    /**
     * Extracts the proper network configuration from the "network_configuration" link.
     * 
     * @param vapp VirtualAppliance We need it to check it the link os correct.
     * @param newvm VirtualMachine new. We need to check if the configuration is correct for the
     *            current virtualmachine's NICs.
     * @param configurationRef reference to configuration.
     * @return the {@link NetworkConfiguration} object to set to VirtualMachine.
     */
    public NetworkConfiguration getNetworkConfigurationFromDto(final VirtualAppliance vapp,
            final VirtualMachine newvm, final SingleResourceTransportDto dto) {
        RESTLink link = dto.searchLink(VirtualMachineNetworkConfigurationResource.DEFAULT_CONFIGURATION);
        if (link == null) {
            // we disable the default network configuration.
            return null;
        }
        String networkConfigurationTemplate = buildPath(VirtualDatacentersResource.VIRTUAL_DATACENTERS_PATH,
                VirtualDatacenterResource.VIRTUAL_DATACENTER_PARAM,
                VirtualAppliancesResource.VIRTUAL_APPLIANCES_PATH, VirtualApplianceResource.VIRTUAL_APPLIANCE_PARAM,
                VirtualMachinesResource.VIRTUAL_MACHINES_PATH, VirtualMachineResource.VIRTUAL_MACHINE_PARAM,
                VirtualMachineNetworkConfigurationResource.NETWORK,
                VirtualMachineNetworkConfigurationResource.CONFIGURATION_PATH,
                VirtualMachineNetworkConfigurationResource.CONFIGURATION_PARAM);

        MultivaluedMap<String, String> configurationValues = URIResolver
                .resolveFromURI(networkConfigurationTemplate, link.getHref());

        // URI needs to have an identifier to a VDC, another one to a Private Network
        // and another one to Private IP
        if (configurationValues == null
                || !configurationValues.containsKey(VirtualDatacenterResource.VIRTUAL_DATACENTER)
                || !configurationValues.containsKey(VirtualApplianceResource.VIRTUAL_APPLIANCE)
                || !configurationValues.containsKey(VirtualMachineResource.VIRTUAL_MACHINE)
                || !configurationValues.containsKey(VirtualMachineNetworkConfigurationResource.CONFIGURATION)) {
            throw new BadRequestException(APIError.NETWORK_INVALID_CONFIGURATION_LINK);
        }

        // Get the identifiers of the link
        Integer vdcId = Integer
                .parseInt(configurationValues.getFirst(VirtualDatacenterResource.VIRTUAL_DATACENTER));
        Integer vappId = Integer.parseInt(configurationValues.getFirst(VirtualApplianceResource.VIRTUAL_APPLIANCE));
        Integer vmId = Integer.parseInt(configurationValues.getFirst(VirtualMachineResource.VIRTUAL_MACHINE));
        Integer configId = Integer
                .parseInt(configurationValues.getFirst(VirtualMachineNetworkConfigurationResource.CONFIGURATION));

        // Check the identifiers
        if (!vdcId.equals(vapp.getVirtualDatacenter().getId())) {
            throw new BadRequestException(APIError.NETWORK_LINK_INVALID_VDC);
        }
        if (!vappId.equals(vapp.getId())) {
            throw new BadRequestException(APIError.NETWORK_LINK_INVALID_VAPP);
        }
        if (!vmId.equals(newvm.getTemporal())) // it is the new resource, the id it is in the
        // 'temporal'
        {
            throw new BadRequestException(APIError.NETWORK_LINK_INVALID_VM);
        }

        List<IpPoolManagement> ips = newvm.getIps();
        for (IpPoolManagement ip : ips) {
            if (ip.getVlanNetwork().getConfiguration().getId().equals(configId)) {
                return ip.getVlanNetwork().getConfiguration();
            }
        }

        // if we have reached this point, it means the configuratin id is not valid
        throw new BadRequestException(APIError.NETWORK_LINK_INVALID_CONFIG);
    }

    /**
     * Get the object {@link VirtualMachineTemplate} from the input dto.
     * 
     * @param dto the object that should have the link to a virtual machine template.
     * @return the found {@link virtualMachineTemplateObject}
     */
    public VirtualMachineTemplate getVirtualMachineTemplateFromDto(final SingleResourceTransportDto dto) {
        String vmTemplatePath = buildPath(EnterprisesResource.ENTERPRISES_PATH, EnterpriseResource.ENTERPRISE_PARAM, //
                DatacenterRepositoriesResource.DATACENTER_REPOSITORIES_PATH,
                DatacenterRepositoryResource.DATACENTER_REPOSITORY_PARAM,
                VirtualMachineTemplatesResource.VIRTUAL_MACHINE_TEMPLATES_PATH,
                VirtualMachineTemplateResource.VIRTUAL_MACHINE_TEMPLATE_PARAM);

        RESTLink link = dto.searchLink(VIRTUAL_MACHINE_TEMPLATE);

        if (link == null) {
            addValidationErrors(APIError.LINKS_VIRTUAL_MACHINE_TEMPLATE_NOT_FOUND);
            flushErrors();
        }

        Integer entId = null;
        Integer dcId = null;
        Integer templId = null;
        try {
            MultivaluedMap<String, String> pathValues = URIResolver.resolveFromURI(vmTemplatePath, link.getHref());

            // URI needs to have an identifier to a ENTERPRISE, another to a DATACENTER_REPOSITORY
            // and another one to the TEMPLATE
            if (pathValues == null || !pathValues.containsKey(EnterpriseResource.ENTERPRISE)
                    || !pathValues.containsKey(DatacenterRepositoryResource.DATACENTER_REPOSITORY)
                    || !pathValues.containsKey(VirtualMachineTemplateResource.VIRTUAL_MACHINE_TEMPLATE)) {
                throw new BadRequestException(APIError.LINKS_VIRTUAL_MACHINE_TEMPLATE_INVALID_URI);
            }

            entId = Integer.valueOf(pathValues.getFirst(EnterpriseResource.ENTERPRISE));
            dcId = Integer.valueOf(pathValues.getFirst(DatacenterRepositoryResource.DATACENTER_REPOSITORY));
            templId = Integer.valueOf(pathValues.getFirst(VirtualMachineTemplateResource.VIRTUAL_MACHINE_TEMPLATE));
        } catch (Exception e) {
            // unhandled exception parsing the uri
            addValidationErrors(APIError.LINKS_INVALID_LINK);
            flushErrors();
        }

        return getVirtualMachineTemplateAndValidateEnterpriseAndDatacenter(entId, dcId, templId);
    }

    /**
     * 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 = vdcRep.findById(vdcId);
        if (vdc == null) {
            addNotFoundErrors(APIError.NON_EXISTENT_VIRTUAL_DATACENTER);
            flushErrors();
        }
        return vdc;
    }

    /**
     * Creates the backup object with a virtual machine.
     * 
     * @param vm original {@link VirtualMachine} obj.
     * @return the backup object.
     */
    public VirtualMachine duplicateVirtualMachineObject(final VirtualMachine vm) {
        VirtualMachine backUpVm = createBackUpMachine(vm);
        // set the 'real' name
        backUpVm.setName(vm.getName());
        createBackUpResources(vm, backUpVm);
        return backUpVm;
    }

    /**
     * Copy of the Virtual Machine object.
     * 
     * @param vm {@link VirtualMachine} object original
     * @return the copy of the input param.
     */
    protected VirtualMachine createBackUpMachine(final VirtualMachine vm) {
        VirtualMachine tmp = new VirtualMachine();

        // backup virtual machine properties
        tmp.setCpu(vm.getCpu());
        tmp.setDatastore(vm.getDatastore());
        tmp.setDescription(vm.getDescription());
        tmp.setEnterprise(vm.getEnterprise());
        tmp.setHdInBytes(vm.getHdInBytes());
        tmp.setHighDisponibility(vm.getHighDisponibility());
        tmp.setHypervisor(vm.getHypervisor());
        tmp.setIdType(vm.getIdType());
        tmp.setName("tmp_" + vm.getName());
        tmp.setPassword(vm.getPassword());
        tmp.setRam(vm.getRam());
        tmp.setState(VirtualMachineState.LOCKED);
        tmp.setSubState(vm.getSubState());
        tmp.setUser(vm.getUser());
        tmp.setUuid(vm.getUuid());
        tmp.setVdrpIP(vm.getVdrpIP());
        tmp.setVdrpPort(vm.getVdrpPort());
        tmp.setVirtualImageConversion(vm.getVirtualImageConversion());
        tmp.setVirtualMachineTemplate(vm.getVirtualMachineTemplate());
        tmp.setNetworkConfiguration(vm.getNetworkConfiguration());
        tmp.setTemporal(vm.getId());

        return tmp;
    }

    /**
     * Copy the resources of the Virtual Machine.
     * 
     * @param vm original virtualmachine
     * @param tmp backup virtualmachine where the resources will be copied.
     */
    protected void createBackUpResources(final VirtualMachine vm, final VirtualMachine tmp) {
        // Backup disks
        List<DiskManagement> disksTemp = new ArrayList<DiskManagement>();
        for (DiskManagement disk : vm.getDisks()) {
            DiskManagement disktmp = new DiskManagement();
            disktmp.setSequence(disk.getSequence());
            disktmp.setDatastore(disk.getDatastore());
            disktmp.setTemporal(disk.getId());
            disktmp.setIdResourceType(disk.getIdResourceType());
            disktmp.setRasd(disk.getRasd());
            disktmp.setReadOnly(disk.getReadOnly());
            disktmp.setSizeInMb(disk.getSizeInMb());
            disktmp.setVirtualAppliance(disk.getVirtualAppliance());
            disktmp.setVirtualDatacenter(disk.getVirtualDatacenter());
            disktmp.setVirtualMachine(tmp);

            disksTemp.add(disktmp);
        }
        tmp.setDisks(disksTemp);

        // Backup NICs
        List<IpPoolManagement> ipsTemp = new ArrayList<IpPoolManagement>();
        for (IpPoolManagement ip : vm.getIps()) {
            IpPoolManagement ipTmp = new IpPoolManagement();
            ipTmp.setSequence(ip.getSequence());
            ipTmp.setTemporal(ip.getId());
            ipTmp.setIdResourceType(ip.getIdResourceType());
            ipTmp.setRasd(ip.getRasd());
            ipTmp.setVirtualAppliance(ip.getVirtualAppliance());
            ipTmp.setVirtualDatacenter(ip.getVirtualDatacenter());
            ipTmp.setVirtualMachine(tmp);

            ipTmp.setName(ip.getName());
            ipTmp.setVlanNetwork(ip.getVlanNetwork());
            ipTmp.setMac(ip.getMac());
            ipTmp.setAvailable(ip.getAvailable());
            ipTmp.setNetworkName(ip.getNetworkName());
            ipTmp.setQuarantine(ip.getQuarantine());
            ipTmp.setIp(ip.getIp());

            ipsTemp.add(ipTmp);
        }
        tmp.setIps(ipsTemp);

        // Backup Volumes
        List<VolumeManagement> volsTemp = new ArrayList<VolumeManagement>();
        for (VolumeManagement vol : vm.getVolumes()) {
            // Stateful volumes should not be trated as VM attached resources
            if (!vol.isStateful()) {
                VolumeManagement volTmp = new VolumeManagement();
                volTmp.setSequence(vol.getSequence());
                volTmp.setTemporal(vol.getId());
                volTmp.setIdResourceType(vol.getIdResourceType());
                volTmp.setRasd(vol.getRasd());
                volTmp.setVirtualAppliance(vol.getVirtualAppliance());
                volTmp.setVirtualDatacenter(vol.getVirtualDatacenter());
                volTmp.setVirtualMachine(tmp);

                volTmp.setStoragePool(vol.getStoragePool());
                volTmp.setVirtualMachineTemplate(vol.getVirtualMachineTemplate());
                volTmp.setIdScsi(vol.getIdScsi());
                volTmp.setState(vol.getState());
                volTmp.setUsedSizeInMB(vol.getUsedSizeInMB());

                volsTemp.add(volTmp);
            }
        }
        tmp.setVolumes(volsTemp);
    }

    /**
     * Gets the {@link VirtualMachine} backup created to store reconfigure previous state.
     * 
     * @return the virtualmachine with ''temp'' == provided vm identifier
     */
    public VirtualMachine getBackupVirtualMachine(final VirtualMachine vmachine) {
        final VirtualMachine vmbackup = repo.findBackup(vmachine);

        if (vmbackup == null) {
            addNotFoundErrors(APIError.VIRTUAL_MACHINE_BACKUP_NOT_FOUND);
            flushErrors();
        }

        return vmbackup;
    }

    /**
     * Cleanup backup resources
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void deleteBackupResources(final VirtualMachine backUpVm) {

        try {
            rasdDao.enableTemporalOnlyFilter();

            List<RasdManagement> rasds = backUpVm.getRasdManagements();

            // First of all, we have to release the VLAN tags if it is needed.
            // This is not a very optimal algorithm, since we traverse again the Rasds later,
            // but we need to know the id
            // of the virtual machine before to delete the entity to know if we can to release the
            // TAG.
            for (RasdManagement rollbackRasd : rasds) {
                if (rollbackRasd instanceof IpPoolManagement) {

                    IpPoolManagement originalRasd = (IpPoolManagement) rasdDao.findById(rollbackRasd.getTemporal());

                    if (!originalRasd.isAttached()) {
                        // check if it was the last IP of the VLAN, and release the VLAN
                        // tag.
                        VLANNetwork vlanNetwork = originalRasd.getVlanNetwork();

                        final boolean assigned = ipPoolManDao.isVlanAssignedToDifferentVM(backUpVm.getId(),
                                vlanNetwork);

                        if (!assigned) {
                            if (vlanNetwork.getType().equals(NetworkType.INTERNAL)) {
                                vlanNetwork.setTag(null);
                            }

                            NetworkAssignment na = netAssignDao.findByVlanNetwork(vlanNetwork);

                            if (na != null) {
                                netAssignDao.remove(na);
                            }
                        }
                    }
                }
            }

            /*
             * CAUTION! We need this flush exactly here. Otherwise it tries to set teh
             * vlanNetwork.setTag(null) after to delete the related IpPoolManagement (that will be
             * deleted in the next loop) and it raises an UnknowEntityException.
             */
            vlanNetworkDao.flush();

            // we need to first delete the vm (as it updates the rasd_man)
            repo.deleteVirtualMachine(backUpVm);

            for (RasdManagement rollbackRasd : rasds) {
                if (rollbackRasd instanceof IpPoolManagement) {
                    IpPoolManagement originalRasd = (IpPoolManagement) rasdDao.findById(rollbackRasd.getTemporal());

                    if (!originalRasd.isAttached()) {
                        // remove the rasd
                        vdcRep.deleteRasd(originalRasd.getRasd());

                        // unmanaged ips disappear when the are not assigned to a virtual machine.
                        if (originalRasd.isUnmanagedIp()) {
                            rasdDao.remove(originalRasd);
                        }

                        // external ips should remove its MAC and name
                        if (originalRasd.isExternalIp()) {
                            originalRasd.setMac(null);
                            originalRasd.setName(null);
                        }
                    }
                }
                // DiskManagements always are deleted
                if (rollbackRasd instanceof DiskManagement) {
                    DiskManagement originalRasd = (DiskManagement) rasdDao.findById(rollbackRasd.getTemporal());
                    if (!originalRasd.isAttached()) {
                        vdcRep.deleteRasd(originalRasd.getRasd());
                        rasdDao.remove(originalRasd);
                    }
                }

                // refresh as the vm delete was updated the rasd
                rasdDao.remove(rasdDao.findById(rollbackRasd.getId()));
            }

            rasdDao.flush();
        } finally {
            rasdDao.restoreDefaultFilters();
        }

        // FIXME This is what we like
        // try
        // {
        // rasdDao.enableTemporalOnlyFilter();
        //
        // for (RasdManagement rasd : vm.getRasdManagements())
        // {
        // rasdDao.remove(rasd);
        // }
        //
        // repo.deleteVirtualMachine(repo.findVirtualMachineById(vm.getId()));
        //
        // rasdDao.flush();
        // }
        // finally
        // {
        // rasdDao.restoreDefaultFilters();
        // }
    }

    /**
     * Updates all the attributes and resource attachments of ''updatedVm'' from the backup
     * ''rollbackVm''.
     * 
     * @param updatedVm, current state of the virtual machine (not applied in the hypervisor)
     * @param rollbackVm, state of updaedVm previous to the reconfigure.
     * @return updatedVm with the attributes and resource attachments of rollbackVm.
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public VirtualMachine restoreBackupVirtualMachine(final VirtualMachine updatedVm,
            final VirtualMachine rollbackVm) {

        // will use VsmServiceStub to force a refresh
        // updatedVm.setState(VirtualMachineState.LOCKED);

        // backup virtual machine properties
        updatedVm.setCpu(rollbackVm.getCpu());
        updatedVm.setDatastore(rollbackVm.getDatastore());
        updatedVm.setDescription(rollbackVm.getDescription());
        updatedVm.setEnterprise(rollbackVm.getEnterprise());
        updatedVm.setHdInBytes(rollbackVm.getHdInBytes());
        updatedVm.setHighDisponibility(rollbackVm.getHighDisponibility());
        updatedVm.setHypervisor(rollbackVm.getHypervisor());
        updatedVm.setIdType(rollbackVm.getIdType());
        updatedVm.setName(rollbackVm.getName().substring("tmp_".length()));
        updatedVm.setPassword(rollbackVm.getPassword());
        updatedVm.setRam(rollbackVm.getRam());
        updatedVm.setSubState(rollbackVm.getSubState());
        updatedVm.setUser(rollbackVm.getUser());
        updatedVm.setUuid(rollbackVm.getUuid());
        updatedVm.setVdrpIP(rollbackVm.getVdrpIP());
        updatedVm.setVdrpPort(rollbackVm.getVdrpPort());
        updatedVm.setVirtualImageConversion(rollbackVm.getVirtualImageConversion());
        updatedVm.setVirtualMachineTemplate(rollbackVm.getVirtualMachineTemplate());
        updatedVm.setNetworkConfiguration(rollbackVm.getNetworkConfiguration());

        List<RasdManagement> updatedResources = updatedVm.getRasdManagements();
        List<RasdManagement> rollbackResources = getBackupResources(rollbackVm);

        LOGGER.debug("removed backup virtual machine");

        for (RasdManagement updatedRasd : updatedResources) {
            RasdManagement rollbackRasd = getBackupResource(rollbackResources, updatedRasd.getId());

            if (rollbackRasd == null) {
                LOGGER.trace("restore: detach resource " + updatedRasd.getId());

                if (updatedRasd instanceof IpPoolManagement) {
                    IpPoolManagement originalRasd = (IpPoolManagement) updatedRasd;

                    // remove the rasd
                    vdcRep.deleteRasd(originalRasd.getRasd());

                    // unmanaged ips disappear when the are not assigned to a virtual machine.
                    if (originalRasd.isUnmanagedIp()) {
                        rasdDao.remove(originalRasd);
                    }
                }
                // DiskManagements always are deleted
                if (updatedRasd instanceof DiskManagement) {
                    DiskManagement originalRasd = (DiskManagement) updatedRasd;

                    vdcRep.deleteRasd(originalRasd.getRasd());
                    rasdDao.remove(originalRasd);
                } else {
                    // volumes only need to be dettached
                    if (!isStatefulVolume(updatedRasd)) {
                        updatedRasd.detach();
                    }
                }

            }
        }

        for (RasdManagement rollbackRasd : rollbackResources) {
            RasdManagement originalRasd = rasdDao.findById(rollbackRasd.getTemporal());

            if (!originalRasd.isAttached()) {
                // Re attach the resource to the virtual machine
                LOGGER.trace("restore: attach resource " + originalRasd.getId());
                originalRasd.attach(originalRasd.getSequence(), updatedVm);
                // I dunno if it is necessary for the rest of resources,
                // but for IPs it is.
                if (originalRasd instanceof IpPoolManagement) {
                    IpPoolManagement ipman = (IpPoolManagement) originalRasd;
                    IpPoolManagement ipRoll = (IpPoolManagement) rollbackRasd;
                    VirtualAppliance vapp = vdcRep.findVirtualApplianceByVirtualMachine(updatedVm);
                    ipman.setVirtualAppliance(vapp);
                    ipman.setVirtualDatacenter(rollbackRasd.getVirtualDatacenter());
                    ipman.setIp(ipRoll.getIp());
                    ipman.setMac(ipRoll.getMac());
                }
            }

        }

        repo.deleteVirtualMachine(rollbackVm);
        repo.update(updatedVm);
        rasdDao.flush();
        // update virtual machine resources

        LOGGER.info("restored virtual machine {} from backup", updatedVm.getUuid());

        return updatedVm;
    }

    /**
     * Get the resources attached to the provided backup virtualmachine.
     */
    private List<RasdManagement> getBackupResources(final VirtualMachine rollbackVm) {
        try {
            rasdDao.enableTemporalOnlyFilter();
            return rollbackVm.getRasdManagements();
        } finally {
            rasdDao.restoreDefaultFilters();
        }
    }

    /**
     * Find the backup resources with temporal pointing to the provided resource identifier.
     * 
     * @return resource with temporal == provided resource id, null if not found
     */
    private RasdManagement getBackupResource(final List<RasdManagement> rollbackResources,
            final Integer tempRasdManId) {
        for (RasdManagement rasdman : rollbackResources) {
            if (tempRasdManId.equals(rasdman.getTemporal())) {
                return rasdman;
            }
        }
        return null;
    }

    /**
     * @param vmId to return
     * @return VirtualMachine with DC.
     */
    public VirtualMachine getVirtualMachineInitialized(final Integer vmId) {
        VirtualMachine virtualMachine = repo.findVirtualMachineById(vmId);

        if (virtualMachine == null) {
            return null;
        }

        if (virtualMachine.getHypervisor() != null) {
            Hibernate.initialize(virtualMachine.getHypervisor().getMachine().getDatacenter());
        }
        if (virtualMachine.getEnterprise() != null) {
            Hibernate.initialize(virtualMachine.getEnterprise());
        }
        if (virtualMachine.getDatastore() != null) {
            Hibernate.initialize(virtualMachine.getDatastore());
        }
        if (virtualMachine.getVirtualMachineTemplate() != null) {
            Hibernate.initialize(virtualMachine.getVirtualMachineTemplate());
        }

        return virtualMachine;
    }

    /**
     * Sets the {@link VirtualMachine#setState(VirtualMachineState)} to
     * {@link VirtualMachineState#UNKNOWN}.
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void setVirtualMachineToUnknown(final Integer vmId) {
        repo.setVirtualMachineToUnknown(vmId);
    }

    /**
     * Provides a standard method to allocate a resource and check if its already allocated.
     * 
     * @param vm {@link VirtualMachine} virtual machine where the resource will be allocated.
     * @param vapp {@link VirtualAppliance} virtual appiance where the resource will be allocated.
     * @param resource resource to allocate
     * @param attachOrder the number of allocation order for this resource.
     * @return true if the resource has been allocated, false if it was previously allocated.
     */
    protected boolean allocateResource(final VirtualMachine vm, final VirtualAppliance vapp,
            final RasdManagement resource, final Integer attachOrder) {

        if (resource.isAttached()) {
            // FIXME BE AWARE OF IT:
            // the provided vm sometimes have ID (came form DDBB) and sometimes havent ID
            // (createBackup) but have the TemporalID. So it is not always called with the same type
            // of parameter.
            final Integer currentId = resource.getVirtualMachine().getId() != null
                    ? resource.getVirtualMachine().getId()
                    : resource.getVirtualMachine().getTemporal();

            if (!currentId.equals(vm.getId())) {
                addConflictErrors(APIError.RESOURCE_ALREADY_ASSIGNED_TO_A_VIRTUAL_MACHINE);
                flushErrors();
            }

            return false;
        }

        if (resource.getVirtualMachine() != null && resource.getVirtualMachine().getTemporal() != null) {
            if (!resource.getVirtualMachine().getTemporal().equals(vm.getId())) {
                addConflictErrors(APIError.RESOURCE_ALREADY_ASSIGNED_TO_A_VIRTUAL_MACHINE);
                flushErrors();
            }

            // else do nothing, the resource is already asigned to this virtual machine.
            return false;
        } else {
            resource.attach(attachOrder, vm, vapp);
            return true;
        }
    }

    /**
     * This method writes without care for permissions.
     * 
     * @param vm void
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateVirtualMachineBySystem(final VirtualMachine vm) {
        repo.update(vm);
    }

    /**
     * This method writes without care for permissions.
     * 
     * @param vm void
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertNodeVirtualImage(final NodeVirtualImage node) {
        repo.insertNodeVirtualImage(node);
    }

    /**
     * This method writes without care for permissions.
     * 
     * @param vm void
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertVirtualMachine(final VirtualMachine virtualMachine) {
        repo.insert(virtualMachine);
    }

    @Transactional(readOnly = true, propagation = Propagation.REQUIRED)
    public Collection<VirtualMachine> getManagedByHypervisor(final Hypervisor hypervisor) {
        return repo.findManagedByHypervisor(hypervisor);
    }
}