You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2019/05/14 09:46:50 UTC

[cloudstack] 01/02: Merge remote-tracking branch 'origin/4.11' into 4.12

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 00ff536f813352a82e847936a819a5a5b9c8c3a6
Merge: 9488c6d 9ff819d
Author: Rohit Yadav <ro...@shapeblue.com>
AuthorDate: Tue May 14 14:26:11 2019 +0530

    Merge remote-tracking branch 'origin/4.11' into 4.12
    
    Signed-off-by: Rohit Yadav <ro...@shapeblue.com>

 api/src/main/java/com/cloud/storage/Volume.java    |   4 +-
 .../configuration/dao/ResourceCountDaoImpl.java    |   5 +-
 .../storage/volume/VolumeServiceImpl.java          |   3 +
 .../kvm/resource/LibvirtComputingResource.java     |  20 +--
 .../wrapper/LibvirtStartCommandWrapper.java        |   7 +-
 .../kvm/resource/LibvirtDomainXMLParserTest.java   |  22 +---
 .../hypervisor/kvm/resource/LibvirtVMDefTest.java  |   2 +-
 scripts/vm/hypervisor/kvm/patch.sh                 |  80 ++++++++++++
 scripts/vm/hypervisor/kvm/patchviasocket.py        |  76 -----------
 scripts/vm/hypervisor/kvm/test_patchviasocket.py   | 144 ---------------------
 .../com/cloud/storage/VolumeApiServiceImpl.java    |  47 ++++---
 .../debian/opt/cloud/bin/setup/cloud-early-config  |  52 +++-----
 systemvm/debian/root/.ssh/authorized_keys          |   1 -
 systemvm/pom.xml                                   |   7 -
 test/integration/smoke/test_volumes.py             |  94 +++++++++++---
 .../scripts/configure_systemvm_services.sh         |   2 +-
 tools/appliance/systemvmtemplate/template.json     |   4 +-
 17 files changed, 229 insertions(+), 341 deletions(-)

diff --cc engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java
index 4bd8302,0000000..e724d8e
mode 100644,000000..100644
--- a/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java
@@@ -1,283 -1,0 +1,284 @@@
 +// Licensed to the Apache Software Foundation (ASF) under one
 +// or more contributor license agreements.  See the NOTICE file
 +// distributed with this work for additional information
 +// regarding copyright ownership.  The ASF licenses this file
 +// to you under the Apache License, Version 2.0 (the
 +// "License"); you may not use this file except in compliance
 +// with the License.  You may obtain a copy of the License at
 +//
 +//   http://www.apache.org/licenses/LICENSE-2.0
 +//
 +// Unless required by applicable law or agreed to in writing,
 +// software distributed under the License is distributed on an
 +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 +// KIND, either express or implied.  See the License for the
 +// specific language governing permissions and limitations
 +// under the License.
 +package com.cloud.configuration.dao;
 +
 +import java.sql.PreparedStatement;
 +import java.sql.ResultSet;
 +import java.sql.SQLException;
 +import java.util.ArrayList;
 +import java.util.HashSet;
 +import java.util.List;
 +import java.util.Set;
 +
 +import javax.annotation.PostConstruct;
 +import javax.inject.Inject;
 +
 +import org.springframework.stereotype.Component;
 +
 +import com.cloud.configuration.Resource;
 +import com.cloud.configuration.Resource.ResourceOwnerType;
 +import com.cloud.configuration.Resource.ResourceType;
 +import com.cloud.configuration.ResourceCountVO;
 +import com.cloud.configuration.ResourceLimit;
 +import com.cloud.domain.DomainVO;
 +import com.cloud.domain.dao.DomainDao;
 +import com.cloud.exception.UnsupportedServiceException;
 +import com.cloud.user.AccountVO;
 +import com.cloud.user.dao.AccountDao;
 +import com.cloud.utils.db.DB;
 +import com.cloud.utils.db.GenericDaoBase;
 +import com.cloud.utils.db.JoinBuilder;
 +import com.cloud.utils.db.SearchBuilder;
 +import com.cloud.utils.db.SearchCriteria;
 +import com.cloud.utils.db.TransactionLegacy;
 +import com.cloud.utils.exception.CloudRuntimeException;
 +
 +@Component
 +public class ResourceCountDaoImpl extends GenericDaoBase<ResourceCountVO, Long> implements ResourceCountDao {
 +    private final SearchBuilder<ResourceCountVO> TypeSearch;
 +
 +    private final SearchBuilder<ResourceCountVO> AccountSearch;
 +    private final SearchBuilder<ResourceCountVO> DomainSearch;
 +
 +    @Inject
 +    private DomainDao _domainDao;
 +    @Inject
 +    private AccountDao _accountDao;
 +
 +    public ResourceCountDaoImpl() {
 +        TypeSearch = createSearchBuilder();
 +        TypeSearch.and("type", TypeSearch.entity().getType(), SearchCriteria.Op.EQ);
 +        TypeSearch.and("accountId", TypeSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
 +        TypeSearch.and("domainId", TypeSearch.entity().getDomainId(), SearchCriteria.Op.EQ);
 +        TypeSearch.done();
 +
 +        AccountSearch = createSearchBuilder();
 +        DomainSearch = createSearchBuilder();
 +    }
 +
 +    @PostConstruct
 +    protected void configure() {
 +        AccountSearch.and("accountId", AccountSearch.entity().getAccountId(), SearchCriteria.Op.NNULL);
 +        SearchBuilder<AccountVO> joinAccount = _accountDao.createSearchBuilder();
 +        joinAccount.and("notremoved", joinAccount.entity().getRemoved(), SearchCriteria.Op.NULL);
 +        AccountSearch.join("account", joinAccount, AccountSearch.entity().getAccountId(), joinAccount.entity().getId(), JoinBuilder.JoinType.INNER);
 +        AccountSearch.done();
 +
 +        DomainSearch.and("domainId", DomainSearch.entity().getDomainId(), SearchCriteria.Op.NNULL);
 +        SearchBuilder<DomainVO> joinDomain = _domainDao.createSearchBuilder();
 +        joinDomain.and("notremoved", joinDomain.entity().getRemoved(), SearchCriteria.Op.NULL);
 +        DomainSearch.join("domain", joinDomain, DomainSearch.entity().getDomainId(), joinDomain.entity().getId(), JoinBuilder.JoinType.INNER);
 +        DomainSearch.done();
 +    }
 +
 +    @Override
 +    public ResourceCountVO findByOwnerAndType(long ownerId, ResourceOwnerType ownerType, ResourceType type) {
 +        SearchCriteria<ResourceCountVO> sc = TypeSearch.create();
 +        sc.setParameters("type", type);
 +
 +        if (ownerType == ResourceOwnerType.Account) {
 +            sc.setParameters("accountId", ownerId);
 +            return findOneIncludingRemovedBy(sc);
 +        } else if (ownerType == ResourceOwnerType.Domain) {
 +            sc.setParameters("domainId", ownerId);
 +            return findOneIncludingRemovedBy(sc);
 +        } else {
 +            return null;
 +        }
 +    }
 +
 +    @Override
 +    public long getResourceCount(long ownerId, ResourceOwnerType ownerType, ResourceType type) {
 +        ResourceCountVO vo = findByOwnerAndType(ownerId, ownerType, type);
 +        if (vo != null) {
 +            return vo.getCount();
 +        } else {
 +            return 0;
 +        }
 +    }
 +
 +    @Override
 +    public void setResourceCount(long ownerId, ResourceOwnerType ownerType, ResourceType type, long count) {
 +        ResourceCountVO resourceCountVO = findByOwnerAndType(ownerId, ownerType, type);
 +        if (resourceCountVO != null && count != resourceCountVO.getCount()) {
 +            resourceCountVO.setCount(count);
 +            update(resourceCountVO.getId(), resourceCountVO);
 +        }
 +    }
 +
 +    @Override
 +    public boolean updateById(long id, boolean increment, long delta) {
 +        delta = increment ? delta : delta * -1;
 +
 +        ResourceCountVO resourceCountVO = findById(id);
 +        resourceCountVO.setCount(resourceCountVO.getCount() + delta);
 +        return update(resourceCountVO.getId(), resourceCountVO);
 +    }
 +
 +    @Override
 +    public Set<Long> listRowsToUpdateForDomain(long domainId, ResourceType type) {
 +        Set<Long> rowIds = new HashSet<Long>();
 +        Set<Long> domainIdsToUpdate = _domainDao.getDomainParentIds(domainId);
 +        for (Long domainIdToUpdate : domainIdsToUpdate) {
 +            ResourceCountVO domainCountRecord = findByOwnerAndType(domainIdToUpdate, ResourceOwnerType.Domain, type);
 +            if (domainCountRecord != null) {
 +                rowIds.add(domainCountRecord.getId());
 +            }
 +        }
 +        return rowIds;
 +    }
 +
 +    @Override
 +    public Set<Long> listAllRowsToUpdate(long ownerId, ResourceOwnerType ownerType, ResourceType type) {
 +        Set<Long> rowIds = new HashSet<Long>();
 +
 +        if (ownerType == ResourceOwnerType.Account) {
 +            //get records for account
 +            ResourceCountVO accountCountRecord = findByOwnerAndType(ownerId, ResourceOwnerType.Account, type);
 +            if (accountCountRecord != null) {
 +                rowIds.add(accountCountRecord.getId());
 +            }
 +
 +            //get records for account's domain and all its parent domains
 +            rowIds.addAll(listRowsToUpdateForDomain(_accountDao.findByIdIncludingRemoved(ownerId).getDomainId(), type));
 +        } else if (ownerType == ResourceOwnerType.Domain) {
 +            return listRowsToUpdateForDomain(ownerId, type);
 +        }
 +
 +        return rowIds;
 +    }
 +
 +    @Override
 +    @DB
 +    public void createResourceCounts(long ownerId, ResourceLimit.ResourceOwnerType ownerType) {
 +
 +        TransactionLegacy txn = TransactionLegacy.currentTxn();
 +        txn.start();
 +
 +        ResourceType[] resourceTypes = Resource.ResourceType.values();
 +        for (ResourceType resourceType : resourceTypes) {
 +            if (!resourceType.supportsOwner(ownerType)) {
 +                continue;
 +            }
 +            ResourceCountVO resourceCountVO = new ResourceCountVO(resourceType, 0, ownerId, ownerType);
 +            persist(resourceCountVO);
 +        }
 +
 +        txn.commit();
 +    }
 +
 +    private List<ResourceCountVO> listByDomainId(long domainId) {
 +        SearchCriteria<ResourceCountVO> sc = TypeSearch.create();
 +        sc.setParameters("domainId", domainId);
 +
 +        return listBy(sc);
 +    }
 +
 +    private List<ResourceCountVO> listByAccountId(long accountId) {
 +        SearchCriteria<ResourceCountVO> sc = TypeSearch.create();
 +        sc.setParameters("accountId", accountId);
 +
 +        return listBy(sc);
 +    }
 +
 +    @Override
 +    public List<ResourceCountVO> listByOwnerId(long ownerId, ResourceOwnerType ownerType) {
 +        if (ownerType == ResourceOwnerType.Account) {
 +            return listByAccountId(ownerId);
 +        } else if (ownerType == ResourceOwnerType.Domain) {
 +            return listByDomainId(ownerId);
 +        } else {
 +            return new ArrayList<ResourceCountVO>();
 +        }
 +    }
 +
 +    @Override
 +    public List<ResourceCountVO> listResourceCountByOwnerType(ResourceOwnerType ownerType) {
 +        if (ownerType == ResourceOwnerType.Account) {
 +            return listBy(AccountSearch.create());
 +        } else if (ownerType == ResourceOwnerType.Domain) {
 +            return listBy(DomainSearch.create());
 +        } else {
 +            return new ArrayList<ResourceCountVO>();
 +        }
 +    }
 +
 +    @Override
 +    public ResourceCountVO persist(ResourceCountVO resourceCountVO) {
 +        ResourceOwnerType ownerType = resourceCountVO.getResourceOwnerType();
 +        ResourceType resourceType = resourceCountVO.getType();
 +        if (!resourceType.supportsOwner(ownerType)) {
 +            throw new UnsupportedServiceException("Resource type " + resourceType + " is not supported for owner of type " + ownerType.getName());
 +        }
 +
 +        return super.persist(resourceCountVO);
 +    }
 +
 +    @Override
 +    public long removeEntriesByOwner(long ownerId, ResourceOwnerType ownerType) {
 +        SearchCriteria<ResourceCountVO> sc = TypeSearch.create();
 +
 +        if (ownerType == ResourceOwnerType.Account) {
 +            sc.setParameters("accountId", ownerId);
 +            return remove(sc);
 +        } else if (ownerType == ResourceOwnerType.Domain) {
 +            sc.setParameters("domainId", ownerId);
 +            return remove(sc);
 +        }
 +        return 0;
 +    }
 +
 +    private String baseSqlCountComputingResourceAllocatedToAccount = "Select "
 +            + " SUM((CASE "
 +            + "        WHEN so.%s is not null THEN so.%s "
 +            + "        ELSE CONVERT(vmd.value, UNSIGNED INTEGER) "
 +            + "    END)) as total "
 +            + " from vm_instance vm "
 +            + " join service_offering so on so.id = vm.service_offering_id "
 +            + " left join user_vm_details vmd on vmd.vm_id = vm.id and vmd.name = '%s' "
 +            + " where vm.type = 'User' and state not in ('Destroyed', 'Error', 'Expunging') and display_vm = true and account_id = ? ";
 +
 +    @Override
 +    public long countCpuNumberAllocatedToAccount(long accountId) {
 +        String sqlCountCpuNumberAllocatedToAccount = String.format(baseSqlCountComputingResourceAllocatedToAccount, ResourceType.cpu, ResourceType.cpu, "cpuNumber");
 +        return executeSqlCountComputingResourcesForAccount(accountId, sqlCountCpuNumberAllocatedToAccount);
 +    }
 +
 +    @Override
 +    public long countMemoryAllocatedToAccount(long accountId) {
 +        String serviceOfferingRamSizeField = "ram_size";
 +        String sqlCountCpuNumberAllocatedToAccount = String.format(baseSqlCountComputingResourceAllocatedToAccount, serviceOfferingRamSizeField, serviceOfferingRamSizeField, "memory");
 +        return executeSqlCountComputingResourcesForAccount(accountId, sqlCountCpuNumberAllocatedToAccount);
 +    }
 +
 +    private long executeSqlCountComputingResourcesForAccount(long accountId, String sqlCountComputingResourcesAllocatedToAccount) {
-         try (TransactionLegacy tx = TransactionLegacy.currentTxn()) {
++        TransactionLegacy tx = TransactionLegacy.currentTxn();
++        try {
 +            PreparedStatement pstmt = tx.prepareAutoCloseStatement(sqlCountComputingResourcesAllocatedToAccount);
 +            pstmt.setLong(1, accountId);
 +
 +            ResultSet rs = pstmt.executeQuery();
 +            if (!rs.next()) {
-                 throw new CloudRuntimeException(String.format("An unexpected case happened while counting allocated computing resources for account: " + accountId));
++                return 0L;
 +            }
 +            return rs.getLong("total");
 +        } catch (SQLException e) {
 +            throw new CloudRuntimeException(e);
 +        }
 +    }
 +
 +}
diff --cc engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
index 8ff0bd2,0000000..2a642f0
mode 100644,000000..100644
--- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
+++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
@@@ -1,2098 -1,0 +1,2101 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.cloudstack.storage.volume;
 +
 +import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.Date;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Random;
 +
 +import javax.inject.Inject;
 +
 +import org.apache.cloudstack.engine.cloud.entity.api.VolumeEntity;
 +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
 +import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
 +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
 +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
 +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
 +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
 +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
 +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
 +import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
 +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
 +import org.apache.cloudstack.framework.async.AsyncCallFuture;
 +import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
 +import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
 +import org.apache.cloudstack.framework.async.AsyncRpcContext;
 +import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 +import org.apache.cloudstack.storage.RemoteHostEndPoint;
 +import org.apache.cloudstack.storage.command.CommandResult;
 +import org.apache.cloudstack.storage.command.CopyCmdAnswer;
 +import org.apache.cloudstack.storage.command.DeleteCommand;
 +import org.apache.cloudstack.storage.datastore.PrimaryDataStoreProviderManager;
 +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
 +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
 +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
 +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
 +import org.apache.cloudstack.storage.image.store.TemplateObject;
 +import org.apache.cloudstack.storage.to.TemplateObjectTO;
 +import org.apache.cloudstack.storage.to.VolumeObjectTO;
 +import org.apache.log4j.Logger;
 +import org.springframework.stereotype.Component;
 +
 +import com.cloud.agent.AgentManager;
 +import com.cloud.agent.api.Answer;
 +import com.cloud.agent.api.ModifyTargetsCommand;
 +import com.cloud.agent.api.storage.ListVolumeAnswer;
 +import com.cloud.agent.api.storage.ListVolumeCommand;
 +import com.cloud.agent.api.storage.ResizeVolumeCommand;
 +import com.cloud.agent.api.to.StorageFilerTO;
 +import com.cloud.agent.api.to.VirtualMachineTO;
 +import com.cloud.alert.AlertManager;
 +import com.cloud.configuration.Config;
 +import com.cloud.configuration.Resource.ResourceType;
 +import com.cloud.dc.dao.ClusterDao;
 +import com.cloud.event.EventTypes;
 +import com.cloud.event.UsageEventUtils;
 +import com.cloud.exception.ResourceAllocationException;
 +import com.cloud.host.Host;
 +import com.cloud.host.HostVO;
 +import com.cloud.host.dao.HostDao;
 +import com.cloud.host.dao.HostDetailsDao;
 +import com.cloud.hypervisor.Hypervisor.HypervisorType;
 +import com.cloud.offering.DiskOffering;
 +import com.cloud.org.Cluster;
 +import com.cloud.org.Grouping.AllocationState;
 +import com.cloud.resource.ResourceState;
 +import com.cloud.server.ManagementService;
 +import com.cloud.storage.DataStoreRole;
 +import com.cloud.storage.RegisterVolumePayload;
 +import com.cloud.storage.ScopeType;
 +import com.cloud.storage.Storage.StoragePoolType;
 +import com.cloud.storage.StoragePool;
 +import com.cloud.storage.VMTemplateStoragePoolVO;
 +import com.cloud.storage.VMTemplateStorageResourceAssoc;
 +import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
 +import com.cloud.storage.Volume;
 +import com.cloud.storage.Volume.State;
 +import com.cloud.storage.VolumeVO;
 +import com.cloud.storage.dao.VMTemplatePoolDao;
 +import com.cloud.storage.dao.VolumeDao;
 +import com.cloud.storage.dao.VolumeDetailsDao;
 +import com.cloud.storage.snapshot.SnapshotManager;
 +import com.cloud.storage.template.TemplateProp;
 +import com.cloud.user.AccountManager;
 +import com.cloud.user.ResourceLimitService;
 +import com.cloud.utils.NumbersUtil;
 +import com.cloud.utils.Pair;
 +import com.cloud.utils.db.DB;
 +import com.cloud.utils.db.GlobalLock;
 +import com.cloud.utils.exception.CloudRuntimeException;
 +
 +@Component
 +public class VolumeServiceImpl implements VolumeService {
 +    private static final Logger s_logger = Logger.getLogger(VolumeServiceImpl.class);
 +    @Inject
 +    protected AgentManager agentMgr;
 +    @Inject
 +    VolumeDao volDao;
 +    @Inject
 +    PrimaryDataStoreProviderManager dataStoreMgr;
 +    @Inject
 +    DataMotionService motionSrv;
 +    @Inject
 +    VolumeDataFactory volFactory;
 +    @Inject
 +    SnapshotManager snapshotMgr;
 +    @Inject
 +    ResourceLimitService _resourceLimitMgr;
 +    @Inject
 +    AccountManager _accountMgr;
 +    @Inject
 +    AlertManager _alertMgr;
 +    @Inject
 +    ConfigurationDao configDao;
 +    @Inject
 +    VolumeDataStoreDao _volumeStoreDao;
 +    @Inject
 +    VMTemplatePoolDao _tmpltPoolDao;
 +    @Inject
 +    SnapshotDataStoreDao _snapshotStoreDao;
 +    @Inject
 +    VolumeDao _volumeDao;
 +    @Inject
 +    EndPointSelector _epSelector;
 +    @Inject
 +    HostDao _hostDao;
 +    @Inject
 +    private PrimaryDataStoreDao storagePoolDao;
 +    @Inject
 +    private HostDetailsDao hostDetailsDao;
 +    @Inject
 +    private ManagementService mgr;
 +    @Inject
 +    private ClusterDao clusterDao;
 +    @Inject
 +    private VolumeDetailsDao _volumeDetailsDao;
 +
 +    private final static String SNAPSHOT_ID = "SNAPSHOT_ID";
 +
 +    public VolumeServiceImpl() {
 +    }
 +
 +    private class CreateVolumeContext<T> extends AsyncRpcContext<T> {
 +
 +        private final DataObject volume;
 +        private final AsyncCallFuture<VolumeApiResult> future;
 +
 +        public CreateVolumeContext(AsyncCompletionCallback<T> callback, DataObject volume, AsyncCallFuture<VolumeApiResult> future) {
 +            super(callback);
 +            this.volume = volume;
 +            this.future = future;
 +        }
 +
 +        public DataObject getVolume() {
 +            return this.volume;
 +        }
 +
 +        public AsyncCallFuture<VolumeApiResult> getFuture() {
 +            return this.future;
 +        }
 +
 +    }
 +
 +    @Override
 +    public ChapInfo getChapInfo(DataObject dataObject, DataStore dataStore) {
 +        DataStoreDriver dataStoreDriver = dataStore.getDriver();
 +
 +        if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
 +            return ((PrimaryDataStoreDriver)dataStoreDriver).getChapInfo(dataObject);
 +        }
 +
 +        return null;
 +    }
 +
 +    @Override
 +    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) {
 +        DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null;
 +
 +        if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
 +            return ((PrimaryDataStoreDriver)dataStoreDriver).grantAccess(dataObject, host, dataStore);
 +        }
 +
 +        return false;
 +    }
 +
 +    @Override
 +    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) {
 +        DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null;
++        if (dataStoreDriver == null) {
++            return;
++        }
 +
 +        if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
 +            ((PrimaryDataStoreDriver)dataStoreDriver).revokeAccess(dataObject, host, dataStore);
 +        }
 +    }
 +
 +    @Override
 +    public AsyncCallFuture<VolumeApiResult> createVolumeAsync(VolumeInfo volume, DataStore dataStore) {
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +        DataObject volumeOnStore = dataStore.create(volume);
 +        volumeOnStore.processEvent(Event.CreateOnlyRequested);
 +
 +        try {
 +            CreateVolumeContext<VolumeApiResult> context = new CreateVolumeContext<VolumeApiResult>(null, volumeOnStore, future);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this);
 +            caller.setCallback(caller.getTarget().createVolumeCallback(null, null)).setContext(context);
 +
 +            dataStore.getDriver().createAsync(dataStore, volumeOnStore, caller);
 +        } catch (CloudRuntimeException ex) {
 +            // clean up already persisted volume_store_ref entry in case of createVolumeCallback is never called
 +            VolumeDataStoreVO volStoreVO = _volumeStoreDao.findByStoreVolume(dataStore.getId(), volume.getId());
 +            if (volStoreVO != null) {
 +                VolumeInfo volObj = volFactory.getVolume(volume, dataStore);
 +                volObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
 +            }
 +            VolumeApiResult volResult = new VolumeApiResult((VolumeObject)volumeOnStore);
 +            volResult.setResult(ex.getMessage());
 +            future.complete(volResult);
 +        }
 +        return future;
 +    }
 +
 +    protected Void createVolumeCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> callback, CreateVolumeContext<VolumeApiResult> context) {
 +        CreateCmdResult result = callback.getResult();
 +        DataObject vo = context.getVolume();
 +        String errMsg = null;
 +        if (result.isSuccess()) {
 +            vo.processEvent(Event.OperationSuccessed, result.getAnswer());
 +        } else {
 +            vo.processEvent(Event.OperationFailed);
 +            errMsg = result.getResult();
 +        }
 +        VolumeApiResult volResult = new VolumeApiResult((VolumeObject)vo);
 +        if (errMsg != null) {
 +            volResult.setResult(errMsg);
 +        }
 +        context.getFuture().complete(volResult);
 +        return null;
 +    }
 +
 +    private class DeleteVolumeContext<T> extends AsyncRpcContext<T> {
 +        private final VolumeObject volume;
 +        private final AsyncCallFuture<VolumeApiResult> future;
 +
 +        public DeleteVolumeContext(AsyncCompletionCallback<T> callback, VolumeObject volume, AsyncCallFuture<VolumeApiResult> future) {
 +            super(callback);
 +            this.volume = volume;
 +            this.future = future;
 +        }
 +
 +        public VolumeObject getVolume() {
 +            return this.volume;
 +        }
 +
 +        public AsyncCallFuture<VolumeApiResult> getFuture() {
 +            return this.future;
 +        }
 +    }
 +
 +    // check if a volume is expunged on both primary and secondary
 +    private boolean canVolumeBeRemoved(long volumeId) {
 +        VolumeVO vol = volDao.findById(volumeId);
 +        if (vol == null) {
 +            // already removed from volumes table
 +            return false;
 +        }
 +        VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volumeId);
 +        if ((vol.getState() == State.Expunged || (vol.getPodId() == null && vol.getState() == State.Destroy)) && volumeStore == null) {
 +            // volume is expunged from primary, as well as on secondary
 +            return true;
 +        } else {
 +            return false;
 +        }
 +
 +    }
 +
 +    @DB
 +    @Override
 +    public AsyncCallFuture<VolumeApiResult> expungeVolumeAsync(VolumeInfo volume) {
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +        VolumeApiResult result = new VolumeApiResult(volume);
 +        if (volume.getDataStore() == null) {
 +            s_logger.info("Expunge volume with no data store specified");
 +            if (canVolumeBeRemoved(volume.getId())) {
 +                s_logger.info("Volume " + volume.getId() + " is not referred anywhere, remove it from volumes table");
 +                volDao.remove(volume.getId());
 +            }
 +            future.complete(result);
 +            return future;
 +        }
 +
 +        // Find out if the volume is at state of download_in_progress on secondary storage
 +        VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume.getId());
 +        if (volumeStore != null) {
 +            if (volumeStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) {
 +                s_logger.debug("Volume: " + volume.getName() + " is currently being uploaded; cant' delete it.");
 +                future.complete(result);
 +                return future;
 +            }
 +        }
 +
 +        VolumeVO vol = volDao.findById(volume.getId());
 +        if (vol == null) {
 +            s_logger.debug("Volume " + volume.getId() + " is not found");
 +            future.complete(result);
 +            return future;
 +        }
 +
 +        if (!volumeExistsOnPrimary(vol)) {
 +            // not created on primary store
 +            if (volumeStore == null) {
 +                // also not created on secondary store
 +                if (s_logger.isDebugEnabled()) {
 +                    s_logger.debug("Marking volume that was never created as destroyed: " + vol);
 +                }
 +                volDao.remove(vol.getId());
 +                future.complete(result);
 +                return future;
 +            }
 +        }
 +        VolumeObject vo = (VolumeObject)volume;
 +
 +        if (volume.getDataStore().getRole() == DataStoreRole.Image) {
 +            // no need to change state in volumes table
 +            volume.processEventOnly(Event.DestroyRequested);
 +        } else if (volume.getDataStore().getRole() == DataStoreRole.Primary) {
 +            volume.processEvent(Event.ExpungeRequested);
 +        }
 +
 +        DeleteVolumeContext<VolumeApiResult> context = new DeleteVolumeContext<VolumeApiResult>(null, vo, future);
 +        AsyncCallbackDispatcher<VolumeServiceImpl, CommandResult> caller = AsyncCallbackDispatcher.create(this);
 +        caller.setCallback(caller.getTarget().deleteVolumeCallback(null, null)).setContext(context);
 +
 +        volume.getDataStore().getDriver().deleteAsync(volume.getDataStore(), volume, caller);
 +        return future;
 +    }
 +
 +    private boolean volumeExistsOnPrimary(VolumeVO vol) {
 +        Long poolId = vol.getPoolId();
 +
 +        if (poolId == null) {
 +            return false;
 +        }
 +
 +        PrimaryDataStore primaryStore = dataStoreMgr.getPrimaryDataStore(poolId);
 +
 +        if (primaryStore == null) {
 +            return false;
 +        }
 +
 +        if (primaryStore.isManaged()) {
 +            return true;
 +        }
 +
 +        String volumePath = vol.getPath();
 +
 +        if (volumePath == null || volumePath.trim().isEmpty()) {
 +            return false;
 +        }
 +
 +        return true;
 +    }
 +
 +    public Void deleteVolumeCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CommandResult> callback, DeleteVolumeContext<VolumeApiResult> context) {
 +        CommandResult result = callback.getResult();
 +        VolumeObject vo = context.getVolume();
 +        VolumeApiResult apiResult = new VolumeApiResult(vo);
 +        try {
 +            if (result.isSuccess()) {
 +                vo.processEvent(Event.OperationSuccessed);
 +                if (canVolumeBeRemoved(vo.getId())) {
 +                    s_logger.info("Volume " + vo.getId() + " is not referred anywhere, remove it from volumes table");
 +                    volDao.remove(vo.getId());
 +                }
 +
 +                SnapshotDataStoreVO snapStoreVo = _snapshotStoreDao.findByVolume(vo.getId(), DataStoreRole.Primary);
 +
 +                if (snapStoreVo != null) {
 +                    long storagePoolId = snapStoreVo.getDataStoreId();
 +                    StoragePoolVO storagePoolVO = storagePoolDao.findById(storagePoolId);
 +
 +                    if (storagePoolVO.isManaged()) {
 +                        DataStore primaryDataStore = dataStoreMgr.getPrimaryDataStore(storagePoolId);
 +                        Map<String, String> mapCapabilities = primaryDataStore.getDriver().getCapabilities();
 +
 +                        String value = mapCapabilities.get(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString());
 +                        Boolean supportsStorageSystemSnapshots = new Boolean(value);
 +
 +                        if (!supportsStorageSystemSnapshots) {
 +                            _snapshotStoreDao.remove(snapStoreVo.getId());
 +                        }
 +                    } else {
 +                        _snapshotStoreDao.remove(snapStoreVo.getId());
 +                    }
 +                }
 +            } else {
 +                vo.processEvent(Event.OperationFailed);
 +                apiResult.setResult(result.getResult());
 +            }
 +        } catch (Exception e) {
 +            s_logger.debug("ignore delete volume status update failure, it will be picked up by storage clean up thread later", e);
 +        }
 +        context.getFuture().complete(apiResult);
 +        return null;
 +    }
 +
 +    @Override
 +    public boolean cloneVolume(long volumeId, long baseVolId) {
 +        // TODO Auto-generated method stub
 +        return false;
 +    }
 +
 +    @Override
 +    public VolumeEntity getVolumeEntity(long volumeId) {
 +        return null;
 +    }
 +
 +    private class ManagedCreateBaseImageContext<T> extends AsyncRpcContext<T> {
 +        private final VolumeInfo _volumeInfo;
 +        private final PrimaryDataStore _primaryDataStore;
 +        private final TemplateInfo _templateInfo;
 +        private final AsyncCallFuture<VolumeApiResult> _future;
 +
 +        public ManagedCreateBaseImageContext(AsyncCompletionCallback<T> callback, VolumeInfo volumeInfo, PrimaryDataStore primaryDatastore, TemplateInfo templateInfo,
 +                AsyncCallFuture<VolumeApiResult> future) {
 +            super(callback);
 +
 +            _volumeInfo = volumeInfo;
 +            _primaryDataStore = primaryDatastore;
 +            _templateInfo = templateInfo;
 +            _future = future;
 +        }
 +
 +        public VolumeInfo getVolumeInfo() {
 +            return _volumeInfo;
 +        }
 +
 +        public PrimaryDataStore getPrimaryDataStore() {
 +            return _primaryDataStore;
 +        }
 +
 +        public TemplateInfo getTemplateInfo() {
 +            return _templateInfo;
 +        }
 +
 +        public AsyncCallFuture<VolumeApiResult> getFuture() {
 +            return _future;
 +        }
 +    }
 +
 +    class CreateBaseImageContext<T> extends AsyncRpcContext<T> {
 +        private final VolumeInfo volume;
 +        private final PrimaryDataStore dataStore;
 +        private final TemplateInfo srcTemplate;
 +        private final AsyncCallFuture<VolumeApiResult> future;
 +        final DataObject destObj;
 +        long templatePoolId;
 +
 +        public CreateBaseImageContext(AsyncCompletionCallback<T> callback, VolumeInfo volume, PrimaryDataStore datastore, TemplateInfo srcTemplate, AsyncCallFuture<VolumeApiResult> future,
 +                DataObject destObj, long templatePoolId) {
 +            super(callback);
 +            this.volume = volume;
 +            this.dataStore = datastore;
 +            this.future = future;
 +            this.srcTemplate = srcTemplate;
 +            this.destObj = destObj;
 +            this.templatePoolId = templatePoolId;
 +        }
 +
 +        public VolumeInfo getVolume() {
 +            return this.volume;
 +        }
 +
 +        public PrimaryDataStore getDataStore() {
 +            return this.dataStore;
 +        }
 +
 +        public TemplateInfo getSrcTemplate() {
 +            return this.srcTemplate;
 +        }
 +
 +        public AsyncCallFuture<VolumeApiResult> getFuture() {
 +            return this.future;
 +        }
 +
 +        public long getTemplatePoolId() {
 +            return templatePoolId;
 +        }
 +
 +    }
 +
 +    private TemplateInfo waitForTemplateDownloaded(PrimaryDataStore store, TemplateInfo template) {
 +        int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
 +        int sleepTime = 120;
 +        int tries = storagePoolMaxWaitSeconds / sleepTime;
 +        while (tries > 0) {
 +            TemplateInfo tmpl = store.getTemplate(template.getId());
 +            if (tmpl != null) {
 +                return tmpl;
 +            }
 +            try {
 +                Thread.sleep(sleepTime * 1000);
 +            } catch (InterruptedException e) {
 +                s_logger.debug("waiting for template download been interrupted: " + e.toString());
 +            }
 +            tries--;
 +        }
 +        return null;
 +    }
 +
 +    @DB
 +    protected void createBaseImageAsync(VolumeInfo volume, PrimaryDataStore dataStore, TemplateInfo template, AsyncCallFuture<VolumeApiResult> future) {
 +        DataObject templateOnPrimaryStoreObj = dataStore.create(template);
 +
 +        VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(dataStore.getId(), template.getId());
 +        if (templatePoolRef == null) {
 +            throw new CloudRuntimeException("Failed to find template " + template.getUniqueName() + " in storage pool " + dataStore.getId());
 +        } else {
 +            if (s_logger.isDebugEnabled()) {
 +                s_logger.debug("Found template " + template.getUniqueName() + " in storage pool " + dataStore.getId() + " with VMTemplateStoragePool id: " + templatePoolRef.getId());
 +            }
 +        }
 +        long templatePoolRefId = templatePoolRef.getId();
 +        CreateBaseImageContext<CreateCmdResult> context = new CreateBaseImageContext<CreateCmdResult>(null, volume, dataStore, template, future, templateOnPrimaryStoreObj, templatePoolRefId);
 +        AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +        caller.setCallback(caller.getTarget().copyBaseImageCallback(null, null)).setContext(context);
 +
 +        int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
 +        if (s_logger.isDebugEnabled()) {
 +            s_logger.debug("Acquire lock on VMTemplateStoragePool " + templatePoolRefId + " with timeout " + storagePoolMaxWaitSeconds + " seconds");
 +        }
 +        templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, storagePoolMaxWaitSeconds);
 +
 +        if (templatePoolRef == null) {
 +            if (s_logger.isDebugEnabled()) {
 +                s_logger.info("Unable to acquire lock on VMTemplateStoragePool " + templatePoolRefId);
 +            }
 +            templatePoolRef = _tmpltPoolDao.findByPoolTemplate(dataStore.getId(), template.getId());
 +            if (templatePoolRef != null && templatePoolRef.getState() == ObjectInDataStoreStateMachine.State.Ready) {
 +                s_logger.info(
 +                        "Unable to acquire lock on VMTemplateStoragePool " + templatePoolRefId + ", But Template " + template.getUniqueName() + " is already copied to primary storage, skip copying");
 +                createVolumeFromBaseImageAsync(volume, templateOnPrimaryStoreObj, dataStore, future);
 +                return;
 +            }
 +            throw new CloudRuntimeException("Unable to acquire lock on VMTemplateStoragePool: " + templatePoolRefId);
 +        }
 +
 +        if (s_logger.isDebugEnabled()) {
 +            s_logger.info("lock is acquired for VMTemplateStoragePool " + templatePoolRefId);
 +        }
 +        try {
 +            if (templatePoolRef.getState() == ObjectInDataStoreStateMachine.State.Ready) {
 +                s_logger.info("Template " + template.getUniqueName() + " is already copied to primary storage, skip copying");
 +                createVolumeFromBaseImageAsync(volume, templateOnPrimaryStoreObj, dataStore, future);
 +                return;
 +            }
 +            templateOnPrimaryStoreObj.processEvent(Event.CreateOnlyRequested);
 +            motionSrv.copyAsync(template, templateOnPrimaryStoreObj, caller);
 +        } catch (Throwable e) {
 +            s_logger.debug("failed to create template on storage", e);
 +            templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
 +            dataStore.create(template);  // make sure that template_spool_ref entry is still present so that the second thread can acquire the lock
 +            VolumeApiResult result = new VolumeApiResult(volume);
 +            result.setResult(e.toString());
 +            future.complete(result);
 +        } finally {
 +            if (s_logger.isDebugEnabled()) {
 +                s_logger.info("releasing lock for VMTemplateStoragePool " + templatePoolRefId);
 +            }
 +            _tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
 +        }
 +        return;
 +    }
 +
 +    protected Void managedCopyBaseImageCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, ManagedCreateBaseImageContext<VolumeApiResult> context) {
 +        CopyCommandResult result = callback.getResult();
 +        VolumeInfo volumeInfo = context.getVolumeInfo();
 +        VolumeApiResult res = new VolumeApiResult(volumeInfo);
 +
 +        if (result.isSuccess()) {
 +            // volumeInfo.processEvent(Event.OperationSuccessed, result.getAnswer());
 +
 +            VolumeVO volume = volDao.findById(volumeInfo.getId());
 +            CopyCmdAnswer answer = (CopyCmdAnswer)result.getAnswer();
 +            TemplateObjectTO templateObjectTo = (TemplateObjectTO)answer.getNewData();
 +
 +            volume.setPath(templateObjectTo.getPath());
 +
 +            if (templateObjectTo.getFormat() != null) {
 +                volume.setFormat(templateObjectTo.getFormat());
 +            }
 +
 +            volDao.update(volume.getId(), volume);
 +        } else {
 +            volumeInfo.processEvent(Event.DestroyRequested);
 +
 +            res.setResult(result.getResult());
 +        }
 +
 +        AsyncCallFuture<VolumeApiResult> future = context.getFuture();
 +
 +        future.complete(res);
 +
 +        return null;
 +    }
 +
 +    protected Void createManagedTemplateImageCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> callback, CreateVolumeContext<CreateCmdResult> context) {
 +        CreateCmdResult result = callback.getResult();
 +        VolumeApiResult res = new VolumeApiResult(null);
 +
 +        res.setResult(result.getResult());
 +
 +        AsyncCallFuture<VolumeApiResult> future = context.getFuture();
 +        DataObject templateOnPrimaryStoreObj = context.getVolume();
 +
 +        if (result.isSuccess()) {
 +            ((TemplateObject)templateOnPrimaryStoreObj).setInstallPath(result.getPath());
 +            templateOnPrimaryStoreObj.processEvent(Event.OperationSuccessed, result.getAnswer());
 +        } else {
 +            templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
 +        }
 +
 +        future.complete(res);
 +
 +        return null;
 +    }
 +
 +    protected Void copyManagedTemplateCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CreateBaseImageContext<VolumeApiResult> context) {
 +        CopyCommandResult result = callback.getResult();
 +        VolumeApiResult res = new VolumeApiResult(context.getVolume());
 +
 +        res.setResult(result.getResult());
 +
 +        AsyncCallFuture<VolumeApiResult> future = context.getFuture();
 +        DataObject templateOnPrimaryStoreObj = context.destObj;
 +
 +        if (result.isSuccess()) {
 +            templateOnPrimaryStoreObj.processEvent(Event.OperationSuccessed, result.getAnswer());
 +        } else {
 +            templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
 +        }
 +
 +        future.complete(res);
 +
 +        return null;
 +    }
 +
 +    @DB
 +    protected Void copyBaseImageCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CreateBaseImageContext<VolumeApiResult> context) {
 +        CopyCommandResult result = callback.getResult();
 +        VolumeApiResult res = new VolumeApiResult(context.getVolume());
 +
 +        AsyncCallFuture<VolumeApiResult> future = context.getFuture();
 +        DataObject templateOnPrimaryStoreObj = context.destObj;
 +        if (!result.isSuccess()) {
 +            templateOnPrimaryStoreObj.processEvent(Event.OperationFailed);
 +            res.setResult(result.getResult());
 +            future.complete(res);
 +            return null;
 +        }
 +
 +        templateOnPrimaryStoreObj.processEvent(Event.OperationSuccessed, result.getAnswer());
 +        createVolumeFromBaseImageAsync(context.volume, templateOnPrimaryStoreObj, context.dataStore, future);
 +        return null;
 +    }
 +
 +    private class CreateVolumeFromBaseImageContext<T> extends AsyncRpcContext<T> {
 +        private final DataObject vo;
 +        private final AsyncCallFuture<VolumeApiResult> future;
 +        private final DataObject templateOnStore;
 +        private final SnapshotInfo snapshot;
 +
 +        public CreateVolumeFromBaseImageContext(AsyncCompletionCallback<T> callback, DataObject vo, DataStore primaryStore, DataObject templateOnStore, AsyncCallFuture<VolumeApiResult> future,
 +                SnapshotInfo snapshot) {
 +            super(callback);
 +            this.vo = vo;
 +            this.future = future;
 +            this.templateOnStore = templateOnStore;
 +            this.snapshot = snapshot;
 +        }
 +
 +        public AsyncCallFuture<VolumeApiResult> getFuture() {
 +            return this.future;
 +        }
 +    }
 +
 +    @DB
 +    protected void createVolumeFromBaseImageAsync(VolumeInfo volume, DataObject templateOnPrimaryStore, PrimaryDataStore pd, AsyncCallFuture<VolumeApiResult> future) {
 +        DataObject volumeOnPrimaryStorage = pd.create(volume);
 +        volumeOnPrimaryStorage.processEvent(Event.CreateOnlyRequested);
 +
 +        CreateVolumeFromBaseImageContext<VolumeApiResult> context = new CreateVolumeFromBaseImageContext<VolumeApiResult>(null, volumeOnPrimaryStorage, pd, templateOnPrimaryStore, future, null);
 +        AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +        caller.setCallback(caller.getTarget().createVolumeFromBaseImageCallBack(null, null));
 +        caller.setContext(context);
 +
 +        motionSrv.copyAsync(context.templateOnStore, volumeOnPrimaryStorage, caller);
 +        return;
 +    }
 +
 +    @DB
 +    protected Void createVolumeFromBaseImageCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CreateVolumeFromBaseImageContext<VolumeApiResult> context) {
 +        DataObject vo = context.vo;
 +        DataObject tmplOnPrimary = context.templateOnStore;
 +        CopyCommandResult result = callback.getResult();
 +        VolumeApiResult volResult = new VolumeApiResult((VolumeObject)vo);
 +
 +        if (result.isSuccess()) {
 +            vo.processEvent(Event.OperationSuccessed, result.getAnswer());
 +        } else {
 +
 +            vo.processEvent(Event.OperationFailed);
 +            volResult.setResult(result.getResult());
 +            // hack for Vmware: host is down, previously download template to the host needs to be re-downloaded, so we need to reset
 +            // template_spool_ref entry here to NOT_DOWNLOADED and Allocated state
 +            Answer ans = result.getAnswer();
 +            if (ans != null && ans instanceof CopyCmdAnswer && ans.getDetails().contains("request template reload")) {
 +                if (tmplOnPrimary != null) {
 +                    s_logger.info("Reset template_spool_ref entry so that vmware template can be reloaded in next try");
 +                    VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(tmplOnPrimary.getDataStore().getId(), tmplOnPrimary.getId());
 +                    if (templatePoolRef != null) {
 +                        long templatePoolRefId = templatePoolRef.getId();
 +                        templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, 1200);
 +                        try {
 +                            if (templatePoolRef == null) {
 +                                s_logger.warn("Reset Template State On Pool failed - unable to lock TemplatePoolRef " + templatePoolRefId);
 +                            } else {
 +                                templatePoolRef.setTemplateSize(0);
 +                                templatePoolRef.setDownloadState(VMTemplateStorageResourceAssoc.Status.NOT_DOWNLOADED);
 +                                templatePoolRef.setState(ObjectInDataStoreStateMachine.State.Allocated);
 +
 +                                _tmpltPoolDao.update(templatePoolRefId, templatePoolRef);
 +                            }
 +                        } finally {
 +                            _tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
 +                        }
 +                    }
 +                }
 +            }
 +        }
 +
 +        AsyncCallFuture<VolumeApiResult> future = context.getFuture();
 +        future.complete(volResult);
 +        return null;
 +    }
 +
 +    /**
 +     * Creates a template volume on managed storage, which will be used for creating ROOT volumes by cloning.
 +     *
 +     * @param srcTemplateInfo Source template on secondary storage
 +     * @param destPrimaryDataStore Managed storage on which we need to create the volume
 +     */
 +    private TemplateInfo createManagedTemplateVolume(TemplateInfo srcTemplateInfo, PrimaryDataStore destPrimaryDataStore) {
 +        // create a template volume on primary storage
 +        AsyncCallFuture<VolumeApiResult> createTemplateFuture = new AsyncCallFuture<>();
 +        TemplateInfo templateOnPrimary = (TemplateInfo)destPrimaryDataStore.create(srcTemplateInfo);
 +
 +        VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destPrimaryDataStore.getId(), templateOnPrimary.getId());
 +
 +        if (templatePoolRef == null) {
 +            throw new CloudRuntimeException("Failed to find template " + srcTemplateInfo.getUniqueName() + " in storage pool " + destPrimaryDataStore.getId());
 +        }
 +
 +        // At this point, we have an entry in the DB that points to our cached template.
 +        // We need to lock it as there may be other VMs that may get started using the same template.
 +        // We want to avoid having to create multiple cache copies of the same template.
 +
 +        int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
 +        long templatePoolRefId = templatePoolRef.getId();
 +
 +        templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, storagePoolMaxWaitSeconds);
 +
 +        if (templatePoolRef == null) {
 +            throw new CloudRuntimeException("Unable to acquire lock on VMTemplateStoragePool: " + templatePoolRefId);
 +        }
 +
 +        // Template already exists
 +        if (templatePoolRef.getState() == ObjectInDataStoreStateMachine.State.Ready) {
 +            _tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
 +
 +            return templateOnPrimary;
 +        }
 +
 +        try {
 +            // create a cache volume on the back-end
 +
 +            templateOnPrimary.processEvent(Event.CreateOnlyRequested);
 +
 +            CreateVolumeContext<CreateCmdResult> createContext = new CreateVolumeContext<>(null, templateOnPrimary, createTemplateFuture);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> createCaller = AsyncCallbackDispatcher.create(this);
 +
 +            createCaller.setCallback(createCaller.getTarget().createManagedTemplateImageCallback(null, null)).setContext(createContext);
 +
 +            destPrimaryDataStore.getDriver().createAsync(destPrimaryDataStore, templateOnPrimary, createCaller);
 +
 +            VolumeApiResult result = createTemplateFuture.get();
 +
 +            if (result.isFailed()) {
 +                String errMesg = result.getResult();
 +
 +                throw new CloudRuntimeException("Unable to create template " + templateOnPrimary.getId() + " on primary storage " + destPrimaryDataStore.getId() + ":" + errMesg);
 +            }
 +        } catch (Throwable e) {
 +            s_logger.debug("Failed to create template volume on storage", e);
 +
 +            templateOnPrimary.processEvent(Event.OperationFailed);
 +
 +            throw new CloudRuntimeException(e.getMessage());
 +        } finally {
 +            _tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
 +        }
 +
 +        return templateOnPrimary;
 +    }
 +
 +    /**
 +     * This function copies a template from secondary storage to a template volume
 +     * created on managed storage. This template volume will be used as a cache.
 +     * Instead of copying the template to a ROOT volume every time, a clone is performed instead.
 +     *
 +     * @param srcTemplateInfo Source from which to copy the template
 +     * @param templateOnPrimary Dest to copy to
 +     * @param templatePoolRef Template reference on primary storage (entry in the template_spool_ref)
 +     * @param destPrimaryDataStore The managed primary storage
 +     * @param destHost The host that we will use for the copy
 +     */
 +    private void copyTemplateToManagedTemplateVolume(TemplateInfo srcTemplateInfo, TemplateInfo templateOnPrimary, VMTemplateStoragePoolVO templatePoolRef, PrimaryDataStore destPrimaryDataStore,
 +            Host destHost) {
 +        AsyncCallFuture<VolumeApiResult> copyTemplateFuture = new AsyncCallFuture<>();
 +        int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600);
 +        long templatePoolRefId = templatePoolRef.getId();
 +
 +        templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, storagePoolMaxWaitSeconds);
 +
 +        if (templatePoolRef == null) {
 +            throw new CloudRuntimeException("Unable to acquire lock on VMTemplateStoragePool: " + templatePoolRefId);
 +        }
 +
 +        if (templatePoolRef.getDownloadState() == Status.DOWNLOADED) {
 +            // There can be cases where we acquired the lock, but the template
 +            // was already copied by a previous thread. Just return in that case.
 +
 +            s_logger.debug("Template already downloaded, nothing to do");
 +
 +            return;
 +        }
 +
 +        try {
 +            // copy the template from sec storage to the created volume
 +            CreateBaseImageContext<CreateCmdResult> copyContext = new CreateBaseImageContext<>(null, null, destPrimaryDataStore, srcTemplateInfo, copyTemplateFuture, templateOnPrimary,
 +                    templatePoolRefId);
 +
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> copyCaller = AsyncCallbackDispatcher.create(this);
 +            copyCaller.setCallback(copyCaller.getTarget().copyManagedTemplateCallback(null, null)).setContext(copyContext);
 +
 +            // Populate details which will be later read by the storage subsystem.
 +            Map<String, String> details = new HashMap<>();
 +
 +            details.put(PrimaryDataStore.MANAGED, Boolean.TRUE.toString());
 +            details.put(PrimaryDataStore.STORAGE_HOST, destPrimaryDataStore.getHostAddress());
 +            details.put(PrimaryDataStore.STORAGE_PORT, String.valueOf(destPrimaryDataStore.getPort()));
 +            details.put(PrimaryDataStore.MANAGED_STORE_TARGET, templateOnPrimary.getInstallPath());
 +            details.put(PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME, srcTemplateInfo.getUniqueName());
 +            details.put(PrimaryDataStore.REMOVE_AFTER_COPY, Boolean.TRUE.toString());
 +            details.put(PrimaryDataStore.VOLUME_SIZE, String.valueOf(templateOnPrimary.getSize()));
 +
 +            ChapInfo chapInfo = getChapInfo(templateOnPrimary, destPrimaryDataStore);
 +
 +            if (chapInfo != null) {
 +                details.put(PrimaryDataStore.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
 +                details.put(PrimaryDataStore.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
 +                details.put(PrimaryDataStore.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
 +                details.put(PrimaryDataStore.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
 +            }
 +
 +            templateOnPrimary.processEvent(Event.CopyingRequested);
 +
 +            destPrimaryDataStore.setDetails(details);
 +
 +            grantAccess(templateOnPrimary, destHost, destPrimaryDataStore);
 +
 +            VolumeApiResult result;
 +
 +            try {
 +                motionSrv.copyAsync(srcTemplateInfo, templateOnPrimary, destHost, copyCaller);
 +
 +                result = copyTemplateFuture.get();
 +            } finally {
 +                revokeAccess(templateOnPrimary, destHost, destPrimaryDataStore);
 +
 +                if (HypervisorType.VMware.equals(destHost.getHypervisorType())) {
 +                    details.put(ModifyTargetsCommand.IQN, templateOnPrimary.getInstallPath());
 +
 +                    List<Map<String, String>> targets = new ArrayList<>();
 +
 +                    targets.add(details);
 +
 +                    removeDynamicTargets(destHost.getId(), targets);
 +                }
 +            }
 +
 +            if (result.isFailed()) {
 +                throw new CloudRuntimeException("Failed to copy template " + templateOnPrimary.getId() + " to primary storage " + destPrimaryDataStore.getId() + ": " + result.getResult());
 +                // XXX: I find it is useful to destroy the volume on primary storage instead of another thread trying the copy again because I've seen
 +                // something weird happens to the volume (XenServer creates an SR, but the VDI copy can fail).
 +                // For now, I just retry the copy.
 +            }
 +        } catch (Throwable e) {
 +            s_logger.debug("Failed to create a template on primary storage", e);
 +
 +            templateOnPrimary.processEvent(Event.OperationFailed);
 +
 +            throw new CloudRuntimeException(e.getMessage());
 +        } finally {
 +            _tmpltPoolDao.releaseFromLockTable(templatePoolRefId);
 +        }
 +    }
 +
 +    private void removeDynamicTargets(long hostId, List<Map<String, String>> targets) {
 +        ModifyTargetsCommand cmd = new ModifyTargetsCommand();
 +
 +        cmd.setTargets(targets);
 +        cmd.setApplyToAllHostsInCluster(true);
 +        cmd.setAdd(false);
 +        cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC);
 +
 +        sendModifyTargetsCommand(cmd, hostId);
 +    }
 +
 +    private void sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) {
 +        Answer answer = agentMgr.easySend(hostId, cmd);
 +
 +        if (answer == null) {
 +            String msg = "Unable to get an answer to the modify targets command";
 +
 +            s_logger.warn(msg);
 +        } else if (!answer.getResult()) {
 +            String msg = "Unable to modify target on the following host: " + hostId;
 +
 +            s_logger.warn(msg);
 +        }
 +    }
 +
 +    /**
 +     * Clones the template volume on managed storage to the ROOT volume
 +     *
 +     * @param volumeInfo ROOT volume to create
 +     * @param templateOnPrimary Template from which to clone the ROOT volume
 +     * @param destPrimaryDataStore Primary storage of the volume
 +     * @param future For async
 +     */
 +    private void createManagedVolumeCloneTemplateAsync(VolumeInfo volumeInfo, TemplateInfo templateOnPrimary, PrimaryDataStore destPrimaryDataStore, AsyncCallFuture<VolumeApiResult> future) {
 +        VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destPrimaryDataStore.getId(), templateOnPrimary.getId());
 +
 +        if (templatePoolRef == null) {
 +            throw new CloudRuntimeException("Failed to find template " + templateOnPrimary.getUniqueName() + " in storage pool " + destPrimaryDataStore.getId());
 +        }
 +
 +        //XXX: not sure if this the right thing to do here. We can always fallback to the "copy from sec storage"
 +        if (templatePoolRef.getDownloadState() == Status.NOT_DOWNLOADED) {
 +            throw new CloudRuntimeException("Template " + templateOnPrimary.getUniqueName() + " has not been downloaded to primary storage.");
 +        }
 +
 +        try {
 +            volumeInfo.processEvent(Event.CreateOnlyRequested);
 +
 +            CreateVolumeFromBaseImageContext<VolumeApiResult> context = new CreateVolumeFromBaseImageContext<>(null, volumeInfo, destPrimaryDataStore, templateOnPrimary, future, null);
 +
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +
 +            caller.setCallback(caller.getTarget().createVolumeFromBaseImageCallBack(null, null));
 +            caller.setContext(context);
 +
 +            motionSrv.copyAsync(templateOnPrimary, volumeInfo, caller);
 +        } catch (Throwable e) {
 +            s_logger.debug("Failed to clone template on primary storage", e);
 +
 +            volumeInfo.processEvent(Event.OperationFailed);
 +
 +            throw new CloudRuntimeException(e.getMessage());
 +        }
 +    }
 +
 +    private void createManagedVolumeCopyTemplateAsync(VolumeInfo volumeInfo, PrimaryDataStore primaryDataStore, TemplateInfo srcTemplateInfo, Host destHost, AsyncCallFuture<VolumeApiResult> future) {
 +        try {
 +            // Create a volume on managed storage.
 +
 +            TemplateInfo destTemplateInfo = (TemplateInfo)primaryDataStore.create(srcTemplateInfo, false);
 +
 +            AsyncCallFuture<VolumeApiResult> createVolumeFuture = createVolumeAsync(volumeInfo, primaryDataStore);
 +            VolumeApiResult createVolumeResult = createVolumeFuture.get();
 +
 +            if (createVolumeResult.isFailed()) {
 +                throw new CloudRuntimeException("Creation of a volume failed: " + createVolumeResult.getResult());
 +            }
 +
 +            // Refresh the volume info from the DB.
 +            volumeInfo = volFactory.getVolume(volumeInfo.getId(), primaryDataStore);
 +
 +            ManagedCreateBaseImageContext<CreateCmdResult> context = new ManagedCreateBaseImageContext<CreateCmdResult>(null, volumeInfo, primaryDataStore, srcTemplateInfo, future);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +
 +            caller.setCallback(caller.getTarget().managedCopyBaseImageCallback(null, null)).setContext(context);
 +
 +            Map<String, String> details = new HashMap<String, String>();
 +
 +            details.put(PrimaryDataStore.MANAGED, Boolean.TRUE.toString());
 +            details.put(PrimaryDataStore.STORAGE_HOST, primaryDataStore.getHostAddress());
 +            details.put(PrimaryDataStore.STORAGE_PORT, String.valueOf(primaryDataStore.getPort()));
 +            // for managed storage, the storage repository (XenServer) or datastore (ESX) name is based off of the iScsiName property of a volume
 +            details.put(PrimaryDataStore.MANAGED_STORE_TARGET, volumeInfo.get_iScsiName());
 +            details.put(PrimaryDataStore.MANAGED_STORE_TARGET_ROOT_VOLUME, volumeInfo.getName());
 +            details.put(PrimaryDataStore.VOLUME_SIZE, String.valueOf(volumeInfo.getSize()));
 +
 +            ChapInfo chapInfo = getChapInfo(volumeInfo, primaryDataStore);
 +
 +            if (chapInfo != null) {
 +                details.put(PrimaryDataStore.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
 +                details.put(PrimaryDataStore.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
 +                details.put(PrimaryDataStore.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
 +                details.put(PrimaryDataStore.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
 +            }
 +
 +            primaryDataStore.setDetails(details);
 +
 +            grantAccess(volumeInfo, destHost, primaryDataStore);
 +
 +            try {
 +                motionSrv.copyAsync(srcTemplateInfo, destTemplateInfo, destHost, caller);
 +            } finally {
 +                revokeAccess(volumeInfo, destHost, primaryDataStore);
 +            }
 +        } catch (Throwable t) {
 +            String errMsg = t.toString();
 +
 +            volumeInfo.processEvent(Event.DestroyRequested);
 +
 +            try {
 +                AsyncCallFuture<VolumeApiResult> expungeVolumeFuture = expungeVolumeAsync(volumeInfo);
 +
 +                VolumeApiResult expungeVolumeResult = expungeVolumeFuture.get();
 +
 +                if (expungeVolumeResult.isFailed()) {
 +                    errMsg += " : Failed to expunge a volume that was created";
 +                }
 +            } catch (Exception ex) {
 +                errMsg += " : " + ex.getMessage();
 +            }
 +
 +            VolumeApiResult result = new VolumeApiResult(volumeInfo);
 +
 +            result.setResult(errMsg);
 +
 +            future.complete(result);
 +        }
 +    }
 +
 +    @Override
 +    public AsyncCallFuture<VolumeApiResult> createManagedStorageVolumeFromTemplateAsync(VolumeInfo volumeInfo, long destDataStoreId, TemplateInfo srcTemplateInfo, long destHostId) {
 +        PrimaryDataStore destPrimaryDataStore = dataStoreMgr.getPrimaryDataStore(destDataStoreId);
 +        Host destHost = _hostDao.findById(destHostId);
 +
 +        if (destHost == null) {
 +            throw new CloudRuntimeException("Destination host should not be null.");
 +        }
 +
 +        Boolean storageCanCloneVolume = new Boolean(destPrimaryDataStore.getDriver().getCapabilities().get(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString()));
 +
 +        boolean computeSupportsVolumeClone = computeSupportsVolumeClone(destHost.getDataCenterId(), destHost.getHypervisorType());
 +
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<>();
 +
 +        if (storageCanCloneVolume && computeSupportsVolumeClone) {
 +            s_logger.debug("Storage " + destDataStoreId + " can support cloning using a cached template and compute side is OK with volume cloning.");
 +
 +            TemplateInfo templateOnPrimary = destPrimaryDataStore.getTemplate(srcTemplateInfo.getId());
 +
 +            if (templateOnPrimary == null) {
 +                templateOnPrimary = createManagedTemplateVolume(srcTemplateInfo, destPrimaryDataStore);
 +
 +                if (templateOnPrimary == null) {
 +                    throw new CloudRuntimeException("Failed to create template " + srcTemplateInfo.getUniqueName() + " on primary storage: " + destDataStoreId);
 +                }
 +            }
 +
 +            // Copy the template to the template volume.
 +            VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destPrimaryDataStore.getId(), templateOnPrimary.getId());
 +
 +            if (templatePoolRef == null) {
 +                throw new CloudRuntimeException("Failed to find template " + srcTemplateInfo.getUniqueName() + " in storage pool " + destPrimaryDataStore.getId());
 +            }
 +
 +            if (templatePoolRef.getDownloadState() == Status.NOT_DOWNLOADED) {
 +                copyTemplateToManagedTemplateVolume(srcTemplateInfo, templateOnPrimary, templatePoolRef, destPrimaryDataStore, destHost);
 +            }
 +
 +            // We have a template on primary storage. Clone it to new volume.
 +            s_logger.debug("Creating a clone from template on primary storage " + destDataStoreId);
 +
 +            createManagedVolumeCloneTemplateAsync(volumeInfo, templateOnPrimary, destPrimaryDataStore, future);
 +        } else {
 +            s_logger.debug("Primary storage does not support cloning or no support for UUID resigning on the host side; copying the template normally");
 +
 +            createManagedVolumeCopyTemplateAsync(volumeInfo, destPrimaryDataStore, srcTemplateInfo, destHost, future);
 +        }
 +
 +        return future;
 +    }
 +
 +    private boolean computeSupportsVolumeClone(long zoneId, HypervisorType hypervisorType) {
 +        if (HypervisorType.VMware.equals(hypervisorType) || HypervisorType.KVM.equals(hypervisorType)) {
 +            return true;
 +        }
 +
 +        return getHost(zoneId, hypervisorType, true) != null;
 +    }
 +
 +    private HostVO getHost(Long zoneId, HypervisorType hypervisorType, boolean computeClusterMustSupportResign) {
 +        if (zoneId == null) {
 +            throw new CloudRuntimeException("Zone ID cannot be null.");
 +        }
 +
 +        List<? extends Cluster> clusters = mgr.searchForClusters(zoneId, new Long(0), Long.MAX_VALUE, hypervisorType.toString());
 +
 +        if (clusters == null) {
 +            clusters = new ArrayList<>();
 +        }
 +
 +        Collections.shuffle(clusters, new Random(System.nanoTime()));
 +
 +        clusters: for (Cluster cluster : clusters) {
 +            if (cluster.getAllocationState() == AllocationState.Enabled) {
 +                List<HostVO> hosts = _hostDao.findByClusterId(cluster.getId());
 +
 +                if (hosts != null) {
 +                    Collections.shuffle(hosts, new Random(System.nanoTime()));
 +
 +                    for (HostVO host : hosts) {
 +                        if (host.getResourceState() == ResourceState.Enabled) {
 +                            if (computeClusterMustSupportResign) {
 +                                if (clusterDao.getSupportsResigning(cluster.getId())) {
 +                                    return host;
 +                                } else {
 +                                    // no other host in the cluster in question should be able to satisfy our requirements here, so move on to the next cluster
 +                                    continue clusters;
 +                                }
 +                            } else {
 +                                return host;
 +                            }
 +                        }
 +                    }
 +                }
 +            }
 +        }
 +
 +        return null;
 +    }
 +
 +    @DB
 +    @Override
 +    public AsyncCallFuture<VolumeApiResult> createVolumeFromTemplateAsync(VolumeInfo volume, long dataStoreId, TemplateInfo template) {
 +        PrimaryDataStore pd = dataStoreMgr.getPrimaryDataStore(dataStoreId);
 +        TemplateInfo templateOnPrimaryStore = pd.getTemplate(template.getId());
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +
 +        if (templateOnPrimaryStore == null) {
 +            createBaseImageAsync(volume, pd, template, future);
 +            return future;
 +        }
 +
 +        createVolumeFromBaseImageAsync(volume, templateOnPrimaryStore, pd, future);
 +        return future;
 +    }
 +
 +    @DB
 +    @Override
 +    public void destroyVolume(long volumeId) {
 +        // mark volume entry in volumes table as destroy state
 +        VolumeInfo vol = volFactory.getVolume(volumeId);
 +        vol.stateTransit(Volume.Event.DestroyRequested);
 +        snapshotMgr.deletePoliciesForVolume(volumeId);
 +
 +        vol.stateTransit(Volume.Event.OperationSucceeded);
 +    }
 +
 +    @Override
 +    public AsyncCallFuture<VolumeApiResult> createVolumeFromSnapshot(VolumeInfo volume, DataStore store, SnapshotInfo snapshot) {
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +
 +        try {
 +            DataObject volumeOnStore = store.create(volume);
 +            volumeOnStore.processEvent(Event.CreateOnlyRequested);
 +            _volumeDetailsDao.addDetail(volume.getId(), SNAPSHOT_ID, Long.toString(snapshot.getId()), false);
 +
 +            CreateVolumeFromBaseImageContext<VolumeApiResult> context = new CreateVolumeFromBaseImageContext<VolumeApiResult>(null, volume, store, volumeOnStore, future, snapshot);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +            caller.setCallback(caller.getTarget().createVolumeFromSnapshotCallback(null, null)).setContext(context);
 +            motionSrv.copyAsync(snapshot, volumeOnStore, caller);
 +        } catch (Exception e) {
 +            s_logger.debug("create volume from snapshot failed", e);
 +            VolumeApiResult result = new VolumeApiResult(volume);
 +            result.setResult(e.toString());
 +            future.complete(result);
 +        }
 +
 +        return future;
 +    }
 +
 +    protected Void createVolumeFromSnapshotCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CreateVolumeFromBaseImageContext<VolumeApiResult> context) {
 +        CopyCommandResult result = callback.getResult();
 +        VolumeInfo volume = (VolumeInfo)context.templateOnStore;
 +        SnapshotInfo snapshot = context.snapshot;
 +        VolumeApiResult apiResult = new VolumeApiResult(volume);
 +        Event event = null;
 +        if (result.isFailed()) {
 +            apiResult.setResult(result.getResult());
 +            event = Event.OperationFailed;
 +        } else {
 +            event = Event.OperationSuccessed;
 +        }
 +
 +        try {
 +            if (result.isSuccess()) {
 +                volume.processEvent(event, result.getAnswer());
 +            } else {
 +                volume.processEvent(event);
 +            }
 +            _volumeDetailsDao.removeDetail(volume.getId(), SNAPSHOT_ID);
 +
 +        } catch (Exception e) {
 +            s_logger.debug("create volume from snapshot failed", e);
 +            apiResult.setResult(e.toString());
 +        }
 +
 +        AsyncCallFuture<VolumeApiResult> future = context.future;
 +        future.complete(apiResult);
 +        return null;
 +    }
 +
 +    protected VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePool pool) {
 +        Long lastPoolId = volume.getPoolId();
 +        String folder = pool.getPath();
 +        // For SMB, pool credentials are also stored in the uri query string.  We trim the query string
 +        // part  here to make sure the credentials do not get stored in the db unencrypted.
 +        if (pool.getPoolType() == StoragePoolType.SMB && folder != null && folder.contains("?")) {
 +            folder = folder.substring(0, folder.indexOf("?"));
 +        }
 +
 +        VolumeVO newVol = new VolumeVO(volume);
 +        newVol.setInstanceId(null);
 +        newVol.setChainInfo(null);
 +        newVol.setPath(null);
 +        newVol.setFolder(folder);
 +        newVol.setPodId(pool.getPodId());
 +        newVol.setPoolId(pool.getId());
 +        newVol.setLastPoolId(lastPoolId);
 +        newVol.setPodId(pool.getPodId());
 +        return volDao.persist(newVol);
 +    }
 +
 +    private class CopyVolumeContext<T> extends AsyncRpcContext<T> {
 +        final VolumeInfo srcVolume;
 +        final VolumeInfo destVolume;
 +        final AsyncCallFuture<VolumeApiResult> future;
 +
 +        public CopyVolumeContext(AsyncCompletionCallback<T> callback, AsyncCallFuture<VolumeApiResult> future, VolumeInfo srcVolume, VolumeInfo destVolume, DataStore destStore) {
 +            super(callback);
 +            this.srcVolume = srcVolume;
 +            this.destVolume = destVolume;
 +            this.future = future;
 +        }
 +
 +    }
 +
 +    protected AsyncCallFuture<VolumeApiResult> copyVolumeFromImageToPrimary(VolumeInfo srcVolume, DataStore destStore) {
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +        VolumeApiResult res = new VolumeApiResult(srcVolume);
 +        VolumeInfo destVolume = null;
 +        try {
 +            destVolume = (VolumeInfo)destStore.create(srcVolume);
 +            destVolume.processEvent(Event.CopyingRequested);
 +            srcVolume.processEvent(Event.CopyingRequested);
 +
 +            CopyVolumeContext<VolumeApiResult> context = new CopyVolumeContext<VolumeApiResult>(null, future, srcVolume, destVolume, destStore);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +            caller.setCallback(caller.getTarget().copyVolumeFromImageToPrimaryCallback(null, null)).setContext(context);
 +
 +            motionSrv.copyAsync(srcVolume, destVolume, caller);
 +            return future;
 +        } catch (Exception e) {
 +            s_logger.error("failed to copy volume from image store", e);
 +            if (destVolume != null) {
 +                destVolume.processEvent(Event.OperationFailed);
 +            }
 +
 +            srcVolume.processEvent(Event.OperationFailed);
 +            res.setResult(e.toString());
 +            future.complete(res);
 +            return future;
 +        }
 +    }
 +
 +    protected Void copyVolumeFromImageToPrimaryCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CopyVolumeContext<VolumeApiResult> context) {
 +        VolumeInfo srcVolume = context.srcVolume;
 +        VolumeInfo destVolume = context.destVolume;
 +        CopyCommandResult result = callback.getResult();
 +        AsyncCallFuture<VolumeApiResult> future = context.future;
 +        VolumeApiResult res = new VolumeApiResult(destVolume);
 +        try {
 +            if (result.isFailed()) {
 +                destVolume.processEvent(Event.OperationFailed);
 +                srcVolume.processEvent(Event.OperationFailed);
 +                res.setResult(result.getResult());
 +                future.complete(res);
 +                return null;
 +            }
 +
 +            srcVolume.processEvent(Event.OperationSuccessed);
 +            destVolume.processEvent(Event.OperationSuccessed, result.getAnswer());
 +            srcVolume.getDataStore().delete(srcVolume);
 +            future.complete(res);
 +        } catch (Exception e) {
 +            res.setResult(e.toString());
 +            future.complete(res);
 +        }
 +        return null;
 +    }
 +
 +    protected AsyncCallFuture<VolumeApiResult> copyVolumeFromPrimaryToImage(VolumeInfo srcVolume, DataStore destStore) {
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +        VolumeApiResult res = new VolumeApiResult(srcVolume);
 +        VolumeInfo destVolume = null;
 +        try {
 +            destVolume = (VolumeInfo)destStore.create(srcVolume);
 +            srcVolume.processEvent(Event.MigrationRequested);    // this is just used for locking that src volume record in DB to avoid using lock
 +            destVolume.processEventOnly(Event.CreateOnlyRequested);
 +
 +            CopyVolumeContext<VolumeApiResult> context = new CopyVolumeContext<VolumeApiResult>(null, future, srcVolume, destVolume, destStore);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +            caller.setCallback(caller.getTarget().copyVolumeFromPrimaryToImageCallback(null, null)).setContext(context);
 +
 +            motionSrv.copyAsync(srcVolume, destVolume, caller);
 +            return future;
 +        } catch (Exception e) {
 +            s_logger.error("failed to copy volume to image store", e);
 +            if (destVolume != null) {
 +                destVolume.getDataStore().delete(destVolume);
 +            }
 +            srcVolume.processEvent(Event.OperationFailed); // unlock source volume record
 +            res.setResult(e.toString());
 +            future.complete(res);
 +            return future;
 +        }
 +    }
 +
 +    protected Void copyVolumeFromPrimaryToImageCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CopyVolumeContext<VolumeApiResult> context) {
 +        VolumeInfo srcVolume = context.srcVolume;
 +        VolumeInfo destVolume = context.destVolume;
 +        CopyCommandResult result = callback.getResult();
 +        AsyncCallFuture<VolumeApiResult> future = context.future;
 +        VolumeApiResult res = new VolumeApiResult(destVolume);
 +        try {
 +            if (result.isFailed()) {
 +                srcVolume.processEvent(Event.OperationFailed); // back to Ready state in Volume table
 +                destVolume.processEventOnly(Event.OperationFailed);
 +                res.setResult(result.getResult());
 +                future.complete(res);
 +            } else {
 +                srcVolume.processEvent(Event.OperationSuccessed); // back to Ready state in Volume table
 +                destVolume.processEventOnly(Event.OperationSuccessed, result.getAnswer());
 +                future.complete(res);
 +            }
 +        } catch (Exception e) {
 +            res.setResult(e.toString());
 +            future.complete(res);
 +        }
 +        return null;
 +    }
 +
 +    @Override
 +    public AsyncCallFuture<VolumeApiResult> copyVolume(VolumeInfo srcVolume, DataStore destStore) {
 +        if (s_logger.isDebugEnabled()) {
 +            DataStore srcStore = srcVolume.getDataStore();
 +            String srcRole = (srcStore != null && srcStore.getRole() != null ? srcVolume.getDataStore().getRole().toString() : "<unknown role>");
 +
 +            String msg = String.format("copying %s(id=%d, role=%s) to %s (id=%d, role=%s)"
 +                    , srcVolume.getName()
 +                    , srcVolume.getId()
 +                    , srcRole
 +                    , destStore.getName()
 +                    , destStore.getId()
 +                    , destStore.getRole());
 +            s_logger.debug(msg);
 +        }
 +
 +        if (srcVolume.getState() == Volume.State.Uploaded) {
 +            return copyVolumeFromImageToPrimary(srcVolume, destStore);
 +        }
 +
 +        if (destStore.getRole() == DataStoreRole.Image) {
 +            return copyVolumeFromPrimaryToImage(srcVolume, destStore);
 +        }
 +
 +        // OfflineVmwareMigration: aren't we missing secondary to secondary in this logic?
 +
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +        VolumeApiResult res = new VolumeApiResult(srcVolume);
 +        try {
 +            if (!snapshotMgr.canOperateOnVolume(srcVolume)) {
 +                s_logger.debug("There are snapshots creating on this volume, can not move this volume");
 +
 +                res.setResult("There are snapshots creating on this volume, can not move this volume");
 +                future.complete(res);
 +                return future;
 +            }
 +
 +            VolumeVO destVol = duplicateVolumeOnAnotherStorage(srcVolume, (StoragePool)destStore);
 +            VolumeInfo destVolume = volFactory.getVolume(destVol.getId(), destStore);
 +            destVolume.processEvent(Event.MigrationCopyRequested);
 +            srcVolume.processEvent(Event.MigrationRequested);
 +
 +            CopyVolumeContext<VolumeApiResult> context = new CopyVolumeContext<VolumeApiResult>(null, future, srcVolume, destVolume, destStore);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +            caller.setCallback(caller.getTarget().copyVolumeCallBack(null, null)).setContext(context);
 +            motionSrv.copyAsync(srcVolume, destVolume, caller);
 +        } catch (Exception e) {
 +            s_logger.error("Failed to copy volume:" + e);
 +            if(s_logger.isDebugEnabled()) {
 +                s_logger.debug("Failed to copy volume.", e);
 +            }
 +            res.setResult(e.toString());
 +            future.complete(res);
 +        }
 +        return future;
 +    }
 +
 +    protected Void copyVolumeCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, CopyVolumeContext<VolumeApiResult> context) {
 +        VolumeInfo srcVolume = context.srcVolume;
 +        VolumeInfo destVolume = context.destVolume;
 +        CopyCommandResult result = callback.getResult();
 +        AsyncCallFuture<VolumeApiResult> future = context.future;
 +        VolumeApiResult res = new VolumeApiResult(destVolume);
 +        try {
 +            if (result.isFailed()) {
 +                res.setResult(result.getResult());
 +                destVolume.processEvent(Event.MigrationCopyFailed);
 +                srcVolume.processEvent(Event.OperationFailed);
 +                destroyVolume(destVolume.getId());
 +                destVolume = volFactory.getVolume(destVolume.getId());
 +                AsyncCallFuture<VolumeApiResult> destroyFuture = expungeVolumeAsync(destVolume);
 +                destroyFuture.get();
 +                future.complete(res);
 +            } else {
 +                srcVolume.processEvent(Event.OperationSuccessed);
 +                destVolume.processEvent(Event.MigrationCopySucceeded, result.getAnswer());
 +                volDao.updateUuid(srcVolume.getId(), destVolume.getId());
 +                try {
 +                    destroyVolume(srcVolume.getId());
 +                    srcVolume = volFactory.getVolume(srcVolume.getId());
 +                    AsyncCallFuture<VolumeApiResult> destroyFuture = expungeVolumeAsync(srcVolume);
 +                    // If volume destroy fails, this could be because of vdi is still in use state, so wait and retry.
 +                    if (destroyFuture.get().isFailed()) {
 +                        Thread.sleep(5 * 1000);
 +                        destroyFuture = expungeVolumeAsync(srcVolume);
 +                        destroyFuture.get();
 +                    }
 +                    future.complete(res);
 +                } catch (Exception e) {
 +                    s_logger.debug("failed to clean up volume on storage", e);
 +                }
 +            }
 +        } catch (Exception e) {
 +            s_logger.debug("Failed to process copy volume callback", e);
 +            res.setResult(e.toString());
 +            future.complete(res);
 +        }
 +
 +        return null;
 +    }
 +
 +    private class MigrateVolumeContext<T> extends AsyncRpcContext<T> {
 +        final VolumeInfo srcVolume;
 +        final VolumeInfo destVolume;
 +        final AsyncCallFuture<VolumeApiResult> future;
 +
 +        /**
 +         * @param callback
 +         */
 +        public MigrateVolumeContext(AsyncCompletionCallback<T> callback, AsyncCallFuture<VolumeApiResult> future, VolumeInfo srcVolume, VolumeInfo destVolume, DataStore destStore) {
 +            super(callback);
 +            this.srcVolume = srcVolume;
 +            this.destVolume = destVolume;
 +            this.future = future;
 +        }
 +    }
 +
 +    @Override
 +    public AsyncCallFuture<VolumeApiResult> migrateVolume(VolumeInfo srcVolume, DataStore destStore) {
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +        VolumeApiResult res = new VolumeApiResult(srcVolume);
 +        try {
 +            if (!snapshotMgr.canOperateOnVolume(srcVolume)) {
 +                s_logger.debug("Snapshots are being created on this volume. This volume cannot be migrated now.");
 +                res.setResult("Snapshots are being created on this volume. This volume cannot be migrated now.");
 +                future.complete(res);
 +                return future;
 +            }
 +
 +            VolumeInfo destVolume = volFactory.getVolume(srcVolume.getId(), destStore);
 +            srcVolume.processEvent(Event.MigrationRequested);
 +            MigrateVolumeContext<VolumeApiResult> context = new MigrateVolumeContext<VolumeApiResult>(null, future, srcVolume, destVolume, destStore);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +            caller.setCallback(caller.getTarget().migrateVolumeCallBack(null, null)).setContext(context);
 +            motionSrv.copyAsync(srcVolume, destVolume, caller);
 +        } catch (Exception e) {
 +            s_logger.debug("Failed to copy volume", e);
 +            res.setResult(e.toString());
 +            future.complete(res);
 +        }
 +        return future;
 +    }
 +
 +    protected Void migrateVolumeCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, MigrateVolumeContext<VolumeApiResult> context) {
 +        VolumeInfo srcVolume = context.srcVolume;
 +        CopyCommandResult result = callback.getResult();
 +        AsyncCallFuture<VolumeApiResult> future = context.future;
 +        VolumeApiResult res = new VolumeApiResult(srcVolume);
 +        try {
 +            if (result.isFailed()) {
 +                res.setResult(result.getResult());
 +                srcVolume.processEvent(Event.OperationFailed);
 +                future.complete(res);
 +            } else {
 +                srcVolume.processEvent(Event.OperationSuccessed);
 +                snapshotMgr.cleanupSnapshotsByVolume(srcVolume.getId());
 +                future.complete(res);
 +            }
 +        } catch (Exception e) {
 +            s_logger.error("Failed to process migrate volume callback", e);
 +            res.setResult(e.toString());
 +            future.complete(res);
 +        }
 +
 +        return null;
 +    }
 +
 +    private class MigrateVmWithVolumesContext<T> extends AsyncRpcContext<T> {
 +        final Map<VolumeInfo, DataStore> volumeToPool;
 +        final AsyncCallFuture<CommandResult> future;
 +
 +        public MigrateVmWithVolumesContext(AsyncCompletionCallback<T> callback, AsyncCallFuture<CommandResult> future, Map<VolumeInfo, DataStore> volumeToPool) {
 +            super(callback);
 +            this.volumeToPool = volumeToPool;
 +            this.future = future;
 +        }
 +    }
 +
 +    @Override
 +    public AsyncCallFuture<CommandResult> migrateVolumes(Map<VolumeInfo, DataStore> volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost) {
 +        AsyncCallFuture<CommandResult> future = new AsyncCallFuture<CommandResult>();
 +        CommandResult res = new CommandResult();
 +        try {
 +            // Check to make sure there are no snapshot operations on a volume
 +            // and
 +            // put it in the migrating state.
 +            List<VolumeInfo> volumesMigrating = new ArrayList<VolumeInfo>();
 +            for (Map.Entry<VolumeInfo, DataStore> entry : volumeMap.entrySet()) {
 +                VolumeInfo volume = entry.getKey();
 +                if (!snapshotMgr.canOperateOnVolume(volume)) {
 +                    s_logger.debug("Snapshots are being created on a volume. Volumes cannot be migrated now.");
 +                    res.setResult("Snapshots are being created on a volume. Volumes cannot be migrated now.");
 +                    future.complete(res);
 +
 +                    // All the volumes that are already in migrating state need
 +                    // to be put back in ready state.
 +                    for (VolumeInfo volumeMigrating : volumesMigrating) {
 +                        volumeMigrating.processEvent(Event.OperationFailed);
 +                    }
 +                    return future;
 +                } else {
 +                    volume.processEvent(Event.MigrationRequested);
 +                    volumesMigrating.add(volume);
 +                }
 +            }
 +
 +            MigrateVmWithVolumesContext<CommandResult> context = new MigrateVmWithVolumesContext<CommandResult>(null, future, volumeMap);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> caller = AsyncCallbackDispatcher.create(this);
 +            caller.setCallback(caller.getTarget().migrateVmWithVolumesCallBack(null, null)).setContext(context);
 +            motionSrv.copyAsync(volumeMap, vmTo, srcHost, destHost, caller);
 +
 +        } catch (Exception e) {
 +            s_logger.debug("Failed to copy volume", e);
 +            res.setResult(e.toString());
 +            future.complete(res);
 +        }
 +
 +        return future;
 +    }
 +
 +    protected Void migrateVmWithVolumesCallBack(AsyncCallbackDispatcher<VolumeServiceImpl, CopyCommandResult> callback, MigrateVmWithVolumesContext<CommandResult> context) {
 +        Map<VolumeInfo, DataStore> volumeToPool = context.volumeToPool;
 +        CopyCommandResult result = callback.getResult();
 +        AsyncCallFuture<CommandResult> future = context.future;
 +        CommandResult res = new CommandResult();
 +        try {
 +            if (result.isFailed()) {
 +                res.setResult(result.getResult());
 +                for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
 +                    VolumeInfo volume = entry.getKey();
 +                    volume.processEvent(Event.OperationFailed);
 +                }
 +                future.complete(res);
 +            } else {
 +                for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) {
 +                    VolumeInfo volume = entry.getKey();
 +                    snapshotMgr.cleanupSnapshotsByVolume(volume.getId());
 +                    volume.processEvent(Event.OperationSuccessed);
 +                }
 +                future.complete(res);
 +            }
 +        } catch (Exception e) {
 +            s_logger.error("Failed to process copy volume callback", e);
 +            res.setResult(e.toString());
 +            future.complete(res);
 +        }
 +
 +        return null;
 +    }
 +
 +    @Override
 +    public AsyncCallFuture<VolumeApiResult> registerVolume(VolumeInfo volume, DataStore store) {
 +
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +        DataObject volumeOnStore = store.create(volume);
 +
 +        volumeOnStore.processEvent(Event.CreateOnlyRequested);
 +
 +        try {
 +            CreateVolumeContext<VolumeApiResult> context = new CreateVolumeContext<VolumeApiResult>(null, volumeOnStore, future);
 +            AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this);
 +            caller.setCallback(caller.getTarget().registerVolumeCallback(null, null));
 +            caller.setContext(context);
 +
 +            store.getDriver().createAsync(store, volumeOnStore, caller);
 +        } catch (CloudRuntimeException ex) {
 +            // clean up already persisted volume_store_ref entry in case of createVolumeCallback is never called
 +            VolumeDataStoreVO volStoreVO = _volumeStoreDao.findByStoreVolume(store.getId(), volume.getId());
 +            if (volStoreVO != null) {
 +                VolumeInfo volObj = volFactory.getVolume(volume, store);
 +                volObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
 +            }
 +            VolumeApiResult res = new VolumeApiResult((VolumeObject)volumeOnStore);
 +            res.setResult(ex.getMessage());
 +            future.complete(res);
 +        }
 +        return future;
 +    }
 +
 +    @Override
 +    public Pair<EndPoint, DataObject> registerVolumeForPostUpload(VolumeInfo volume, DataStore store) {
 +
 +        EndPoint ep = _epSelector.select(store);
 +        if (ep == null) {
 +            String errorMessage = "There is no secondary storage VM for image store " + store.getName();
 +            s_logger.warn(errorMessage);
 +            throw new CloudRuntimeException(errorMessage);
 +        }
 +        DataObject volumeOnStore = store.create(volume);
 +        return new Pair<>(ep, volumeOnStore);
 +    }
 +
 +    protected Void registerVolumeCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> callback, CreateVolumeContext<VolumeApiResult> context) {
 +        CreateCmdResult result = callback.getResult();
 +        VolumeObject vo = (VolumeObject)context.volume;
 +        try {
 +            if (result.isFailed()) {
 +                vo.processEvent(Event.OperationFailed);
 +                // delete the volume entry from volumes table in case of failure
 +                VolumeVO vol = volDao.findById(vo.getId());
 +                if (vol != null) {
 +                    volDao.remove(vo.getId());
 +                }
 +
 +            } else {
 +                vo.processEvent(Event.OperationSuccessed, result.getAnswer());
 +
 +                if (vo.getSize() != null) {
 +                    // publish usage events
 +                    // get physical size from volume_store_ref table
 +                    long physicalSize = 0;
 +                    DataStore ds = vo.getDataStore();
 +                    VolumeDataStoreVO volStore = _volumeStoreDao.findByStoreVolume(ds.getId(), vo.getId());
 +                    if (volStore != null) {
 +                        physicalSize = volStore.getPhysicalSize();
 +                    } else {
 +                        s_logger.warn("No entry found in volume_store_ref for volume id: " + vo.getId() + " and image store id: " + ds.getId() + " at the end of uploading volume!");
 +                    }
 +                    Scope dsScope = ds.getScope();
 +                    if (dsScope.getScopeType() == ScopeType.ZONE) {
 +                        if (dsScope.getScopeId() != null) {
 +                            UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_UPLOAD, vo.getAccountId(), dsScope.getScopeId(), vo.getId(), vo.getName(), null, null, physicalSize, vo.getSize(),
 +                                    Volume.class.getName(), vo.getUuid());
 +                        } else {
 +                            s_logger.warn("Zone scope image store " + ds.getId() + " has a null scope id");
 +                        }
 +                    } else if (dsScope.getScopeType() == ScopeType.REGION) {
 +                        // publish usage event for region-wide image store using a -1 zoneId for 4.2, need to revisit post-4.2
 +                        UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_UPLOAD, vo.getAccountId(), -1, vo.getId(), vo.getName(), null, null, physicalSize, vo.getSize(),
 +                                Volume.class.getName(), vo.getUuid());
 +
 +                        _resourceLimitMgr.incrementResourceCount(vo.getAccountId(), ResourceType.secondary_storage, vo.getSize());
 +                    }
 +                }
 +            }
 +            VolumeApiResult res = new VolumeApiResult(vo);
 +            context.future.complete(res);
 +            return null;
 +
 +        } catch (Exception e) {
 +            s_logger.error("register volume failed: ", e);
 +            // delete the volume entry from volumes table in case of failure
 +            VolumeVO vol = volDao.findById(vo.getId());
 +            if (vol != null) {
 +                volDao.remove(vo.getId());
 +            }
 +            VolumeApiResult res = new VolumeApiResult(null);
 +            context.future.complete(res);
 +            return null;
 +        }
 +    }
 +
 +    @Override
 +    public AsyncCallFuture<VolumeApiResult> resize(VolumeInfo volume) {
 +        AsyncCallFuture<VolumeApiResult> future = new AsyncCallFuture<VolumeApiResult>();
 +        VolumeApiResult result = new VolumeApiResult(volume);
 +        try {
 +            volume.processEvent(Event.ResizeRequested);
 +        } catch (Exception e) {
 +            s_logger.debug("Failed to change state to resize", e);
 +            result.setResult(e.toString());
 +            future.complete(result);
 +            return future;
 +        }
 +        CreateVolumeContext<VolumeApiResult> context = new CreateVolumeContext<VolumeApiResult>(null, volume, future);
 +        AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this);
 +        caller.setCallback(caller.getTarget().resizeVolumeCallback(caller, context)).setContext(context);
 +
 +        try {
 +            volume.getDataStore().getDriver().resize(volume, caller);
 +        } catch (Exception e) {
 +            s_logger.debug("Failed to change state to resize", e);
 +
 +            result.setResult(e.toString());
 +
 +            future.complete(result);
 +        }
 +
 +        return future;
 +    }
 +
 +    @Override
 +    public void resizeVolumeOnHypervisor(long volumeId, long newSize, long destHostId, String instanceName) {
 +        final String errMsg = "Resize command failed";
 +
 +        try {
 +            Answer answer = null;
 +            Host destHost = _hostDao.findById(destHostId);
 +            EndPoint ep = RemoteHostEndPoint.getHypervisorHostEndPoint(destHost);
 +
 +            if (ep != null) {
 +                VolumeVO volume = volDao.findById(volumeId);
 +                PrimaryDataStore primaryDataStore = this.dataStoreMgr.getPrimaryDataStore(volume.getPoolId());
 +                ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(volume.getPath(), new StorageFilerTO(primaryDataStore), volume.getSize(), newSize, true, instanceName,
 +                        primaryDataStore.isManaged(), volume.get_iScsiName());
 +
 +                answer = ep.sendMessage(resizeCmd);
 +            } else {
 +                throw new CloudRuntimeException("Could not find a remote endpoint to send command to. Check if host or SSVM is down.");
 +            }
 +
 +            if (answer == null || !answer.getResult()) {
 +                throw new CloudRuntimeException(answer != null ? answer.getDetails() : errMsg);
 +            }
 +        } catch (Exception e) {
 +            throw new CloudRuntimeException(errMsg, e);
 +        }
 +    }
 +
 +    protected Void resizeVolumeCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> callback, CreateVolumeContext<VolumeApiResult> context) {
 +        CreateCmdResult result = callback.getResult();
 +        AsyncCallFuture<VolumeApiResult> future = context.future;
 +        VolumeInfo volume = (VolumeInfo)context.volume;
 +
 +        if (result.isFailed()) {
 +            try {
 +                volume.processEvent(Event.OperationFailed);
 +            } catch (Exception e) {
 +                s_logger.debug("Failed to change state", e);
 +            }
 +            VolumeApiResult res = new VolumeApiResult(volume);
 +            res.setResult(result.getResult());
 +            future.complete(res);
 +            return null;
 +        }
 +
 +        try {
 +            volume.processEvent(Event.OperationSuccessed);
 +        } catch (Exception e) {
 +            s_logger.debug("Failed to change state", e);
 +            VolumeApiResult res = new VolumeApiResult(volume);
 +            res.setResult(result.getResult());
 +            future.complete(res);
 +            return null;
 +        }
 +
 +        VolumeApiResult res = new VolumeApiResult(volume);
 +        future.complete(res);
 +
 +        return null;
 +    }
 +
 +    @Override
 +    public void handleVolumeSync(DataStore store) {
 +        if (store == null) {
 +            s_logger.warn("Huh? image store is null");
 +            return;
 +        }
 +        long storeId = store.getId();
 +
 +        // add lock to make template sync for a data store only be done once
 +        String lockString = "volumesync.storeId:" + storeId;
 +        GlobalLock syncLock = GlobalLock.getInternLock(lockString);
 +        try {
 +            if (syncLock.lock(3)) {
 +                try {
 +                    Map<Long, TemplateProp> volumeInfos = listVolume(store);
 +                    if (volumeInfos == null) {
 +                        return;
 +                    }
 +
 +                    // find all the db volumes including those with NULL url column to avoid accidentally deleting volumes on image store later.
 +                    List<VolumeDataStoreVO> dbVolumes = _volumeStoreDao.listByStoreId(storeId);
 +                    List<VolumeDataStoreVO> toBeDownloaded = new ArrayList<VolumeDataStoreVO>(dbVolumes);
 +                    for (VolumeDataStoreVO volumeStore : dbVolumes) {
 +                        VolumeVO volume = volDao.findById(volumeStore.getVolumeId());
 +                        if (volume == null) {
 +                            s_logger.warn("Volume_store_ref table shows that volume " + volumeStore.getVolumeId() + " is on image store " + storeId
 +                                    + ", but the volume is not found in volumes table, potentially some bugs in deleteVolume, so we just treat this volume to be deleted and mark it as destroyed");
 +                            volumeStore.setDestroyed(true);
 +                            _volumeStoreDao.update(volumeStore.getId(), volumeStore);
 +                            continue;
 +                        }
 +                        // Exists then don't download
 +                        if (volumeInfos.containsKey(volume.getId())) {
 +                            TemplateProp volInfo = volumeInfos.remove(volume.getId());
 +                            toBeDownloaded.remove(volumeStore);
 +                            s_logger.info("Volume Sync found " + volume.getUuid() + " already in the volume image store table");
 +                            if (volumeStore.getDownloadState() != Status.DOWNLOADED) {
 +                                volumeStore.setErrorString("");
 +                            }
 +                            if (volInfo.isCorrupted()) {
 +                                volumeStore.setDownloadState(Status.DOWNLOAD_ERROR);
 +                                String msg = "Volume " + volume.getUuid() + " is corrupted on image store";
 +                                volumeStore.setErrorString(msg);
 +                                s_logger.info(msg);
 +                                if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) {
 +                                    s_logger.info("Volume Sync found " + volume.getUuid() + " uploaded using SSVM on image store " + storeId + " as corrupted, marking it as failed");
 +                                    _volumeStoreDao.update(volumeStore.getId(), volumeStore);
 +                                    // mark volume as failed, so that storage GC will clean it up
 +                                    VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId());
 +                                    volObj.processEvent(Event.OperationFailed);
 +                                } else if (volumeStore.getDownloadUrl() == null) {
 +                                    msg = "Volume (" + volume.getUuid() + ") with install path " + volInfo.getInstallPath() + " is corrupted, please check in image store: "
 +                                            + volumeStore.getDataStoreId();
 +                                    s_logger.warn(msg);
 +                                } else {
 +                                    s_logger.info("Removing volume_store_ref entry for corrupted volume " + volume.getName());
 +                                    _volumeStoreDao.remove(volumeStore.getId());
 +                                    toBeDownloaded.add(volumeStore);
 +                                }
 +                            } else { // Put them in right status
 +                                volumeStore.setDownloadPercent(100);
 +                                volumeStore.setDownloadState(Status.DOWNLOADED);
 +                                volumeStore.setState(ObjectInDataStoreStateMachine.State.Ready);
 +                                volumeStore.setInstallPath(volInfo.getInstallPath());
 +                                volumeStore.setSize(volInfo.getSize());
 +                                volumeStore.setPhysicalSize(volInfo.getPhysicalSize());
 +                                volumeStore.setLastUpdated(new Date());
 +                                _volumeStoreDao.update(volumeStore.getId(), volumeStore);
 +
 +                                if (volume.getSize() == 0) {
 +                                    // Set volume size in volumes table
 +                                    volume.setSize(volInfo.getSize());
 +                                    volDao.update(volumeStore.getVolumeId(), volume);
 +                                }
 +
 +                                if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) {
 +                                    VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId());
 +                                    volObj.processEvent(Event.OperationSuccessed);
 +                                }
 +
 +                                if (volInfo.getSize() > 0) {
 +                                    try {
 +                                        _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), com.cloud.configuration.Resource.ResourceType.secondary_storage,
 +                                                volInfo.getSize() - volInfo.getPhysicalSize());
 +                                    } catch (ResourceAllocationException e) {
 +                                        s_logger.warn(e.getMessage());
 +                                        _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED, volume.getDataCenterId(), volume.getPodId(), e.getMessage(), e.getMessage());
 +                                    } finally {
 +                                        _resourceLimitMgr.recalculateResourceCount(volume.getAccountId(), volume.getDomainId(),
 +                                                com.cloud.configuration.Resource.ResourceType.secondary_storage.getOrdinal());
 +                                    }
 +                                }
 +                            }
 +                            continue;
 +                        } else if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) { // failed uploads through SSVM
 +                            s_logger.info("Volume Sync did not find " + volume.getUuid() + " uploaded using SSVM on image store " + storeId + ", marking it as failed");
 +                            toBeDownloaded.remove(volumeStore);
 +                            volumeStore.setDownloadState(Status.DOWNLOAD_ERROR);
 +                            String msg = "Volume " + volume.getUuid() + " is corrupted on image store";
 +                            volumeStore.setErrorString(msg);
 +                            _volumeStoreDao.update(volumeStore.getId(), volumeStore);
 +                            // mark volume as failed, so that storage GC will clean it up
 +                            VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId());
 +                            volObj.processEvent(Event.OperationFailed);
 +                            continue;
 +                        }
 +                        // Volume is not on secondary but we should download.
 +                        if (volumeStore.getDownloadState() != Status.DOWNLOADED) {
 +                            s_logger.info("Volume Sync did not find " + volume.getName() + " ready on image store " + storeId + ", will request download to start/resume shortly");
 +                        }
 +                    }
 +
 +                    // Download volumes which haven't been downloaded yet.
 +                    if (toBeDownloaded.size() > 0) {
 +                        for (VolumeDataStoreVO volumeHost : toBeDownloaded) {
 +                            if (volumeHost.getDownloadUrl() == null) { // If url is null, skip downloading
 +                                s_logger.info("Skip downloading volume " + volumeHost.getVolumeId() + " since no download url is specified.");
 +                                continue;
 +                            }
 +
 +                            // if this is a region store, and there is already an DOWNLOADED entry there without install_path information, which
 +                            // means that this is a duplicate entry from migration of previous NFS to staging.
 +                            if (store.getScope().getScopeType() == ScopeType.REGION) {
 +                                if (volumeHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED && volumeHost.getInstallPath() == null) {
 +                                    s_logger.info("Skip sync volume for migration of previous NFS to object store");
 +                                    continue;
 +                                }
 +                            }
 +
 +                            s_logger.debug("Volume " + volumeHost.getVolumeId() + " needs to be downloaded to " + store.getName());
 +                            // reset volume status back to Allocated
 +                            VolumeObject vol = (VolumeObject)volFactory.getVolume(volumeHost.getVolumeId());
 +                            vol.processEvent(Event.OperationFailed); // reset back volume status
 +                            // remove leftover volume_store_ref entry since re-download will create it again
 +                            _volumeStoreDao.remove(volumeHost.getId());
 +                            // get an updated volumeVO
 +                            vol = (VolumeObject)volFactory.getVolume(volumeHost.getVolumeId());
 +                            RegisterVolumePayload payload = new RegisterVolumePayload(volumeHost.getDownloadUrl(), volumeHost.getChecksum(), vol.getFormat().toString());
 +                            vol.addPayload(payload);
 +                            createVolumeAsync(vol, store);
 +                        }
 +                    }
 +
 +                    // Delete volumes which are not present on DB.
 +                    for (Map.Entry<Long, TemplateProp> entry : volumeInfos.entrySet()) {
 +                        Long uniqueName = entry.getKey();
 +                        TemplateProp tInfo = entry.getValue();
 +
 +                        // we cannot directly call expungeVolumeAsync here to reuse delete logic since in this case db does not have this volume at all.
 +                        VolumeObjectTO tmplTO = new VolumeObjectTO();
 +                        tmplTO.setDataStore(store.getTO());
 +                        tmplTO.setPath(tInfo.getInstallPath());
 +                        tmplTO.setId(tInfo.getId());
 +                        DeleteCommand dtCommand = new DeleteCommand(tmplTO);
 +                        EndPoint ep = _epSelector.select(store);
 +                        Answer answer = null;
 +                        if (ep == null) {
 +                            String errMsg = "No remote endpoint to send command, check if host or ssvm is down?";
 +                            s_logger.error(errMsg);
 +                            answer = new Answer(dtCommand, false, errMsg);
 +                        } else {
 +                            answer = ep.sendMessage(dtCommand);
 +                        }
 +                        if (answer == null || !answer.getResult()) {
 +                            s_logger.info("Failed to deleted volume at store: " + store.getName());
 +
 +                        } else {
 +                            String description = "Deleted volume " + tInfo.getTemplateName() + " on secondary storage " + storeId;
 +                            s_logger.info(description);
 +                        }
 +                    }
 +                } finally {
 +                    syncLock.unlock();
 +                }
 +            } else {
 +                s_logger.info("Couldn't get global lock on " + lockString + ", another thread may be doing volume sync on data store " + storeId + " now.");
 +            }
 +        } finally {
 +            syncLock.releaseRef();
 +        }
 +    }
 +
 +    private Map<Long, TemplateProp> listVolume(DataStore store) {
 +        ListVolumeCommand cmd = new ListVolumeCommand(store.getTO(), store.getUri());
 +        EndPoint ep = _epSelector.select(store);
 +        Answer answer = null;
 +        if (ep == null) {
 +            String errMsg = "No remote endpoint to send command, check if host or ssvm is down?";
 +            s_logger.error(errMsg);
 +            answer = new Answer(cmd, false, errMsg);
 +        } else {
 +            answer = ep.sendMessage(cmd);
 +        }
 +        if (answer != null && answer.getResult()) {
 +            ListVolumeAnswer tanswer = (ListVolumeAnswer)answer;
 +            return tanswer.getTemplateInfo();
 +        } else {
 +            if (s_logger.isDebugEnabled()) {
 +                s_logger.debug("Can not list volumes for image store " + store.getId());
 +            }
 +        }
 +
 +        return null;
 +    }
 +
 +    @Override
 +    public SnapshotInfo takeSnapshot(VolumeInfo volume) {
 +        SnapshotInfo snapshot = null;
 +        try {
 +            snapshot = snapshotMgr.takeSnapshot(volume);
 +        } catch (CloudRuntimeException cre) {
 +            s_logger.error("Take snapshot: " + volume.getId() + " failed", cre);
 +            throw cre;
 +        } catch (Exception e) {
 +            if (s_logger.isDebugEnabled()) {
 +                s_logger.debug("unknown exception while taking snapshot for volume " + volume.getId() + " was caught", e);
 +            }
 +            throw new CloudRuntimeException("Failed to take snapshot", e);
 +        }
 +
 +        return snapshot;
 +    }
 +
 +    // For managed storage on Xen and VMware, we need to potentially make space for hypervisor snapshots.
 +    // The disk offering can collect this information and pass it on to the volume that's about to be created.
 +    // Ex. if you want a 10 GB CloudStack volume to reside on managed storage on Xen, this leads to an SR
 +    // that is a total size of (10 GB * (hypervisorSnapshotReserveSpace / 100) + 10 GB).
 +    @Override
 +    public VolumeInfo updateHypervisorSnapshotReserveForVolume(DiskOffering diskOffering, long volumeId, HypervisorType hyperType) {
 +        if (diskOffering != null && hyperType != null) {
 +            Integer hypervisorSnapshotReserve = diskOffering.getHypervisorSnapshotReserve();
 +
 +            if (hyperType == HypervisorType.KVM) {
 +                hypervisorSnapshotReserve = null;
 +            } else if (hypervisorSnapshotReserve == null || hypervisorSnapshotReserve < 0) {
 +                hypervisorSnapshotReserve = 0;
 +            }
 +
 +            VolumeVO volume = volDao.findById(volumeId);
 +
 +            volume.setHypervisorSnapshotReserve(hypervisorSnapshotReserve);
 +
 +            volDao.update(volume.getId(), volume);
 +        }
 +
 +        return volFactory.getVolume(volumeId);
 +    }
 +}
diff --cc plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index c07b2d9,0000000..03fa5bf
mode 100644,000000..100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@@ -1,3856 -1,0 +1,3850 @@@
 +// Licensed to the Apache Software Foundation (ASF) under one
 +// or more contributor license agreements.  See the NOTICE file
 +// distributed with this work for additional information
 +// regarding copyright ownership.  The ASF licenses this file
 +// to you under the Apache License, Version 2.0 (the
 +// "License"); you may not use this file except in compliance
 +// with the License.  You may obtain a copy of the License at
 +//
 +//   http://www.apache.org/licenses/LICENSE-2.0
 +//
 +// Unless required by applicable law or agreed to in writing,
 +// software distributed under the License is distributed on an
 +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 +// KIND, either express or implied.  See the License for the
 +// specific language governing permissions and limitations
 +// under the License.
 +package com.cloud.hypervisor.kvm.resource;
 +
 +import java.io.BufferedReader;
 +import java.io.File;
 +import java.io.FileNotFoundException;
 +import java.io.IOException;
 +import java.io.StringReader;
 +import java.net.InetAddress;
 +import java.net.URI;
 +import java.net.URISyntaxException;
 +import java.nio.file.Paths;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Calendar;
 +import java.util.Collections;
 +import java.util.Comparator;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Properties;
 +import java.util.Set;
 +import java.util.UUID;
 +import java.util.concurrent.ConcurrentHashMap;
 +import java.util.regex.Matcher;
 +import java.util.regex.Pattern;
 +
 +import javax.naming.ConfigurationException;
 +import javax.xml.parsers.DocumentBuilder;
 +import javax.xml.parsers.DocumentBuilderFactory;
 +import javax.xml.parsers.ParserConfigurationException;
 +
 +import com.cloud.resource.RequestWrapper;
 +import org.apache.cloudstack.api.ApiConstants;
 +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
 +import org.apache.cloudstack.storage.to.TemplateObjectTO;
 +import org.apache.cloudstack.storage.to.VolumeObjectTO;
 +import org.apache.cloudstack.utils.hypervisor.HypervisorUtils;
 +import org.apache.cloudstack.utils.linux.CPUStat;
 +import org.apache.cloudstack.utils.linux.KVMHostInfo;
 +import org.apache.cloudstack.utils.linux.MemStat;
 +import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
 +import org.apache.cloudstack.utils.security.KeyStoreUtils;
 +import org.apache.commons.collections.MapUtils;
 +import org.apache.commons.io.FileUtils;
 +import org.apache.commons.lang.ArrayUtils;
 +import org.apache.commons.lang.math.NumberUtils;
 +import org.apache.log4j.Logger;
 +import org.joda.time.Duration;
 +import org.libvirt.Connect;
 +import org.libvirt.Domain;
 +import org.libvirt.DomainBlockStats;
 +import org.libvirt.DomainInfo;
 +import org.libvirt.DomainInfo.DomainState;
 +import org.libvirt.DomainInterfaceStats;
 +import org.libvirt.DomainSnapshot;
 +import org.libvirt.LibvirtException;
 +import org.libvirt.MemoryStatistic;
 +import org.libvirt.NodeInfo;
 +import org.w3c.dom.Document;
 +import org.w3c.dom.Element;
 +import org.w3c.dom.Node;
 +import org.w3c.dom.NodeList;
 +import org.xml.sax.InputSource;
 +import org.xml.sax.SAXException;
 +
 +import com.cloud.agent.api.Answer;
 +import com.cloud.agent.api.Command;
 +import com.cloud.agent.api.HostVmStateReportEntry;
 +import com.cloud.agent.api.PingCommand;
 +import com.cloud.agent.api.PingRoutingCommand;
 +import com.cloud.agent.api.PingRoutingWithNwGroupsCommand;
 +import com.cloud.agent.api.SetupGuestNetworkCommand;
 +import com.cloud.agent.api.StartupCommand;
 +import com.cloud.agent.api.StartupRoutingCommand;
 +import com.cloud.agent.api.StartupStorageCommand;
 +import com.cloud.agent.api.VmDiskStatsEntry;
 +import com.cloud.agent.api.VmNetworkStatsEntry;
 +import com.cloud.agent.api.VmStatsEntry;
 +import com.cloud.agent.api.routing.IpAssocCommand;
 +import com.cloud.agent.api.routing.IpAssocVpcCommand;
 +import com.cloud.agent.api.routing.NetworkElementCommand;
 +import com.cloud.agent.api.routing.SetSourceNatCommand;
 +import com.cloud.agent.api.to.DataStoreTO;
 +import com.cloud.agent.api.to.DataTO;
 +import com.cloud.agent.api.to.DiskTO;
 +import com.cloud.agent.api.to.IpAddressTO;
 +import com.cloud.agent.api.to.NfsTO;
 +import com.cloud.agent.api.to.NicTO;
 +import com.cloud.agent.api.to.VirtualMachineTO;
 +import com.cloud.agent.resource.virtualnetwork.VRScripts;
 +import com.cloud.agent.resource.virtualnetwork.VirtualRouterDeployer;
 +import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource;
 +import com.cloud.dc.Vlan;
 +import com.cloud.exception.InternalErrorException;
 +import com.cloud.host.Host.Type;
 +import com.cloud.hypervisor.Hypervisor.HypervisorType;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ClockDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ConsoleDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.CpuModeDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.CpuTuneDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DevicesDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef.DeviceType;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef.DiscardType;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef.DiskProtocol;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.FeaturesDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.FilesystemDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GraphicDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.GuestResourceDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InputDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef.GuestNetType;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.RngDef.RngBackendModel;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SerialDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.TermPolicy;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.VideoDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogAction;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.WatchDogDef.WatchDogModel;
 +import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtRequestWrapper;
 +import com.cloud.hypervisor.kvm.resource.wrapper.LibvirtUtilitiesHelper;
 +import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
 +import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
 +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
 +import com.cloud.hypervisor.kvm.storage.KVMStorageProcessor;
 +import com.cloud.network.Networks.BroadcastDomainType;
 +import com.cloud.network.Networks.RouterPrivateIpStrategy;
 +import com.cloud.network.Networks.TrafficType;
 +import com.cloud.resource.ServerResource;
 +import com.cloud.resource.ServerResourceBase;
 +import com.cloud.storage.JavaStorageLayer;
 +import com.cloud.storage.Storage;
 +import com.cloud.storage.Storage.StoragePoolType;
 +import com.cloud.storage.StorageLayer;
 +import com.cloud.storage.Volume;
 +import com.cloud.storage.resource.StorageSubsystemCommandHandler;
 +import com.cloud.storage.resource.StorageSubsystemCommandHandlerBase;
 +import com.cloud.utils.ExecutionResult;
 +import com.cloud.utils.NumbersUtil;
 +import com.cloud.utils.Pair;
 +import com.cloud.utils.PropertiesUtil;
 +import com.cloud.utils.StringUtils;
 +import com.cloud.utils.Ternary;
 +import com.cloud.utils.exception.CloudRuntimeException;
 +import com.cloud.utils.net.NetUtils;
 +import com.cloud.utils.script.OutputInterpreter;
 +import com.cloud.utils.script.OutputInterpreter.AllLinesParser;
 +import com.cloud.utils.script.Script;
 +import com.cloud.utils.ssh.SshHelper;
 +import com.cloud.vm.VirtualMachine;
 +import com.cloud.vm.VirtualMachine.PowerState;
 +import com.cloud.vm.VmDetailConstants;
 +import com.google.common.base.Strings;
 +
 +/**
 + * LibvirtComputingResource execute requests on the computing/routing host using
 + * the libvirt API
 + *
 + * @config {@table || Param Name | Description | Values | Default || ||
 + *         hypervisor.type | type of local hypervisor | string | kvm || ||
 + *         hypervisor.uri | local hypervisor to connect to | URI |
 + *         qemu:///system || || domr.arch | instruction set for domr template |
 + *         string | i686 || || private.bridge.name | private bridge where the
 + *         domrs have their private interface | string | vmops0 || ||
 + *         public.bridge.name | public bridge where the domrs have their public
 + *         interface | string | br0 || || private.network.name | name of the
 + *         network where the domrs have their private interface | string |
 + *         vmops-private || || private.ipaddr.start | start of the range of
 + *         private ip addresses for domrs | ip address | 192.168.166.128 || ||
 + *         private.ipaddr.end | end of the range of private ip addresses for
 + *         domrs | ip address | start + 126 || || private.macaddr.start | start
 + *         of the range of private mac addresses for domrs | mac address |
 + *         00:16:3e:77:e2:a0 || || private.macaddr.end | end of the range of
 + *         private mac addresses for domrs | mac address | start + 126 || ||
 + *         pool | the parent of the storage pool hierarchy * }
 + **/
 +public class LibvirtComputingResource extends ServerResourceBase implements ServerResource, VirtualRouterDeployer {
 +    private static final Logger s_logger = Logger.getLogger(LibvirtComputingResource.class);
 +
 +    private String _modifyVlanPath;
 +    private String _versionstringpath;
-     private String _patchViaSocketPath;
++    private String _patchScriptPath;
 +    private String _createvmPath;
 +    private String _manageSnapshotPath;
 +    private String _resizeVolumePath;
 +    private String _createTmplPath;
 +    private String _heartBeatPath;
 +    private String _vmActivityCheckPath;
 +    private String _securityGroupPath;
 +    private String _ovsPvlanDhcpHostPath;
 +    private String _ovsPvlanVmPath;
 +    private String _routerProxyPath;
 +    private String _ovsTunnelPath;
 +    private String _host;
 +    private String _dcId;
 +    private String _pod;
 +    private String _clusterId;
 +
 +    private long _hvVersion;
 +    private Duration _timeout;
 +    private static final int NUMMEMSTATS =2;
 +
 +    private KVMHAMonitor _monitor;
 +    public static final String SSHKEYSPATH = "/root/.ssh";
 +    public static final String SSHPRVKEYPATH = SSHKEYSPATH + File.separator + "id_rsa.cloud";
 +    public static final String SSHPUBKEYPATH = SSHKEYSPATH + File.separator + "id_rsa.pub.cloud";
 +
 +    public static final String BASH_SCRIPT_PATH = "/bin/bash";
 +
 +    private String _mountPoint = "/mnt";
 +    private StorageLayer _storage;
 +    private KVMStoragePoolManager _storagePoolMgr;
 +
 +    private VifDriver _defaultVifDriver;
 +    private Map<TrafficType, VifDriver> _trafficTypeVifDrivers;
 +
 +    protected static final String DEFAULT_OVS_VIF_DRIVER_CLASS_NAME = "com.cloud.hypervisor.kvm.resource.OvsVifDriver";
 +    protected static final String DEFAULT_BRIDGE_VIF_DRIVER_CLASS_NAME = "com.cloud.hypervisor.kvm.resource.BridgeVifDriver";
 +
 +    protected HypervisorType _hypervisorType;
 +    protected String _hypervisorURI;
 +    protected long _hypervisorLibvirtVersion;
 +    protected long _hypervisorQemuVersion;
 +    protected String _hypervisorPath;
 +    protected String _hostDistro;
 +    protected String _networkDirectSourceMode;
 +    protected String _networkDirectDevice;
 +    protected String _sysvmISOPath;
 +    protected String _privNwName;
 +    protected String _privBridgeName;
 +    protected String _linkLocalBridgeName;
 +    protected String _publicBridgeName;
 +    protected String _guestBridgeName;
 +    protected String _privateIp;
 +    protected String _pool;
 +    protected String _localGateway;
 +    private boolean _canBridgeFirewall;
 +    protected String _localStoragePath;
 +    protected String _localStorageUUID;
 +    protected boolean _noMemBalloon = false;
 +    protected String _guestCpuMode;
 +    protected String _guestCpuModel;
 +    protected boolean _noKvmClock;
 +    protected String _videoHw;
 +    protected int _videoRam;
 +    protected Pair<Integer,Integer> hostOsVersion;
 +    protected int _migrateSpeed;
 +    protected int _migrateDowntime;
 +    protected int _migratePauseAfter;
 +    protected boolean _diskActivityCheckEnabled;
 +    protected long _diskActivityCheckFileSizeMin = 10485760; // 10MB
 +    protected int _diskActivityCheckTimeoutSeconds = 120; // 120s
 +    protected long _diskActivityInactiveThresholdMilliseconds = 30000; // 30s
 +    protected boolean _rngEnable = false;
 +    protected RngBackendModel _rngBackendModel = RngBackendModel.RANDOM;
 +    protected String _rngPath = "/dev/random";
 +    protected int _rngRatePeriod = 1000;
 +    protected int _rngRateBytes = 2048;
 +    protected File _qemuSocketsPath;
 +    private final String _qemuGuestAgentSocketName = "org.qemu.guest_agent.0";
 +    protected WatchDogAction _watchDogAction = WatchDogAction.NONE;
 +    protected WatchDogModel _watchDogModel = WatchDogModel.I6300ESB;
 +
 +    private final Map <String, String> _pifs = new HashMap<String, String>();
 +    private final Map<String, VmStats> _vmStats = new ConcurrentHashMap<String, VmStats>();
 +
 +    protected static final HashMap<DomainState, PowerState> s_powerStatesTable;
 +    static {
 +        s_powerStatesTable = new HashMap<DomainState, PowerState>();
 +        s_powerStatesTable.put(DomainState.VIR_DOMAIN_SHUTOFF, PowerState.PowerOff);
 +        s_powerStatesTable.put(DomainState.VIR_DOMAIN_PAUSED, PowerState.PowerOn);
 +        s_powerStatesTable.put(DomainState.VIR_DOMAIN_RUNNING, PowerState.PowerOn);
 +        s_powerStatesTable.put(DomainState.VIR_DOMAIN_BLOCKED, PowerState.PowerOn);
 +        s_powerStatesTable.put(DomainState.VIR_DOMAIN_NOSTATE, PowerState.PowerUnknown);
 +        s_powerStatesTable.put(DomainState.VIR_DOMAIN_SHUTDOWN, PowerState.PowerOff);
 +    }
 +
 +    private VirtualRoutingResource _virtRouterResource;
 +
 +    private String _pingTestPath;
 +
 +    private String _updateHostPasswdPath;
 +
 +    private long _dom0MinMem;
 +
 +    private long _dom0OvercommitMem;
 +
 +    protected int _cmdsTimeout;
 +    protected int _stopTimeout;
 +    protected CPUStat _cpuStat = new CPUStat();
 +    protected MemStat _memStat = new MemStat(_dom0MinMem, _dom0OvercommitMem);
 +    private final LibvirtUtilitiesHelper libvirtUtilitiesHelper = new LibvirtUtilitiesHelper();
 +
 +    @Override
 +    public ExecutionResult executeInVR(final String routerIp, final String script, final String args) {
 +        return executeInVR(routerIp, script, args, _timeout);
 +    }
 +
 +    @Override
 +    public ExecutionResult executeInVR(final String routerIp, final String script, final String args, final Duration timeout) {
 +        final Script command = new Script(_routerProxyPath, timeout, s_logger);
 +        final AllLinesParser parser = new AllLinesParser();
 +        command.add(script);
 +        command.add(routerIp);
 +        if (args != null) {
 +            command.add(args);
 +        }
 +        String details = command.execute(parser);
 +        if (details == null) {
 +            details = parser.getLines();
 +        }
 +
 +        s_logger.debug("Executing script in VR: " + script);
 +
 +        return new ExecutionResult(command.getExitValue() == 0, details);
 +    }
 +
 +    @Override
 +    public ExecutionResult createFileInVR(final String routerIp, final String path, final String filename, final String content) {
 +        final File permKey = new File("/root/.ssh/id_rsa.cloud");
 +        boolean success = true;
 +        String details = "Creating file in VR, with ip: " + routerIp + ", file: " + filename;
 +        s_logger.debug(details);
 +
 +        try {
 +            SshHelper.scpTo(routerIp, 3922, "root", permKey, null, path, content.getBytes(), filename, null);
 +        } catch (final Exception e) {
 +            s_logger.warn("Fail to create file " + path + filename + " in VR " + routerIp, e);
 +            details = e.getMessage();
 +            success = false;
 +        }
 +        return new ExecutionResult(success, details);
 +    }
 +
 +    @Override
 +    public ExecutionResult prepareCommand(final NetworkElementCommand cmd) {
 +        //Update IP used to access router
 +        cmd.setRouterAccessIp(cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP));
 +        assert cmd.getRouterAccessIp() != null;
 +
 +        if (cmd instanceof IpAssocVpcCommand) {
 +            return prepareNetworkElementCommand((IpAssocVpcCommand)cmd);
 +        } else if (cmd instanceof IpAssocCommand) {
 +            return prepareNetworkElementCommand((IpAssocCommand)cmd);
 +        } else if (cmd instanceof SetupGuestNetworkCommand) {
 +            return prepareNetworkElementCommand((SetupGuestNetworkCommand)cmd);
 +        } else if (cmd instanceof SetSourceNatCommand) {
 +            return prepareNetworkElementCommand((SetSourceNatCommand)cmd);
 +        }
 +        return new ExecutionResult(true, null);
 +    }
 +
 +    @Override
 +    public ExecutionResult cleanupCommand(final NetworkElementCommand cmd) {
 +        if (cmd instanceof IpAssocCommand && !(cmd instanceof IpAssocVpcCommand)) {
 +            return cleanupNetworkElementCommand((IpAssocCommand)cmd);
 +        }
 +        return new ExecutionResult(true, null);
 +    }
 +
 +    public LibvirtUtilitiesHelper getLibvirtUtilitiesHelper() {
 +        return libvirtUtilitiesHelper;
 +    }
 +
 +    public CPUStat getCPUStat() {
 +        return _cpuStat;
 +    }
 +
 +    public MemStat getMemStat() {
 +        return _memStat;
 +    }
 +
 +    public VirtualRoutingResource getVirtRouterResource() {
 +        return _virtRouterResource;
 +    }
 +
 +    public String getPublicBridgeName() {
 +        return _publicBridgeName;
 +    }
 +
 +    public KVMStoragePoolManager getStoragePoolMgr() {
 +        return _storagePoolMgr;
 +    }
 +
 +    public String getPrivateIp() {
 +        return _privateIp;
 +    }
 +
 +    public int getMigrateDowntime() {
 +        return _migrateDowntime;
 +    }
 +
 +    public int getMigratePauseAfter() {
 +        return _migratePauseAfter;
 +    }
 +
 +    public int getMigrateSpeed() {
 +        return _migrateSpeed;
 +    }
 +
 +    public String getPingTestPath() {
 +        return _pingTestPath;
 +    }
 +
 +    public String getUpdateHostPasswdPath() {
 +        return _updateHostPasswdPath;
 +    }
 +
 +    public Duration getTimeout() {
 +        return _timeout;
 +    }
 +
 +    public String getOvsTunnelPath() {
 +        return _ovsTunnelPath;
 +    }
 +
 +    public KVMHAMonitor getMonitor() {
 +        return _monitor;
 +    }
 +
 +    public StorageLayer getStorage() {
 +        return _storage;
 +    }
 +
 +    public String createTmplPath() {
 +        return _createTmplPath;
 +    }
 +
 +    public int getCmdsTimeout() {
 +        return _cmdsTimeout;
 +    }
 +
 +    public String manageSnapshotPath() {
 +        return _manageSnapshotPath;
 +    }
 +
 +    public String getGuestBridgeName() {
 +        return _guestBridgeName;
 +    }
 +
 +    public String getVmActivityCheckPath() {
 +        return _vmActivityCheckPath;
 +    }
 +
 +    public String getOvsPvlanDhcpHostPath() {
 +        return _ovsPvlanDhcpHostPath;
 +    }
 +
 +    public String getOvsPvlanVmPath() {
 +        return _ovsPvlanVmPath;
 +    }
 +
 +    public String getResizeVolumePath() {
 +        return _resizeVolumePath;
 +    }
 +
 +    public StorageSubsystemCommandHandler getStorageHandler() {
 +        return storageHandler;
 +    }
 +
 +    private static final class KeyValueInterpreter extends OutputInterpreter {
 +        private final Map<String, String> map = new HashMap<String, String>();
 +
 +        @Override
 +        public String interpret(final BufferedReader reader) throws IOException {
 +            String line = null;
 +            int numLines = 0;
 +            while ((line = reader.readLine()) != null) {
 +                final String[] toks = line.trim().split("=");
 +                if (toks.length < 2) {
 +                    s_logger.warn("Failed to parse Script output: " + line);
 +                } else {
 +                    map.put(toks[0].trim(), toks[1].trim());
 +                }
 +                numLines++;
 +            }
 +            if (numLines == 0) {
 +                s_logger.warn("KeyValueInterpreter: no output lines?");
 +            }
 +            return null;
 +        }
 +
 +        public Map<String, String> getKeyValues() {
 +            return map;
 +        }
 +    }
 +
 +    @Override
 +    protected String getDefaultScriptsDir() {
 +        return null;
 +    }
 +
 +    protected List<String> _cpuFeatures;
 +
 +    protected enum BridgeType {
 +        NATIVE, OPENVSWITCH
 +    }
 +
 +    protected BridgeType _bridgeType;
 +
 +    protected StorageSubsystemCommandHandler storageHandler;
 +
 +    protected boolean dpdkSupport = false;
 +    protected String dpdkOvsPath;
 +    protected static final String DPDK_NUMA = ApiConstants.EXTRA_CONFIG + "-dpdk-numa";
 +    protected static final String DPDK_HUGE_PAGES = ApiConstants.EXTRA_CONFIG + "-dpdk-hugepages";
 +    protected static final String DPDK_INTERFACE_PREFIX = ApiConstants.EXTRA_CONFIG + "-dpdk-interface-";
 +
 +    private String getEndIpFromStartIp(final String startIp, final int numIps) {
 +        final String[] tokens = startIp.split("[.]");
 +        assert tokens.length == 4;
 +        int lastbyte = Integer.parseInt(tokens[3]);
 +        lastbyte = lastbyte + numIps;
 +        tokens[3] = Integer.toString(lastbyte);
 +        final StringBuilder end = new StringBuilder(15);
 +        end.append(tokens[0]).append(".").append(tokens[1]).append(".").append(tokens[2]).append(".").append(tokens[3]);
 +        return end.toString();
 +    }
 +
 +    private Map<String, Object> getDeveloperProperties() throws ConfigurationException {
 +
 +        final File file = PropertiesUtil.findConfigFile("developer.properties");
 +        if (file == null) {
 +            throw new ConfigurationException("Unable to find developer.properties.");
 +        }
 +
 +        s_logger.info("developer.properties found at " + file.getAbsolutePath());
 +        try {
 +            final Properties properties = PropertiesUtil.loadFromFile(file);
 +
 +            final String startMac = (String)properties.get("private.macaddr.start");
 +            if (startMac == null) {
 +                throw new ConfigurationException("Developers must specify start mac for private ip range");
 +            }
 +
 +            final String startIp = (String)properties.get("private.ipaddr.start");
 +            if (startIp == null) {
 +                throw new ConfigurationException("Developers must specify start ip for private ip range");
 +            }
 +            final Map<String, Object> params = PropertiesUtil.toMap(properties);
 +
 +            String endIp = (String)properties.get("private.ipaddr.end");
 +            if (endIp == null) {
 +                endIp = getEndIpFromStartIp(startIp, 16);
 +                params.put("private.ipaddr.end", endIp);
 +            }
 +            return params;
 +        } catch (final FileNotFoundException ex) {
 +            throw new CloudRuntimeException("Cannot find the file: " + file.getAbsolutePath(), ex);
 +        } catch (final IOException ex) {
 +            throw new CloudRuntimeException("IOException in reading " + file.getAbsolutePath(), ex);
 +        }
 +    }
 +
 +    protected String getDefaultNetworkScriptsDir() {
 +        return "scripts/vm/network/vnet";
 +    }
 +
 +    protected String getDefaultStorageScriptsDir() {
 +        return "scripts/storage/qcow2";
 +    }
 +
 +    protected String getDefaultHypervisorScriptsDir() {
 +        return "scripts/vm/hypervisor";
 +    }
 +
 +    protected String getDefaultKvmScriptsDir() {
 +        return "scripts/vm/hypervisor/kvm";
 +    }
 +
 +    protected String getDefaultDomrScriptsDir() {
 +        return "scripts/network/domr";
 +    }
 +
 +    protected String getNetworkDirectSourceMode() {
 +        return _networkDirectSourceMode;
 +    }
 +
 +    protected String getNetworkDirectDevice() {
 +        return _networkDirectDevice;
 +    }
 +
 +    @Override
 +    public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
 +        boolean success = super.configure(name, params);
 +        if (!success) {
 +            return false;
 +        }
 +
 +        _storage = new JavaStorageLayer();
 +        _storage.configure("StorageLayer", params);
 +
 +        String domrScriptsDir = (String)params.get("domr.scripts.dir");
 +        if (domrScriptsDir == null) {
 +            domrScriptsDir = getDefaultDomrScriptsDir();
 +        }
 +
 +        String hypervisorScriptsDir = (String)params.get("hypervisor.scripts.dir");
 +        if (hypervisorScriptsDir == null) {
 +            hypervisorScriptsDir = getDefaultHypervisorScriptsDir();
 +        }
 +
 +        String kvmScriptsDir = (String)params.get("kvm.scripts.dir");
 +        if (kvmScriptsDir == null) {
 +            kvmScriptsDir = getDefaultKvmScriptsDir();
 +        }
 +
 +        String networkScriptsDir = (String)params.get("network.scripts.dir");
 +        if (networkScriptsDir == null) {
 +            networkScriptsDir = getDefaultNetworkScriptsDir();
 +        }
 +
 +        String storageScriptsDir = (String)params.get("storage.scripts.dir");
 +        if (storageScriptsDir == null) {
 +            storageScriptsDir = getDefaultStorageScriptsDir();
 +        }
 +
 +        final String bridgeType = (String)params.get("network.bridge.type");
 +        if (bridgeType == null) {
 +            _bridgeType = BridgeType.NATIVE;
 +        } else {
 +            _bridgeType = BridgeType.valueOf(bridgeType.toUpperCase());
 +        }
 +
 +        String dpdk = (String) params.get("openvswitch.dpdk.enabled");
 +        if (_bridgeType == BridgeType.OPENVSWITCH && Boolean.parseBoolean(dpdk)) {
 +            dpdkSupport = true;
 +            dpdkOvsPath = (String) params.get("openvswitch.dpdk.ovs.path");
 +            if (dpdkOvsPath != null && !dpdkOvsPath.endsWith("/")) {
 +                dpdkOvsPath += "/";
 +            }
 +        }
 +
 +        params.put("domr.scripts.dir", domrScriptsDir);
 +
 +        _virtRouterResource = new VirtualRoutingResource(this);
 +        success = _virtRouterResource.configure(name, params);
 +
 +        if (!success) {
 +            return false;
 +        }
 +
 +        _host = (String)params.get("host");
 +        if (_host == null) {
 +            _host = "localhost";
 +        }
 +
 +        _dcId = (String)params.get("zone");
 +        if (_dcId == null) {
 +            _dcId = "default";
 +        }
 +
 +        _pod = (String)params.get("pod");
 +        if (_pod == null) {
 +            _pod = "default";
 +        }
 +
 +        _clusterId = (String)params.get("cluster");
 +
 +        _updateHostPasswdPath = Script.findScript(hypervisorScriptsDir, VRScripts.UPDATE_HOST_PASSWD);
 +        if (_updateHostPasswdPath == null) {
 +            throw new ConfigurationException("Unable to find update_host_passwd.sh");
 +        }
 +
 +        _modifyVlanPath = Script.findScript(networkScriptsDir, "modifyvlan.sh");
 +        if (_modifyVlanPath == null) {
 +            throw new ConfigurationException("Unable to find modifyvlan.sh");
 +        }
 +
 +        _versionstringpath = Script.findScript(kvmScriptsDir, "versions.sh");
 +        if (_versionstringpath == null) {
 +            throw new ConfigurationException("Unable to find versions.sh");
 +        }
 +
-         _patchViaSocketPath = Script.findScript(kvmScriptsDir + "/patch/", "patchviasocket.py");
-         if (_patchViaSocketPath == null) {
-             throw new ConfigurationException("Unable to find patchviasocket.py");
++        _patchScriptPath = Script.findScript(kvmScriptsDir, "patch.sh");
++        if (_patchScriptPath == null) {
++            throw new ConfigurationException("Unable to find patch.sh");
 +        }
 +
 +        _heartBeatPath = Script.findScript(kvmScriptsDir, "kvmheartbeat.sh");
 +        if (_heartBeatPath == null) {
 +            throw new ConfigurationException("Unable to find kvmheartbeat.sh");
 +        }
 +
 +        _createvmPath = Script.findScript(storageScriptsDir, "createvm.sh");
 +        if (_createvmPath == null) {
 +            throw new ConfigurationException("Unable to find the createvm.sh");
 +        }
 +
 +        _manageSnapshotPath = Script.findScript(storageScriptsDir, "managesnapshot.sh");
 +        if (_manageSnapshotPath == null) {
 +            throw new ConfigurationException("Unable to find the managesnapshot.sh");
 +        }
 +
 +        _resizeVolumePath = Script.findScript(storageScriptsDir, "resizevolume.sh");
 +        if (_resizeVolumePath == null) {
 +            throw new ConfigurationException("Unable to find the resizevolume.sh");
 +        }
 +
 +        _vmActivityCheckPath = Script.findScript(kvmScriptsDir, "kvmvmactivity.sh");
 +        if (_vmActivityCheckPath == null) {
 +            throw new ConfigurationException("Unable to find kvmvmactivity.sh");
 +        }
 +
 +        _createTmplPath = Script.findScript(storageScriptsDir, "createtmplt.sh");
 +        if (_createTmplPath == null) {
 +            throw new ConfigurationException("Unable to find the createtmplt.sh");
 +        }
 +
 +        _securityGroupPath = Script.findScript(networkScriptsDir, "security_group.py");
 +        if (_securityGroupPath == null) {
 +            throw new ConfigurationException("Unable to find the security_group.py");
 +        }
 +
 +        _ovsTunnelPath = Script.findScript(networkScriptsDir, "ovstunnel.py");
 +        if (_ovsTunnelPath == null) {
 +            throw new ConfigurationException("Unable to find the ovstunnel.py");
 +        }
 +
 +        _routerProxyPath = Script.findScript("scripts/network/domr/", "router_proxy.sh");
 +        if (_routerProxyPath == null) {
 +            throw new ConfigurationException("Unable to find the router_proxy.sh");
 +        }
 +
 +        _ovsPvlanDhcpHostPath = Script.findScript(networkScriptsDir, "ovs-pvlan-dhcp-host.sh");
 +        if (_ovsPvlanDhcpHostPath == null) {
 +            throw new ConfigurationException("Unable to find the ovs-pvlan-dhcp-host.sh");
 +        }
 +
 +        _ovsPvlanVmPath = Script.findScript(networkScriptsDir, "ovs-pvlan-vm.sh");
 +        if (_ovsPvlanVmPath == null) {
 +            throw new ConfigurationException("Unable to find the ovs-pvlan-vm.sh");
 +        }
 +
 +        String value = (String)params.get("developer");
 +        final boolean isDeveloper = Boolean.parseBoolean(value);
 +
 +        if (isDeveloper) {
 +            params.putAll(getDeveloperProperties());
 +        }
 +
 +        _pool = (String)params.get("pool");
 +        if (_pool == null) {
 +            _pool = "/root";
 +        }
 +
 +        final String instance = (String)params.get("instance");
 +
 +        _hypervisorType = HypervisorType.getType((String)params.get("hypervisor.type"));
 +        if (_hypervisorType == HypervisorType.None) {
 +            _hypervisorType = HypervisorType.KVM;
 +        }
 +
 +        _hypervisorURI = (String)params.get("hypervisor.uri");
 +        if (_hypervisorURI == null) {
 +            _hypervisorURI = LibvirtConnection.getHypervisorURI(_hypervisorType.toString());
 +        }
 +
 +        _networkDirectSourceMode = (String)params.get("network.direct.source.mode");
 +        _networkDirectDevice = (String)params.get("network.direct.device");
 +
 +        String startMac = (String)params.get("private.macaddr.start");
 +        if (startMac == null) {
 +            startMac = "00:16:3e:77:e2:a0";
 +        }
 +
 +        String startIp = (String)params.get("private.ipaddr.start");
 +        if (startIp == null) {
 +            startIp = "192.168.166.128";
 +        }
 +
 +        _pingTestPath = Script.findScript(kvmScriptsDir, "pingtest.sh");
 +        if (_pingTestPath == null) {
 +            throw new ConfigurationException("Unable to find the pingtest.sh");
 +        }
 +
 +        _linkLocalBridgeName = (String)params.get("private.bridge.name");
 +        if (_linkLocalBridgeName == null) {
 +            if (isDeveloper) {
 +                _linkLocalBridgeName = "cloud-" + instance + "-0";
 +            } else {
 +                _linkLocalBridgeName = "cloud0";
 +            }
 +        }
 +
 +        _publicBridgeName = (String)params.get("public.network.device");
 +        if (_publicBridgeName == null) {
 +            _publicBridgeName = "cloudbr0";
 +        }
 +
 +        _privBridgeName = (String)params.get("private.network.device");
 +        if (_privBridgeName == null) {
 +            _privBridgeName = "cloudbr1";
 +        }
 +
 +        _guestBridgeName = (String)params.get("guest.network.device");
 +        if (_guestBridgeName == null) {
 +            _guestBridgeName = _privBridgeName;
 +        }
 +
 +        _privNwName = (String)params.get("private.network.name");
 +        if (_privNwName == null) {
 +            if (isDeveloper) {
 +                _privNwName = "cloud-" + instance + "-private";
 +            } else {
 +                _privNwName = "cloud-private";
 +            }
 +        }
 +
 +        _localStoragePath = (String)params.get("local.storage.path");
 +        if (_localStoragePath == null) {
 +            _localStoragePath = "/var/lib/libvirt/images/";
 +        }
 +
 +        /* Directory to use for Qemu sockets like for the Qemu Guest Agent */
 +        _qemuSocketsPath = new File("/var/lib/libvirt/qemu");
 +        String _qemuSocketsPathVar = (String)params.get("qemu.sockets.path");
 +        if (_qemuSocketsPathVar != null && StringUtils.isNotBlank(_qemuSocketsPathVar)) {
 +            _qemuSocketsPath = new File(_qemuSocketsPathVar);
 +        }
 +
 +        final File storagePath = new File(_localStoragePath);
 +        _localStoragePath = storagePath.getAbsolutePath();
 +
 +        _localStorageUUID = (String)params.get("local.storage.uuid");
 +        if (_localStorageUUID == null) {
 +            _localStorageUUID = UUID.randomUUID().toString();
 +        }
 +
 +        value = (String)params.get("scripts.timeout");
 +        _timeout = Duration.standardSeconds(NumbersUtil.parseInt(value, 30 * 60));
 +
 +        value = (String)params.get("stop.script.timeout");
 +        _stopTimeout = NumbersUtil.parseInt(value, 120) * 1000;
 +
 +        value = (String)params.get("cmds.timeout");
 +        _cmdsTimeout = NumbersUtil.parseInt(value, 7200) * 1000;
 +
 +        value = (String) params.get("vm.memballoon.disable");
 +        if (Boolean.parseBoolean(value)) {
 +            _noMemBalloon = true;
 +        }
 +
 +        _videoHw = (String) params.get("vm.video.hardware");
 +        value = (String) params.get("vm.video.ram");
 +        _videoRam = NumbersUtil.parseInt(value, 0);
 +
 +        value = (String)params.get("host.reserved.mem.mb");
 +        // Reserve 1GB unless admin overrides
 +        _dom0MinMem = NumbersUtil.parseInt(value, 1024) * 1024* 1024L;
 +
 +        value = (String)params.get("host.overcommit.mem.mb");
 +        // Support overcommit memory for host if host uses ZSWAP, KSM and other memory
 +        // compressing technologies
 +        _dom0OvercommitMem = NumbersUtil.parseInt(value, 0) * 1024 * 1024L;
 +
 +        value = (String) params.get("kvmclock.disable");
 +        if (Boolean.parseBoolean(value)) {
 +            _noKvmClock = true;
 +        }
 +
 +        value = (String) params.get("vm.rng.enable");
 +        if (Boolean.parseBoolean(value)) {
 +            _rngEnable = true;
 +
 +            value = (String) params.get("vm.rng.model");
 +            if (!Strings.isNullOrEmpty(value)) {
 +                _rngBackendModel = RngBackendModel.valueOf(value.toUpperCase());
 +            }
 +
 +            value = (String) params.get("vm.rng.path");
 +            if (!Strings.isNullOrEmpty(value)) {
 +                _rngPath = value;
 +            }
 +
 +            value = (String) params.get("vm.rng.rate.bytes");
 +            _rngRateBytes = NumbersUtil.parseInt(value, new Integer(_rngRateBytes));
 +
 +            value = (String) params.get("vm.rng.rate.period");
 +            _rngRatePeriod = NumbersUtil.parseInt(value, new Integer(_rngRatePeriod));
 +        }
 +
 +        value = (String) params.get("vm.watchdog.model");
 +        if (!Strings.isNullOrEmpty(value)) {
 +            _watchDogModel = WatchDogModel.valueOf(value.toUpperCase());
 +        }
 +
 +        value = (String) params.get("vm.watchdog.action");
 +        if (!Strings.isNullOrEmpty(value)) {
 +            _watchDogAction = WatchDogAction.valueOf(value.toUpperCase());
 +        }
 +
 +        LibvirtConnection.initialize(_hypervisorURI);
 +        Connect conn = null;
 +        try {
 +            conn = LibvirtConnection.getConnection();
 +
 +            if (_bridgeType == BridgeType.OPENVSWITCH) {
 +                if (conn.getLibVirVersion() < 10 * 1000 + 0) {
 +                    throw new ConfigurationException("Libvirt version 0.10.0 required for openvswitch support, but version " + conn.getLibVirVersion() + " detected");
 +                }
 +            }
 +        } catch (final LibvirtException e) {
 +            throw new CloudRuntimeException(e.getMessage());
 +        }
 +
 +        if (HypervisorType.KVM == _hypervisorType) {
 +            /* Does node support HVM guest? If not, exit */
 +            if (!IsHVMEnabled(conn)) {
 +                throw new ConfigurationException("NO HVM support on this machine, please make sure: " + "1. VT/SVM is supported by your CPU, or is enabled in BIOS. "
 +                        + "2. kvm modules are loaded (kvm, kvm_amd|kvm_intel)");
 +            }
 +        }
 +
 +        _hypervisorPath = getHypervisorPath(conn);
 +        try {
 +            _hvVersion = conn.getVersion();
 +            _hvVersion = _hvVersion % 1000000 / 1000;
 +            _hypervisorLibvirtVersion = conn.getLibVirVersion();
 +            _hypervisorQemuVersion = conn.getVersion();
 +        } catch (final LibvirtException e) {
 +            s_logger.trace("Ignoring libvirt error.", e);
 +        }
 +
 +        _guestCpuMode = (String)params.get("guest.cpu.mode");
 +        if (_guestCpuMode != null) {
 +            _guestCpuModel = (String)params.get("guest.cpu.model");
 +
 +            if (_hypervisorLibvirtVersion < 9 * 1000 + 10) {
 +                s_logger.warn("Libvirt version 0.9.10 required for guest cpu mode, but version " + prettyVersion(_hypervisorLibvirtVersion) +
 +                        " detected, so it will be disabled");
 +                _guestCpuMode = "";
 +                _guestCpuModel = "";
 +            }
 +            params.put("guest.cpu.mode", _guestCpuMode);
 +            params.put("guest.cpu.model", _guestCpuModel);
 +        }
 +
 +        final String cpuFeatures = (String)params.get("guest.cpu.features");
 +        if (cpuFeatures != null) {
 +            _cpuFeatures = new ArrayList<String>();
 +            for (final String feature: cpuFeatures.split(" ")) {
 +                if (!feature.isEmpty()) {
 +                    _cpuFeatures.add(feature);
 +                }
 +            }
 +        }
 +
 +        final String[] info = NetUtils.getNetworkParams(_privateNic);
 +
 +        _monitor = new KVMHAMonitor(null, info[0], _heartBeatPath);
 +        final Thread ha = new Thread(_monitor);
 +        ha.start();
 +
 +        _storagePoolMgr = new KVMStoragePoolManager(_storage, _monitor);
 +
 +        _sysvmISOPath = (String)params.get("systemvm.iso.path");
 +        if (_sysvmISOPath == null) {
 +            final String[] isoPaths = {"/usr/share/cloudstack-common/vms/systemvm.iso"};
 +            for (final String isoPath : isoPaths) {
 +                if (_storage.exists(isoPath)) {
 +                    _sysvmISOPath = isoPath;
 +                    break;
 +                }
 +            }
 +            if (_sysvmISOPath == null) {
 +                s_logger.debug("Can't find system vm ISO");
 +            }
 +        }
 +
 +        final Map<String, String> bridges = new HashMap<String, String>();
 +
 +        params.put("libvirt.host.bridges", bridges);
 +        params.put("libvirt.host.pifs", _pifs);
 +
 +        params.put("libvirt.computing.resource", this);
 +        params.put("libvirtVersion", _hypervisorLibvirtVersion);
 +
 +
 +        configureVifDrivers(params);
 +
 +        /*
 +        switch (_bridgeType) {
 +        case OPENVSWITCH:
 +            getOvsPifs();
 +            break;
 +        case NATIVE:
 +        default:
 +            getPifs();
 +            break;
 +        }
 +        */
 +
 +        if (_pifs.get("private") == null) {
 +            s_logger.error("Failed to get private nic name");
 +            throw new ConfigurationException("Failed to get private nic name");
 +        }
 +
 +        if (_pifs.get("public") == null) {
 +            s_logger.error("Failed to get public nic name");
 +            throw new ConfigurationException("Failed to get public nic name");
 +        }
 +        s_logger.debug("Found pif: " + _pifs.get("private") + " on " + _privBridgeName + ", pif: " + _pifs.get("public") + " on " + _publicBridgeName);
 +
 +        _canBridgeFirewall = canBridgeFirewall(_pifs.get("public"));
 +
 +        _localGateway = Script.runSimpleBashScript("ip route show default 0.0.0.0/0|head -1|awk '{print $3}'");
 +        if (_localGateway == null) {
 +            s_logger.warn("No default IPv4 gateway found");
 +        }
 +
 +        _mountPoint = (String)params.get("mount.path");
 +        if (_mountPoint == null) {
 +            _mountPoint = "/mnt";
 +        }
 +
 +        value = (String) params.get("vm.migrate.downtime");
 +        _migrateDowntime = NumbersUtil.parseInt(value, -1);
 +
 +        value = (String) params.get("vm.migrate.pauseafter");
 +        _migratePauseAfter = NumbersUtil.parseInt(value, -1);
 +
 +        value = (String)params.get("vm.migrate.speed");
 +        _migrateSpeed = NumbersUtil.parseInt(value, -1);
 +        if (_migrateSpeed == -1) {
 +            //get guest network device speed
 +            _migrateSpeed = 0;
 +            final String speed = Script.runSimpleBashScript("ethtool " + _pifs.get("public") + " |grep Speed | cut -d \\  -f 2");
 +            if (speed != null) {
 +                final String[] tokens = speed.split("M");
 +                if (tokens.length == 2) {
 +                    try {
 +                        _migrateSpeed = Integer.parseInt(tokens[0]);
 +                    } catch (final NumberFormatException e) {
 +                        s_logger.trace("Ignoring migrateSpeed extraction error.", e);
 +                    }
 +                    s_logger.debug("device " + _pifs.get("public") + " has speed: " + String.valueOf(_migrateSpeed));
 +                }
 +            }
 +            params.put("vm.migrate.speed", String.valueOf(_migrateSpeed));
 +        }
 +
 +        bridges.put("linklocal", _linkLocalBridgeName);
 +        bridges.put("public", _publicBridgeName);
 +        bridges.put("private", _privBridgeName);
 +        bridges.put("guest", _guestBridgeName);
 +
 +        getVifDriver(TrafficType.Control).createControlNetwork(_linkLocalBridgeName);
 +
 +        configureDiskActivityChecks(params);
 +
 +        final KVMStorageProcessor storageProcessor = new KVMStorageProcessor(_storagePoolMgr, this);
 +        storageProcessor.configure(name, params);
 +        storageHandler = new StorageSubsystemCommandHandlerBase(storageProcessor);
 +
 +        return true;
 +    }
 +
 +    protected void configureDiskActivityChecks(final Map<String, Object> params) {
 +        _diskActivityCheckEnabled = Boolean.parseBoolean((String)params.get("vm.diskactivity.checkenabled"));
 +        if (_diskActivityCheckEnabled) {
 +            final int timeout = NumbersUtil.parseInt((String)params.get("vm.diskactivity.checktimeout_s"), 0);
 +            if (timeout > 0) {
 +                _diskActivityCheckTimeoutSeconds = timeout;
 +            }
 +            final long inactiveTime = NumbersUtil.parseLong((String)params.get("vm.diskactivity.inactivetime_ms"), 0L);
 +            if (inactiveTime > 0) {
 +                _diskActivityInactiveThresholdMilliseconds = inactiveTime;
 +            }
 +        }
 +    }
 +
 +    protected void configureVifDrivers(final Map<String, Object> params) throws ConfigurationException {
 +        final String LIBVIRT_VIF_DRIVER = "libvirt.vif.driver";
 +
 +        _trafficTypeVifDrivers = new HashMap<TrafficType, VifDriver>();
 +
 +        // Load the default vif driver
 +        String defaultVifDriverName = (String)params.get(LIBVIRT_VIF_DRIVER);
 +        if (defaultVifDriverName == null) {
 +            if (_bridgeType == BridgeType.OPENVSWITCH) {
 +                s_logger.info("No libvirt.vif.driver specified. Defaults to OvsVifDriver.");
 +                defaultVifDriverName = DEFAULT_OVS_VIF_DRIVER_CLASS_NAME;
 +            } else {
 +                s_logger.info("No libvirt.vif.driver specified. Defaults to BridgeVifDriver.");
 +                defaultVifDriverName = DEFAULT_BRIDGE_VIF_DRIVER_CLASS_NAME;
 +            }
 +        }
 +        _defaultVifDriver = getVifDriverClass(defaultVifDriverName, params);
 +
 +        // Load any per-traffic-type vif drivers
 +        for (final Map.Entry<String, Object> entry : params.entrySet()) {
 +            final String k = entry.getKey();
 +            final String vifDriverPrefix = LIBVIRT_VIF_DRIVER + ".";
 +
 +            if (k.startsWith(vifDriverPrefix)) {
 +                // Get trafficType
 +                final String trafficTypeSuffix = k.substring(vifDriverPrefix.length());
 +
 +                // Does this suffix match a real traffic type?
 +                final TrafficType trafficType = TrafficType.getTrafficType(trafficTypeSuffix);
 +                if (!trafficType.equals(TrafficType.None)) {
 +                    // Get vif driver class name
 +                    final String vifDriverClassName = (String)entry.getValue();
 +                    // if value is null, ignore
 +                    if (vifDriverClassName != null) {
 +                        // add traffic type to vif driver mapping to Map
 +                        _trafficTypeVifDrivers.put(trafficType, getVifDriverClass(vifDriverClassName, params));
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    protected VifDriver getVifDriverClass(final String vifDriverClassName, final Map<String, Object> params) throws ConfigurationException {
 +        VifDriver vifDriver;
 +
 +        try {
 +            final Class<?> clazz = Class.forName(vifDriverClassName);
 +            vifDriver = (VifDriver)clazz.newInstance();
 +            vifDriver.configure(params);
 +        } catch (final ClassNotFoundException e) {
 +            throw new ConfigurationException("Unable to find class for libvirt.vif.driver " + e);
 +        } catch (final InstantiationException e) {
 +            throw new ConfigurationException("Unable to instantiate class for libvirt.vif.driver " + e);
 +        } catch (final IllegalAccessException e) {
 +            throw new ConfigurationException("Unable to instantiate class for libvirt.vif.driver " + e);
 +        }
 +        return vifDriver;
 +    }
 +
 +    public VifDriver getVifDriver(final TrafficType trafficType) {
 +        VifDriver vifDriver = _trafficTypeVifDrivers.get(trafficType);
 +
 +        if (vifDriver == null) {
 +            vifDriver = _defaultVifDriver;
 +        }
 +
 +        return vifDriver;
 +    }
 +
 +    public VifDriver getVifDriver(final TrafficType trafficType, final String bridgeName) {
 +        VifDriver vifDriver = null;
 +
 +        for (VifDriver driver : getAllVifDrivers()) {
 +            if (driver.isExistingBridge(bridgeName)) {
 +                vifDriver = driver;
 +                break;
 +            }
 +        }
 +
 +        if (vifDriver == null) {
 +            vifDriver = getVifDriver(trafficType);
 +        }
 +
 +        return vifDriver;
 +    }
 +
 +    public List<VifDriver> getAllVifDrivers() {
 +        final Set<VifDriver> vifDrivers = new HashSet<VifDriver>();
 +
 +        vifDrivers.add(_defaultVifDriver);
 +        vifDrivers.addAll(_trafficTypeVifDrivers.values());
 +
 +        final ArrayList<VifDriver> vifDriverList = new ArrayList<VifDriver>(vifDrivers);
 +
 +        return vifDriverList;
 +    }
 +
 +    private void getPifs() {
 +        final File dir = new File("/sys/devices/virtual/net");
 +        final File[] netdevs = dir.listFiles();
 +        final List<String> bridges = new ArrayList<String>();
 +        for (int i = 0; i < netdevs.length; i++) {
 +            final File isbridge = new File(netdevs[i].getAbsolutePath() + "/bridge");
 +            final String netdevName = netdevs[i].getName();
 +            s_logger.debug("looking in file " + netdevs[i].getAbsolutePath() + "/bridge");
 +            if (isbridge.exists()) {
 +                s_logger.debug("Found bridge " + netdevName);
 +                bridges.add(netdevName);
 +            }
 +        }
 +
 +        for (final String bridge : bridges) {
 +            s_logger.debug("looking for pif for bridge " + bridge);
 +            final String pif = getPif(bridge);
 +            if (isPublicBridge(bridge)) {
 +                _pifs.put("public", pif);
 +            }
 +            if (isGuestBridge(bridge)) {
 +                _pifs.put("private", pif);
 +            }
 +            _pifs.put(bridge, pif);
 +        }
 +
 +        // guest(private) creates bridges on a pif, if private bridge not found try pif direct
 +        // This addresses the unnecessary requirement of someone to create an unused bridge just for traffic label
 +        if (_pifs.get("private") == null) {
 +            s_logger.debug("guest(private) traffic label '" + _guestBridgeName + "' not found as bridge, looking for physical interface");
 +            final File dev = new File("/sys/class/net/" + _guestBridgeName);
 +            if (dev.exists()) {
 +                s_logger.debug("guest(private) traffic label '" + _guestBridgeName + "' found as a physical device");
 +                _pifs.put("private", _guestBridgeName);
 +            }
 +        }
 +
 +        // public creates bridges on a pif, if private bridge not found try pif direct
 +        // This addresses the unnecessary requirement of someone to create an unused bridge just for traffic label
 +        if (_pifs.get("public") == null) {
 +            s_logger.debug("public traffic label '" + _publicBridgeName+ "' not found as bridge, looking for physical interface");
 +            final File dev = new File("/sys/class/net/" + _publicBridgeName);
 +            if (dev.exists()) {
 +                s_logger.debug("public traffic label '" + _publicBridgeName + "' found as a physical device");
 +                _pifs.put("public", _publicBridgeName);
 +            }
 +        }
 +
 +        s_logger.debug("done looking for pifs, no more bridges");
 +    }
 +
 +    boolean isGuestBridge(String bridge) {
 +        return _guestBridgeName != null && bridge.equals(_guestBridgeName);
 +    }
 +
 +    private void getOvsPifs() {
 +        final String cmdout = Script.runSimpleBashScript("ovs-vsctl list-br | sed '{:q;N;s/\\n/%/g;t q}'");
 +        s_logger.debug("cmdout was " + cmdout);
 +        final List<String> bridges = Arrays.asList(cmdout.split("%"));
 +        for (final String bridge : bridges) {
 +            s_logger.debug("looking for pif for bridge " + bridge);
 +            // String pif = getOvsPif(bridge);
 +            // Not really interested in the pif name at this point for ovs
 +            // bridges
 +            final String pif = bridge;
 +            if (isPublicBridge(bridge)) {
 +                _pifs.put("public", pif);
 +            }
 +            if (isGuestBridge(bridge)) {
 +                _pifs.put("private", pif);
 +            }
 +            _pifs.put(bridge, pif);
 +        }
 +        s_logger.debug("done looking for pifs, no more bridges");
 +    }
 +
 +    public boolean isPublicBridge(String bridge) {
 +        return _publicBridgeName != null && bridge.equals(_publicBridgeName);
 +    }
 +
 +    private String getPif(final String bridge) {
 +        String pif = matchPifFileInDirectory(bridge);
 +        final File vlanfile = new File("/proc/net/vlan/" + pif);
 +
 +        if (vlanfile.isFile()) {
 +            pif = Script.runSimpleBashScript("grep ^Device\\: /proc/net/vlan/" + pif + " | awk {'print $2'}");
 +        }
 +
 +        return pif;
 +    }
 +
 +    private String matchPifFileInDirectory(final String bridgeName) {
 +        final File brif = new File("/sys/devices/virtual/net/" + bridgeName + "/brif");
 +
 +        if (!brif.isDirectory()) {
 +            final File pif = new File("/sys/class/net/" + bridgeName);
 +            if (pif.isDirectory()) {
 +                // if bridgeName already refers to a pif, return it as-is
 +                return bridgeName;
 +            }
 +            s_logger.debug("failing to get physical interface from bridge " + bridgeName + ", does " + brif.getAbsolutePath() + "exist?");
 +            return "";
 +        }
 +
 +        final File[] interfaces = brif.listFiles();
 +
 +        for (int i = 0; i < interfaces.length; i++) {
 +            final String fname = interfaces[i].getName();
 +            s_logger.debug("matchPifFileInDirectory: file name '" + fname + "'");
 +            if (isInterface(fname)) {
 +                return fname;
 +            }
 +        }
 +
 +        s_logger.debug("failing to get physical interface from bridge " + bridgeName + ", did not find an eth*, bond*, team*, vlan*, em*, p*p*, ens*, eno*, enp*, or enx* in " + brif.getAbsolutePath());
 +        return "";
 +    }
 +
 +    static String [] ifNamePatterns = {
 +            "^eth",
 +            "^bond",
 +            "^vlan",
 +            "^vx",
 +            "^em",
 +            "^ens",
 +            "^eno",
 +            "^enp",
 +            "^team",
 +            "^enx",
 +            "^dummy",
 +            "^lo",
 +            "^p\\d+p\\d+"
 +    };
 +
 +    /**
 +     * @param fname
 +     * @return
 +     */
 +    protected static boolean isInterface(final String fname) {
 +        StringBuffer commonPattern = new StringBuffer();
 +        for (final String ifNamePattern : ifNamePatterns) {
 +            commonPattern.append("|(").append(ifNamePattern).append(".*)");
 +        }
 +        if(fname.matches(commonPattern.toString())) {
 +            return true;
 +        }
 +        return false;
 +    }
 +
 +    public boolean checkNetwork(final TrafficType trafficType, final String networkName) {
 +        if (networkName == null) {
 +            return true;
 +        }
 +
 +        if (getVifDriver(trafficType, networkName) instanceof OvsVifDriver) {
 +            return checkOvsNetwork(networkName);
 +        } else {
 +            return checkBridgeNetwork(networkName);
 +        }
 +    }
 +
 +    private boolean checkBridgeNetwork(final String networkName) {
 +        if (networkName == null) {
 +            return true;
 +        }
 +
 +        final String name = matchPifFileInDirectory(networkName);
 +
 +        if (name == null || name.isEmpty()) {
 +            return false;
 +        } else {
 +            return true;
 +        }
 +    }
 +
 +    private boolean checkOvsNetwork(final String networkName) {
 +        s_logger.debug("Checking if network " + networkName + " exists as openvswitch bridge");
 +        if (networkName == null) {
 +            return true;
 +        }
 +
 +        final Script command = new Script("/bin/sh", _timeout);
 +        command.add("-c");
 +        command.add("ovs-vsctl br-exists " + networkName);
 +        return "0".equals(command.execute(null));
 +    }
 +
 +    public boolean passCmdLine(final String vmName, final String cmdLine) throws InternalErrorException {
-         final Script command = new Script(_patchViaSocketPath, 5 * 1000, s_logger);
++        final Script command = new Script(_patchScriptPath, 30 * 1000, s_logger);
 +        String result;
 +        command.add("-n", vmName);
-         command.add("-p", cmdLine.replaceAll(" ", "%"));
++        command.add("-c", cmdLine);
 +        result = command.execute();
 +        if (result != null) {
-             s_logger.error("passcmd failed:" + result);
++            s_logger.error("Passing cmdline failed:" + result);
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    boolean isDirectAttachedNetwork(final String type) {
 +        if ("untagged".equalsIgnoreCase(type)) {
 +            return true;
 +        } else {
 +            try {
 +                Long.valueOf(type);
 +            } catch (final NumberFormatException e) {
 +                return true;
 +            }
 +            return false;
 +        }
 +    }
 +
 +    public String startVM(final Connect conn, final String vmName, final String domainXML) throws LibvirtException, InternalErrorException {
 +        try {
 +            /*
 +                We create a transient domain here. When this method gets
 +                called we receive a full XML specification of the guest,
 +                so no need to define it persistent.
 +
 +                This also makes sure we never have any old "garbage" defined
 +                in libvirt which might haunt us.
 +             */
 +
 +            // check for existing inactive vm definition and remove it
 +            // this can sometimes happen during crashes, etc
 +            Domain dm = null;
 +            try {
 +                dm = conn.domainLookupByName(vmName);
 +                if (dm != null && dm.isPersistent() == 1) {
 +                    // this is safe because it doesn't stop running VMs
 +                    dm.undefine();
 +                }
 +            } catch (final LibvirtException e) {
 +                // this is what we want, no domain found
 +            } finally {
 +                if (dm != null) {
 +                    dm.free();
 +                }
 +            }
 +
 +            conn.domainCreateXML(domainXML, 0);
 +        } catch (final LibvirtException e) {
 +            throw e;
 +        }
 +        return null;
 +    }
 +
 +    @Override
 +    public boolean stop() {
 +        try {
 +            final Connect conn = LibvirtConnection.getConnection();
 +            conn.close();
 +        } catch (final LibvirtException e) {
 +            s_logger.trace("Ignoring libvirt error.", e);
 +        }
 +
 +        return true;
 +    }
 +
 +    /**
 +     * This finds a command wrapper to handle the command and executes it.
 +     * If no wrapper is found an {@see UnsupportedAnswer} is sent back.
 +     * Any other exceptions are to be caught and wrapped in an generic {@see Answer}, marked as failed.
 +     *
 +     * @param cmd the instance of a {@see Command} to execute.
 +     * @return the for the {@see Command} appropriate {@see Answer} or {@see UnsupportedAnswer}
 +     */
 +    @Override
 +    public Answer executeRequest(final Command cmd) {
 +
 +        final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance();
 +        try {
 +            return wrapper.execute(cmd, this);
 +        } catch (final RequestWrapper.CommandNotSupported cmde) {
 +            return Answer.createUnsupportedCommandAnswer(cmd);
 +        }
 +    }
 +
 +    public synchronized boolean destroyTunnelNetwork(final String bridge) {
 +        findOrCreateTunnelNetwork(bridge);
 +
 +        final Script cmd = new Script(_ovsTunnelPath, _timeout, s_logger);
 +        cmd.add("destroy_ovs_bridge");
 +        cmd.add("--bridge", bridge);
 +
 +        final String result = cmd.execute();
 +
 +        if (result != null) {
 +            s_logger.debug("OVS Bridge could not be destroyed due to error ==> " + result);
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    public synchronized boolean findOrCreateTunnelNetwork(final String nwName) {
 +        try {
 +            if (checkNetwork(TrafficType.Guest, nwName)) {
 +                return true;
 +            }
 +            // if not found, create a new one
 +            final Map<String, String> otherConfig = new HashMap<String, String>();
 +            otherConfig.put("ovs-host-setup", "");
 +            Script.runSimpleBashScript("ovs-vsctl -- --may-exist add-br "
 +                    + nwName + " -- set bridge " + nwName
 +                    + " other_config:ovs-host-setup='-1'");
 +            s_logger.debug("### KVM network for tunnels created:" + nwName);
 +        } catch (final Exception e) {
 +            s_logger.warn("createTunnelNetwork failed", e);
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    public synchronized boolean configureTunnelNetwork(final long networkId,
 +                                                       final long hostId, final String nwName) {
 +        try {
 +            final boolean findResult = findOrCreateTunnelNetwork(nwName);
 +            if (!findResult) {
 +                s_logger.warn("LibvirtComputingResource.findOrCreateTunnelNetwork() failed! Cannot proceed creating the tunnel.");
 +                return false;
 +            }
 +            final String configuredHosts = Script
 +                    .runSimpleBashScript("ovs-vsctl get bridge " + nwName
 +                            + " other_config:ovs-host-setup");
 +            boolean configured = false;
 +            if (configuredHosts != null) {
 +                final String hostIdsStr[] = configuredHosts.split(",");
 +                for (final String hostIdStr : hostIdsStr) {
 +                    if (hostIdStr.equals(((Long)hostId).toString())) {
 +                        configured = true;
 +                        break;
 +                    }
 +                }
 +            }
 +            if (!configured) {
 +                final Script cmd = new Script(_ovsTunnelPath, _timeout, s_logger);
 +                cmd.add("setup_ovs_bridge");
 +                cmd.add("--key", nwName);
 +                cmd.add("--cs_host_id", ((Long)hostId).toString());
 +                cmd.add("--bridge", nwName);
 +                final String result = cmd.execute();
 +                if (result != null) {
 +                    throw new CloudRuntimeException(
 +                            "Unable to pre-configure OVS bridge " + nwName
 +                                    + " for network ID:" + networkId);
 +                }
 +            }
 +        } catch (final Exception e) {
 +            s_logger.warn("createandConfigureTunnelNetwork failed", e);
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    protected Storage.StorageResourceType getStorageResourceType() {
 +        return Storage.StorageResourceType.STORAGE_POOL;
 +    }
 +
 +    // this is much like PrimaryStorageDownloadCommand, but keeping it separate
 +    public KVMPhysicalDisk templateToPrimaryDownload(final String templateUrl, final KVMStoragePool primaryPool, final String volUuid) {
 +        final int index = templateUrl.lastIndexOf("/");
 +        final String mountpoint = templateUrl.substring(0, index);
 +        String templateName = null;
 +        if (index < templateUrl.length() - 1) {
 +            templateName = templateUrl.substring(index + 1);
 +        }
 +
 +        KVMPhysicalDisk templateVol = null;
 +        KVMStoragePool secondaryPool = null;
 +        try {
 +            secondaryPool = _storagePoolMgr.getStoragePoolByURI(mountpoint);
 +            /* Get template vol */
 +            if (templateName == null) {
 +                secondaryPool.refresh();
 +                final List<KVMPhysicalDisk> disks = secondaryPool.listPhysicalDisks();
 +                if (disks == null || disks.isEmpty()) {
 +                    s_logger.error("Failed to get volumes from pool: " + secondaryPool.getUuid());
 +                    return null;
 +                }
 +                for (final KVMPhysicalDisk disk : disks) {
 +                    if (disk.getName().endsWith("qcow2")) {
 +                        templateVol = disk;
 +                        break;
 +                    }
 +                }
 +                if (templateVol == null) {
 +                    s_logger.error("Failed to get template from pool: " + secondaryPool.getUuid());
 +                    return null;
 +                }
 +            } else {
 +                templateVol = secondaryPool.getPhysicalDisk(templateName);
 +            }
 +
 +            /* Copy volume to primary storage */
 +
 +            final KVMPhysicalDisk primaryVol = _storagePoolMgr.copyPhysicalDisk(templateVol, volUuid, primaryPool, 0);
 +            return primaryVol;
 +        } catch (final CloudRuntimeException e) {
 +            s_logger.error("Failed to download template to primary storage", e);
 +            return null;
 +        } finally {
 +            if (secondaryPool != null) {
 +                _storagePoolMgr.deleteStoragePool(secondaryPool.getType(), secondaryPool.getUuid());
 +            }
 +        }
 +    }
 +
 +    public String getResizeScriptType(final KVMStoragePool pool, final KVMPhysicalDisk vol) {
 +        final StoragePoolType poolType = pool.getType();
 +        final PhysicalDiskFormat volFormat = vol.getFormat();
 +
 +        if (pool.getType() == StoragePoolType.CLVM && volFormat == PhysicalDiskFormat.RAW) {
 +            return "CLVM";
 +        } else if ((poolType == StoragePoolType.NetworkFilesystem
 +                || poolType == StoragePoolType.SharedMountPoint
 +                || poolType == StoragePoolType.Filesystem
 +                || poolType == StoragePoolType.Gluster)
 +                && volFormat == PhysicalDiskFormat.QCOW2 ) {
 +            return "QCOW2";
 +        }
 +        throw new CloudRuntimeException("Cannot determine resize type from pool type " + pool.getType());
 +    }
 +
 +    private String getBroadcastUriFromBridge(final String brName) {
 +        final String pif = matchPifFileInDirectory(brName);
 +        final Pattern pattern = Pattern.compile("(\\D+)(\\d+)(\\D*)(\\d*)(\\D*)(\\d*)");
 +        final Matcher matcher = pattern.matcher(pif);
 +        s_logger.debug("getting broadcast uri for pif " + pif + " and bridge " + brName);
 +        if(matcher.find()) {
 +            if (brName.startsWith("brvx")){
 +                return BroadcastDomainType.Vxlan.toUri(matcher.group(2)).toString();
 +            }
 +            else{
 +                if (!matcher.group(6).isEmpty()) {
 +                    return BroadcastDomainType.Vlan.toUri(matcher.group(6)).toString();
 +                } else if (!matcher.group(4).isEmpty()) {
 +                    return BroadcastDomainType.Vlan.toUri(matcher.group(4)).toString();
 +                } else {
 +                    //untagged or not matching (eth|bond|team)#.#
 +                    s_logger.debug("failed to get vNet id from bridge " + brName
 +                            + "attached to physical interface" + pif + ", perhaps untagged interface");
 +                    return "";
 +                }
 +            }
 +        } else {
 +            s_logger.debug("failed to get vNet id from bridge " + brName + "attached to physical interface" + pif);
 +            return "";
 +        }
 +    }
 +
 +    private void VifHotPlug(final Connect conn, final String vmName, final String broadcastUri, final String macAddr) throws InternalErrorException, LibvirtException {
 +        final NicTO nicTO = new NicTO();
 +        nicTO.setMac(macAddr);
 +        nicTO.setType(TrafficType.Public);
 +        if (broadcastUri == null) {
 +            nicTO.setBroadcastType(BroadcastDomainType.Native);
 +        } else {
 +            final URI uri = BroadcastDomainType.fromString(broadcastUri);
 +            nicTO.setBroadcastType(BroadcastDomainType.getSchemeValue(uri));
 +            nicTO.setBroadcastUri(uri);
 +        }
 +
 +        final Domain vm = getDomain(conn, vmName);
 +        vm.attachDevice(getVifDriver(nicTO.getType()).plug(nicTO, "Other PV", "", null).toString());
 +    }
 +
 +
 +    private void vifHotUnPlug (final Connect conn, final String vmName, final String macAddr) throws InternalErrorException, LibvirtException {
 +
 +        Domain vm = null;
 +        vm = getDomain(conn, vmName);
 +        final List<InterfaceDef> pluggedNics = getInterfaces(conn, vmName);
 +        for (final InterfaceDef pluggedNic : pluggedNics) {
 +            if (pluggedNic.getMacAddress().equalsIgnoreCase(macAddr)) {
 +                vm.detachDevice(pluggedNic.toString());
 +                // We don't know which "traffic type" is associated with
 +                // each interface at this point, so inform all vif drivers
 +                for (final VifDriver vifDriver : getAllVifDrivers()) {
 +                    vifDriver.unplug(pluggedNic);
 +                }
 +            }
 +        }
 +    }
 +
 +    private ExecutionResult prepareNetworkElementCommand(final SetupGuestNetworkCommand cmd) {
 +        Connect conn;
 +        final NicTO nic = cmd.getNic();
 +        final String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME);
 +
 +        try {
 +            conn = LibvirtConnection.getConnectionByVmName(routerName);
 +            final List<InterfaceDef> pluggedNics = getInterfaces(conn, routerName);
 +            InterfaceDef routerNic = null;
 +
 +            for (final InterfaceDef pluggedNic : pluggedNics) {
 +                if (pluggedNic.getMacAddress().equalsIgnoreCase(nic.getMac())) {
 +                    routerNic = pluggedNic;
 +                    break;
 +                }
 +            }
 +
 +            if (routerNic == null) {
 +                return new ExecutionResult(false, "Can not find nic with mac " + nic.getMac() + " for VM " + routerName);
 +            }
 +
 +            return new ExecutionResult(true, null);
 +        } catch (final LibvirtException e) {
 +            final String msg = "Creating guest network failed due to " + e.toString();
 +            s_logger.warn(msg, e);
 +            return new ExecutionResult(false, msg);
 +        }
 +    }
 +
 +    protected ExecutionResult prepareNetworkElementCommand(final SetSourceNatCommand cmd) {
 +        Connect conn;
 +        final String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME);
 +        cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP);
 +        final IpAddressTO pubIP = cmd.getIpAddress();
 +
 +        try {
 +            conn = LibvirtConnection.getConnectionByVmName(routerName);
 +            Integer devNum = 0;
 +            final String pubVlan = pubIP.getBroadcastUri();
 +            final List<InterfaceDef> pluggedNics = getInterfaces(conn, routerName);
 +
 +            for (final InterfaceDef pluggedNic : pluggedNics) {
 +                final String pluggedVlanBr = pluggedNic.getBrName();
 +                final String pluggedVlanId = getBroadcastUriFromBridge(pluggedVlanBr);
 +                if (pubVlan.equalsIgnoreCase(Vlan.UNTAGGED) && pluggedVlanBr.equalsIgnoreCase(_publicBridgeName)) {
 +                    break;
 +                } else if (pluggedVlanBr.equalsIgnoreCase(_linkLocalBridgeName)) {
 +                    /*skip over, no physical bridge device exists*/
 +                } else if (pluggedVlanId == null) {
 +                    /*this should only be true in the case of link local bridge*/
 +                    return new ExecutionResult(false, "unable to find the vlan id for bridge " + pluggedVlanBr + " when attempting to set up" + pubVlan +
 +                            " on router " + routerName);
 +                } else if (pluggedVlanId.equals(pubVlan)) {
 +                    break;
 +                }
 +                devNum++;
 +            }
 +
 +            pubIP.setNicDevId(devNum);
 +
 +            return new ExecutionResult(true, "success");
 +        } catch (final LibvirtException e) {
 +            final String msg = "Ip SNAT failure due to " + e.toString();
 +            s_logger.error(msg, e);
 +            return new ExecutionResult(false, msg);
 +        }
 +    }
 +
 +    protected ExecutionResult prepareNetworkElementCommand(final IpAssocVpcCommand cmd) {
 +        Connect conn;
 +        final String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME);
 +
 +        try {
 +            conn = getLibvirtUtilitiesHelper().getConnectionByVmName(routerName);
 +            final IpAddressTO[] ips = cmd.getIpAddresses();
 +            Integer devNum = 0;
 +            final List<InterfaceDef> pluggedNics = getInterfaces(conn, routerName);
 +            final Map<String, Integer> macAddressToNicNum = new HashMap<>(pluggedNics.size());
 +
 +            for (final InterfaceDef pluggedNic : pluggedNics) {
 +                final String pluggedVlan = pluggedNic.getBrName();
 +                macAddressToNicNum.put(pluggedNic.getMacAddress(), devNum);
 +                devNum++;
 +            }
 +
 +            for (final IpAddressTO ip : ips) {
 +                ip.setNicDevId(macAddressToNicNum.get(ip.getVifMacAddress()));
 +            }
 +
 +            return new ExecutionResult(true, null);
 +        } catch (final LibvirtException e) {
 +            s_logger.error("Ip Assoc failure on applying one ip due to exception:  ", e);
 +            return new ExecutionResult(false, e.getMessage());
 +        }
 +    }
 +
 +    public ExecutionResult prepareNetworkElementCommand(final IpAssocCommand cmd) {
 +        final String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME);
 +        final String routerIp = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP);
 +        Connect conn;
 +        try {
 +            conn = LibvirtConnection.getConnectionByVmName(routerName);
 +            final List<InterfaceDef> nics = getInterfaces(conn, routerName);
 +            final Map<String, Integer> broadcastUriAllocatedToVM = new HashMap<String, Integer>();
 +            Integer nicPos = 0;
 +            for (final InterfaceDef nic : nics) {
 +                if (nic.getBrName().equalsIgnoreCase(_linkLocalBridgeName)) {
 +                    broadcastUriAllocatedToVM.put("LinkLocal", nicPos);
 +                } else {
 +                    if (nic.getBrName().equalsIgnoreCase(_publicBridgeName) || nic.getBrName().equalsIgnoreCase(_privBridgeName) ||
 +                            nic.getBrName().equalsIgnoreCase(_guestBridgeName)) {
 +                        broadcastUriAllocatedToVM.put(BroadcastDomainType.Vlan.toUri(Vlan.UNTAGGED).toString(), nicPos);
 +                    } else {
 +                        final String broadcastUri = getBroadcastUriFromBridge(nic.getBrName());
 +                        broadcastUriAllocatedToVM.put(broadcastUri, nicPos);
 +                    }
 +                }
 +                nicPos++;
 +            }
 +            final IpAddressTO[] ips = cmd.getIpAddresses();
 +            int nicNum = 0;
 +            for (final IpAddressTO ip : ips) {
 +                boolean newNic = false;
 +                if (!broadcastUriAllocatedToVM.containsKey(ip.getBroadcastUri())) {
 +                    /* plug a vif into router */
 +                    VifHotPlug(conn, routerName, ip.getBroadcastUri(), ip.getVifMacAddress());
 +                    broadcastUriAllocatedToVM.put(ip.getBroadcastUri(), nicPos++);
 +                    newNic = true;
 +                }
 +                nicNum = broadcastUriAllocatedToVM.get(ip.getBroadcastUri());
 +                networkUsage(routerIp, "addVif", "eth" + nicNum);
 +
 +                ip.setNicDevId(nicNum);
 +                ip.setNewNic(newNic);
 +            }
 +            return new ExecutionResult(true, null);
 +        } catch (final LibvirtException e) {
 +            s_logger.error("ipassoccmd failed", e);
 +            return new ExecutionResult(false, e.getMessage());
 +        } catch (final InternalErrorException e) {
 +            s_logger.error("ipassoccmd failed", e);
 +            return new ExecutionResult(false, e.getMessage());
 +        }
 +    }
 +
 +    protected ExecutionResult cleanupNetworkElementCommand(final IpAssocCommand cmd) {
 +
 +        final String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME);
 +        final String routerIp = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP);
 +        final String lastIp = cmd.getAccessDetail(NetworkElementCommand.NETWORK_PUB_LAST_IP);
 +        Connect conn;
 +
 +
 +        try{
 +            conn = LibvirtConnection.getConnectionByVmName(routerName);
 +            final List<InterfaceDef> nics = getInterfaces(conn, routerName);
 +            final Map<String, Integer> broadcastUriAllocatedToVM = new HashMap<String, Integer>();
 +
 +            Integer nicPos = 0;
 +            for (final InterfaceDef nic : nics) {
 +                if (nic.getBrName().equalsIgnoreCase(_linkLocalBridgeName)) {
 +                    broadcastUriAllocatedToVM.put("LinkLocal", nicPos);
 +                } else {
 +                    if (nic.getBrName().equalsIgnoreCase(_publicBridgeName) || nic.getBrName().equalsIgnoreCase(_privBridgeName) ||
 +                            nic.getBrName().equalsIgnoreCase(_guestBridgeName)) {
 +                        broadcastUriAllocatedToVM.put(BroadcastDomainType.Vlan.toUri(Vlan.UNTAGGED).toString(), nicPos);
 +                    } else {
 +                        final String broadcastUri = getBroadcastUriFromBridge(nic.getBrName());
 +                        broadcastUriAllocatedToVM.put(broadcastUri, nicPos);
 +                    }
 +                }
 +                nicPos++;
 +            }
 +
 +            final IpAddressTO[] ips = cmd.getIpAddresses();
 +            int nicNum = 0;
 +            for (final IpAddressTO ip : ips) {
 +
 +                if (!broadcastUriAllocatedToVM.containsKey(ip.getBroadcastUri())) {
 +                    /* plug a vif into router */
 +                    VifHotPlug(conn, routerName, ip.getBroadcastUri(), ip.getVifMacAddress());
 +                    broadcastUriAllocatedToVM.put(ip.getBroadcastUri(), nicPos++);
 +                }
 +                nicNum = broadcastUriAllocatedToVM.get(ip.getBroadcastUri());
 +
 +                if (org.apache.commons.lang.StringUtils.equalsIgnoreCase(lastIp, "true") && !ip.isAdd()) {
 +                    // in isolated network eth2 is the default public interface. We don't want to delete it.
 +                    if (nicNum != 2) {
 +                        vifHotUnPlug(conn, routerName, ip.getVifMacAddress());
 +                        networkUsage(routerIp, "deleteVif", "eth" + nicNum);
 +                    }
 +                }
 +            }
 +
 +        } catch (final LibvirtException e) {
 +            s_logger.error("ipassoccmd failed", e);
 +            return new ExecutionResult(false, e.getMessage());
 +        } catch (final InternalErrorException e) {
 +            s_logger.error("ipassoccmd failed", e);
 +            return new ExecutionResult(false, e.getMessage());
 +        }
 +
 +        return new ExecutionResult(true, null);
 +    }
 +
 +    protected PowerState convertToPowerState(final DomainState ps) {
 +        final PowerState state = s_powerStatesTable.get(ps);
 +        return state == null ? PowerState.PowerUnknown : state;
 +    }
 +
 +    public PowerState getVmState(final Connect conn, final String vmName) {
 +        int retry = 3;
 +        Domain vms = null;
 +        while (retry-- > 0) {
 +            try {
 +                vms = conn.domainLookupByName(vmName);
 +                final PowerState s = convertToPowerState(vms.getInfo().state);
 +                return s;
 +            } catch (final LibvirtException e) {
 +                s_logger.warn("Can't get vm state " + vmName + e.getMessage() + "retry:" + retry);
 +            } finally {
 +                try {
 +                    if (vms != null) {
 +                        vms.free();
 +                    }
 +                } catch (final LibvirtException l) {
 +                    s_logger.trace("Ignoring libvirt error.", l);
 +                }
 +            }
 +        }
 +        return PowerState.PowerOff;
 +    }
 +
 +    public String networkUsage(final String privateIpAddress, final String option, final String vif) {
 +        final Script getUsage = new Script(_routerProxyPath, s_logger);
 +        getUsage.add("netusage.sh");
 +        getUsage.add(privateIpAddress);
 +        if (option.equals("get")) {
 +            getUsage.add("-g");
 +        } else if (option.equals("create")) {
 +            getUsage.add("-c");
 +        } else if (option.equals("reset")) {
 +            getUsage.add("-r");
 +        } else if (option.equals("addVif")) {
 +            getUsage.add("-a", vif);
 +        } else if (option.equals("deleteVif")) {
 +            getUsage.add("-d", vif);
 +        }
 +
 +        final OutputInterpreter.OneLineParser usageParser = new OutputInterpreter.OneLineParser();
 +        final String result = getUsage.execute(usageParser);
 +        if (result != null) {
 +            s_logger.debug("Failed to execute networkUsage:" + result);
 +            return null;
 +        }
 +        return usageParser.getLine();
 +    }
 +
 +    public long[] getNetworkStats(final String privateIP) {
 +        final String result = networkUsage(privateIP, "get", null);
 +        final long[] stats = new long[2];
 +        if (result != null) {
 +            final String[] splitResult = result.split(":");
 +            int i = 0;
 +            while (i < splitResult.length - 1) {
 +                stats[0] += Long.parseLong(splitResult[i++]);
 +                stats[1] += Long.parseLong(splitResult[i++]);
 +            }
 +        }
 +        return stats;
 +    }
 +
 +    public String configureVPCNetworkUsage(final String privateIpAddress, final String publicIp, final String option, final String vpcCIDR) {
 +        final Script getUsage = new Script(_routerProxyPath, s_logger);
 +        getUsage.add("vpc_netusage.sh");
 +        getUsage.add(privateIpAddress);
 +        getUsage.add("-l", publicIp);
 +
 +        if (option.equals("get")) {
 +            getUsage.add("-g");
 +        } else if (option.equals("create")) {
 +            getUsage.add("-c");
 +            getUsage.add("-v", vpcCIDR);
 +        } else if (option.equals("reset")) {
 +            getUsage.add("-r");
 +        } else if (option.equals("vpn")) {
 +            getUsage.add("-n");
 +        } else if (option.equals("remove")) {
 +            getUsage.add("-d");
 +        }
 +
 +        final OutputInterpreter.OneLineParser usageParser = new OutputInterpreter.OneLineParser();
 +        final String result = getUsage.execute(usageParser);
 +        if (result != null) {
 +            s_logger.debug("Failed to execute VPCNetworkUsage:" + result);
 +            return null;
 +        }
 +        return usageParser.getLine();
 +    }
 +
 +    public long[] getVPCNetworkStats(final String privateIP, final String publicIp, final String option) {
 +        final String result = configureVPCNetworkUsage(privateIP, publicIp, option, null);
 +        final long[] stats = new long[2];
 +        if (result != null) {
 +            final String[] splitResult = result.split(":");
 +            int i = 0;
 +            while (i < splitResult.length - 1) {
 +                stats[0] += Long.parseLong(splitResult[i++]);
 +                stats[1] += Long.parseLong(splitResult[i++]);
 +            }
 +        }
 +        return stats;
 +    }
 +
 +    public void handleVmStartFailure(final Connect conn, final String vmName, final LibvirtVMDef vm) {
 +        if (vm != null && vm.getDevices() != null) {
 +            cleanupVMNetworks(conn, vm.getDevices().getInterfaces());
 +        }
 +    }
 +
 +    protected String getUuid(String uuid) {
 +        if (uuid == null) {
 +            uuid = UUID.randomUUID().toString();
 +        } else {
 +            try {
 +                final UUID uuid2 = UUID.fromString(uuid);
 +                final String uuid3 = uuid2.toString();
 +                if (!uuid3.equals(uuid)) {
 +                    uuid = UUID.randomUUID().toString();
 +                }
 +            } catch (final IllegalArgumentException e) {
 +                uuid = UUID.randomUUID().toString();
 +            }
 +        }
 +        return uuid;
 +    }
 +
 +    /**
 +     * Set quota and period tags on 'ctd' when CPU limit use is set
 +     */
 +    protected void setQuotaAndPeriod(VirtualMachineTO vmTO, CpuTuneDef ctd) {
 +        if (vmTO.getLimitCpuUse() && vmTO.getCpuQuotaPercentage() != null) {
 +            Double cpuQuotaPercentage = vmTO.getCpuQuotaPercentage();
 +            int period = CpuTuneDef.DEFAULT_PERIOD;
 +            int quota = (int) (period * cpuQuotaPercentage);
 +            if (quota < CpuTuneDef.MIN_QUOTA) {
 +                s_logger.info("Calculated quota (" + quota + ") below the minimum (" + CpuTuneDef.MIN_QUOTA + ") for VM domain " + vmTO.getUuid() + ", setting it to minimum " +
 +                        "and calculating period instead of using the default");
 +                quota = CpuTuneDef.MIN_QUOTA;
 +                period = (int) ((double) quota / cpuQuotaPercentage);
 +                if (period > CpuTuneDef.MAX_PERIOD) {
 +                    s_logger.info("Calculated period (" + period + ") exceeds the maximum (" + CpuTuneDef.MAX_PERIOD +
 +                            "), setting it to the maximum");
 +                    period = CpuTuneDef.MAX_PERIOD;
 +                }
 +            }
 +            ctd.setQuota(quota);
 +            ctd.setPeriod(period);
 +            s_logger.info("Setting quota=" + quota + ", period=" + period + " to VM domain " + vmTO.getUuid());
 +        }
 +    }
 +
 +    protected void enlightenWindowsVm(VirtualMachineTO vmTO, FeaturesDef features) {
 +        if (vmTO.getOs().contains("Windows PV")) {
 +            // If OS is Windows PV, then enable the features. Features supported on Windows 2008 and later
 +            LibvirtVMDef.HyperVEnlightenmentFeatureDef hyv = new LibvirtVMDef.HyperVEnlightenmentFeatureDef();
 +            hyv.setFeature("relaxed", true);
 +            hyv.setFeature("vapic", true);
 +            hyv.setFeature("spinlocks", true);
 +            hyv.setRetries(8096);
 +            features.addHyperVFeature(hyv);
 +            s_logger.info("Enabling KVM Enlightment Features to VM domain " + vmTO.getUuid());
 +        }
 +    }
 +
 +    public LibvirtVMDef createVMFromSpec(final VirtualMachineTO vmTO) {
 +        final LibvirtVMDef vm = new LibvirtVMDef();
 +        vm.setDomainName(vmTO.getName());
 +        String uuid = vmTO.getUuid();
 +        uuid = getUuid(uuid);
 +        vm.setDomUUID(uuid);
 +        vm.setDomDescription(vmTO.getOs());
 +        vm.setPlatformEmulator(vmTO.getPlatformEmulator());
 +
 +        Map<String, String> extraConfig = vmTO.getExtraConfig();
 +        if (dpdkSupport && (!extraConfig.containsKey(DPDK_NUMA) || !extraConfig.containsKey(DPDK_HUGE_PAGES))) {
 +            s_logger.info("DPDK is enabled but it needs extra configurations for CPU NUMA and Huge Pages for VM deployment");
 +        }
 +
 +        final GuestDef guest = new GuestDef();
 +
 +        if (HypervisorType.LXC == _hypervisorType && VirtualMachine.Type.User == vmTO.getType()) {
 +            // LXC domain is only valid for user VMs. Use KVM for system VMs.
 +            guest.setGuestType(GuestDef.GuestType.LXC);
 +            vm.setHvsType(HypervisorType.LXC.toString().toLowerCase());
 +        } else {
 +            guest.setGuestType(GuestDef.GuestType.KVM);
 +            vm.setHvsType(HypervisorType.KVM.toString().toLowerCase());
 +            vm.setLibvirtVersion(_hypervisorLibvirtVersion);
 +            vm.setQemuVersion(_hypervisorQemuVersion);
 +        }
 +        guest.setGuestArch(vmTO.getArch());
 +        guest.setMachineType("pc");
 +        guest.setUuid(uuid);
 +        guest.setBootOrder(GuestDef.BootOrder.CDROM);
 +        guest.setBootOrder(GuestDef.BootOrder.HARDISK);
 +
 +        vm.addComp(guest);
 +
 +        final GuestResourceDef grd = new GuestResourceDef();
 +
 +        if (vmTO.getMinRam() != vmTO.getMaxRam() && !_noMemBalloon) {
 +            grd.setMemBalloning(true);
 +            grd.setCurrentMem(vmTO.getMinRam() / 1024);
 +            grd.setMemorySize(vmTO.getMaxRam() / 1024);
 +        } else {
 +            grd.setMemorySize(vmTO.getMaxRam() / 1024);
 +        }
 +        final int vcpus = vmTO.getCpus();
 +        grd.setVcpuNum(vcpus);
 +        vm.addComp(grd);
 +
 +        if (!extraConfig.containsKey(DPDK_NUMA)) {
 +            final CpuModeDef cmd = new CpuModeDef();
 +            cmd.setMode(_guestCpuMode);
 +            cmd.setModel(_guestCpuModel);
 +            if (vmTO.getType() == VirtualMachine.Type.User) {
 +                cmd.setFeatures(_cpuFeatures);
 +            }
 +            // multi cores per socket, for larger core configs
 +            if (vcpus % 6 == 0) {
 +                final int sockets = vcpus / 6;
 +                cmd.setTopology(6, sockets);
 +            } else if (vcpus % 4 == 0) {
 +                final int sockets = vcpus / 4;
 +                cmd.setTopology(4, sockets);
 +            }
 +            vm.addComp(cmd);
 +        }
 +
 +        if (_hypervisorLibvirtVersion >= 9000) {
 +            final CpuTuneDef ctd = new CpuTuneDef();
 +            /**
 +             A 4.0.X/4.1.X management server doesn't send the correct JSON
 +             command for getMinSpeed, it only sends a 'speed' field.
 +
 +             So if getMinSpeed() returns null we fall back to getSpeed().
 +
 +             This way a >4.1 agent can work communicate a <=4.1 management server
 +
 +             This change is due to the overcommit feature in 4.2
 +             */
 +            if (vmTO.getMinSpeed() != null) {
 +                ctd.setShares(vmTO.getCpus() * vmTO.getMinSpeed());
 +            } else {
 +                ctd.setShares(vmTO.getCpus() * vmTO.getSpeed());
 +            }
 +
 +            setQuotaAndPeriod(vmTO, ctd);
 +
 +            vm.addComp(ctd);
 +        }
 +
 +        final FeaturesDef features = new FeaturesDef();
 +        features.addFeatures("pae");
 +        features.addFeatures("apic");
 +        features.addFeatures("acpi");
 +
 +        //KVM hyperv enlightenment features based on OS Type
 +        enlightenWindowsVm(vmTO, features);
 +
 +        vm.addComp(features);
 +
 +        final TermPolicy term = new TermPolicy();
 +        term.setCrashPolicy("destroy");
 +        term.setPowerOffPolicy("destroy");
 +        term.setRebootPolicy("restart");
 +        vm.addComp(term);
 +
 +        final ClockDef clock = new ClockDef();
 +        if (vmTO.getOs().startsWith("Windows")) {
 +            clock.setClockOffset(ClockDef.ClockOffset.LOCALTIME);
 +            clock.setTimer("hypervclock", null, null);
 +        } else if (vmTO.getType() != VirtualMachine.Type.User || isGuestPVEnabled(vmTO.getOs())) {
 +            if (_hypervisorLibvirtVersion >= 9 * 1000 + 10) {
 +                clock.setTimer("kvmclock", null, null, _noKvmClock);
 +            }
 +        }
 +
 +        vm.addComp(clock);
 +
 +        final DevicesDef devices = new DevicesDef();
 +        devices.setEmulatorPath(_hypervisorPath);
 +        devices.setGuestType(guest.getGuestType());
 +
 +        final SerialDef serial = new SerialDef("pty", null, (short)0);
 +        devices.addDevice(serial);
 +
-         /* Add a VirtIO channel for SystemVMs for communication and provisioning */
-         if (vmTO.getType() != VirtualMachine.Type.User) {
-             File virtIoChannel = Paths.get(_qemuSocketsPath.getPath(), vmTO.getName() + ".agent").toFile();
-             devices.addDevice(new ChannelDef(vmTO.getName() + ".vport", ChannelDef.ChannelType.UNIX, virtIoChannel));
-         }
- 
 +        if (_rngEnable) {
 +            final RngDef rngDevice = new RngDef(_rngPath, _rngBackendModel, _rngRateBytes, _rngRatePeriod);
 +            devices.addDevice(rngDevice);
 +        }
 +
 +        /* Add a VirtIO channel for the Qemu Guest Agent tools */
 +        File virtIoChannel = Paths.get(_qemuSocketsPath.getPath(), vmTO.getName() + "." + _qemuGuestAgentSocketName).toFile();
 +        devices.addDevice(new ChannelDef(_qemuGuestAgentSocketName, ChannelDef.ChannelType.UNIX, virtIoChannel));
 +
 +        devices.addDevice(new WatchDogDef(_watchDogAction, _watchDogModel));
 +
 +        final VideoDef videoCard = new VideoDef(_videoHw, _videoRam);
 +        devices.addDevice(videoCard);
 +
 +        final ConsoleDef console = new ConsoleDef("pty", null, null, (short)0);
 +        devices.addDevice(console);
 +
 +        //add the VNC port passwd here, get the passwd from the vmInstance.
 +        final String passwd = vmTO.getVncPassword();
 +        final GraphicDef grap = new GraphicDef("vnc", (short)0, true, vmTO.getVncAddr(), passwd, null);
 +        devices.addDevice(grap);
 +
 +        final InputDef input = new InputDef("tablet", "usb");
 +        devices.addDevice(input);
 +
 +
 +        DiskDef.DiskBus busT = getDiskModelFromVMDetail(vmTO);
 +
 +        if (busT == null) {
 +            busT = getGuestDiskModel(vmTO.getPlatformEmulator());
 +        }
 +
 +        // If we're using virtio scsi, then we need to add a virtual scsi controller
 +        if (busT == DiskDef.DiskBus.SCSI) {
 +            final SCSIDef sd = new SCSIDef((short)0, 0, 0, 9, 0, vcpus);
 +            devices.addDevice(sd);
 +        }
 +
 +        vm.addComp(devices);
 +
 +        addExtraConfigComponent(extraConfig, vm);
 +
 +        return vm;
 +    }
 +
 +    /**
 +     * Add extra configurations (if any) as a String component to the domain XML
 +     */
 +    protected void addExtraConfigComponent(Map<String, String> extraConfig, LibvirtVMDef vm) {
 +        if (MapUtils.isNotEmpty(extraConfig)) {
 +            StringBuilder extraConfigBuilder = new StringBuilder();
 +            for (String key : extraConfig.keySet()) {
 +                if (!key.startsWith(DPDK_INTERFACE_PREFIX)) {
 +                    extraConfigBuilder.append(extraConfig.get(key));
 +                }
 +            }
 +            String comp = extraConfigBuilder.toString();
 +            if (org.apache.commons.lang.StringUtils.isNotBlank(comp)) {
 +                vm.addComp(comp);
 +            }
 +        }
 +    }
 +
 +    public void createVifs(final VirtualMachineTO vmSpec, final LibvirtVMDef vm) throws InternalErrorException, LibvirtException {
 +        final NicTO[] nics = vmSpec.getNics();
 +        final Map <String, String> params = vmSpec.getDetails();
 +        String nicAdapter = "";
 +        if (params != null && params.get("nicAdapter") != null && !params.get("nicAdapter").isEmpty()) {
 +            nicAdapter = params.get("nicAdapter");
 +        }
 +        Map<String, String> extraConfig = vmSpec.getExtraConfig();
 +        for (int i = 0; i < nics.length; i++) {
 +            for (final NicTO nic : vmSpec.getNics()) {
 +                if (nic.getDeviceId() == i) {
 +                    createVif(vm, nic, nicAdapter, extraConfig);
 +                }
 +            }
 +        }
 +    }
 +
 +    public String getVolumePath(final Connect conn, final DiskTO volume) throws LibvirtException, URISyntaxException {
 +        final DataTO data = volume.getData();
 +        final DataStoreTO store = data.getDataStore();
 +
 +        if (volume.getType() == Volume.Type.ISO && data.getPath() != null && (store instanceof NfsTO ||
 +                store instanceof PrimaryDataStoreTO && data instanceof TemplateObjectTO && !((TemplateObjectTO) data).isDirectDownload())) {
 +            final String isoPath = store.getUrl().split("\\?")[0] + File.separator + data.getPath();
 +            final int index = isoPath.lastIndexOf("/");
 +            final String path = isoPath.substring(0, index);
 +            final String name = isoPath.substring(index + 1);
 +            final KVMStoragePool secondaryPool = _storagePoolMgr.getStoragePoolByURI(path);
 +            final KVMPhysicalDisk isoVol = secondaryPool.getPhysicalDisk(name);
 +            return isoVol.getPath();
 +        } else {
 +            return data.getPath();
 +        }
 +    }
 +
 +    public void createVbd(final Connect conn, final VirtualMachineTO vmSpec, final String vmName, final LibvirtVMDef vm) throws InternalErrorException, LibvirtException, URISyntaxException {
 +        final List<DiskTO> disks = Arrays.asList(vmSpec.getDisks());
 +        Collections.sort(disks, new Comparator<DiskTO>() {
 +            @Override
 +            public int compare(final DiskTO arg0, final DiskTO arg1) {
 +                return arg0.getDiskSeq() > arg1.getDiskSeq() ? 1 : -1;
 +            }
 +        });
 +
 +        for (final DiskTO volume : disks) {
 +            KVMPhysicalDisk physicalDisk = null;
 +            KVMStoragePool pool = null;
 +            final DataTO data = volume.getData();
 +            if (volume.getType() == Volume.Type.ISO && data.getPath() != null) {
 +                DataStoreTO dataStore = data.getDataStore();
 +                String dataStoreUrl = null;
 +                if (dataStore instanceof NfsTO) {
 +                    NfsTO nfsStore = (NfsTO)data.getDataStore();
 +                    dataStoreUrl = nfsStore.getUrl();
 +                } else if (dataStore instanceof PrimaryDataStoreTO && ((PrimaryDataStoreTO) dataStore).getPoolType().equals(StoragePoolType.NetworkFilesystem)) {
 +                    //In order to support directly downloaded ISOs
 +                    String psHost = ((PrimaryDataStoreTO) dataStore).getHost();
 +                    String psPath = ((PrimaryDataStoreTO) dataStore).getPath();
 +                    dataStoreUrl = "nfs://" + psHost + File.separator + psPath;
 +                }
 +                final String volPath = dataStoreUrl + File.separator + data.getPath();
 +                final int index = volPath.lastIndexOf("/");
 +                final String volDir = volPath.substring(0, index);
 +                final String volName = volPath.substring(index + 1);
 +                final KVMStoragePool secondaryStorage = _storagePoolMgr.getStoragePoolByURI(volDir);
 +                physicalDisk = secondaryStorage.getPhysicalDisk(volName);
 +            } else if (volume.getType() != Volume.Type.ISO) {
 +                final PrimaryDataStoreTO store = (PrimaryDataStoreTO)data.getDataStore();
 +                physicalDisk = _storagePoolMgr.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
 +                pool = physicalDisk.getPool();
 +            }
 +
 +            String volPath = null;
 +            if (physicalDisk != null) {
 +                volPath = physicalDisk.getPath();
 +            }
 +
 +            // check for disk activity, if detected we should exit because vm is running elsewhere
 +            if (_diskActivityCheckEnabled && physicalDisk != null && physicalDisk.getFormat() == PhysicalDiskFormat.QCOW2) {
 +                s_logger.debug("Checking physical disk file at path " + volPath + " for disk activity to ensure vm is not running elsewhere");
 +                try {
 +                    HypervisorUtils.checkVolumeFileForActivity(volPath, _diskActivityCheckTimeoutSeconds, _diskActivityInactiveThresholdMilliseconds, _diskActivityCheckFileSizeMin);
 +                } catch (final IOException ex) {
 +                    throw new CloudRuntimeException("Unable to check physical disk file for activity", ex);
 +                }
 +                s_logger.debug("Disk activity check cleared");
 +            }
 +
 +            // if params contains a rootDiskController key, use its value (this is what other HVs are doing)
 +            DiskDef.DiskBus diskBusType = getDiskModelFromVMDetail(vmSpec);
 +
 +            if (diskBusType == null) {
 +                diskBusType = getGuestDiskModel(vmSpec.getPlatformEmulator());
 +            }
 +
 +            // I'm not sure why previously certain DATADISKs were hard-coded VIRTIO and others not, however this
 +            // maintains existing functionality with the exception that SCSI will override VIRTIO.
 +            DiskDef.DiskBus diskBusTypeData = (diskBusType == DiskDef.DiskBus.SCSI) ? diskBusType : DiskDef.DiskBus.VIRTIO;
 +
 +            final DiskDef disk = new DiskDef();
 +            int devId = volume.getDiskSeq().intValue();
 +            if (volume.getType() == Volume.Type.ISO) {
 +                if (volPath == null) {
 +                    /* Add iso as placeholder */
 +                    disk.defISODisk(null, devId);
 +                } else {
 +                    disk.defISODisk(volPath, devId);
 +                }
 +            } else {
 +                if (diskBusType == DiskDef.DiskBus.SCSI ) {
 +                    disk.setQemuDriver(true);
 +                    disk.setDiscard(DiscardType.UNMAP);
 +                }
 +
 +                if (pool.getType() == StoragePoolType.RBD) {
 +                    /*
 +                            For RBD pools we use the secret mechanism in libvirt.
 +                            We store the secret under the UUID of the pool, that's why
 +                            we pass the pool's UUID as the authSecret
 +                     */
 +                    disk.defNetworkBasedDisk(physicalDisk.getPath().replace("rbd:", ""), pool.getSourceHost(), pool.getSourcePort(), pool.getAuthUserName(),
 +                            pool.getUuid(), devId, diskBusType, DiskProtocol.RBD, DiskDef.DiskFmtType.RAW);
 +                } else if (pool.getType() == StoragePoolType.Gluster) {
 +                    final String mountpoint = pool.getLocalPath();
 +                    final String path = physicalDisk.getPath();
 +                    final String glusterVolume = pool.getSourceDir().replace("/", "");
 +                    disk.defNetworkBasedDisk(glusterVolume + path.replace(mountpoint, ""), pool.getSourceHost(), pool.getSourcePort(), null,
 +                            null, devId, diskBusType, DiskProtocol.GLUSTER, DiskDef.DiskFmtType.QCOW2);
 +                } else if (pool.getType() == StoragePoolType.CLVM || physicalDisk.getFormat() == PhysicalDiskFormat.RAW) {
 +                    disk.defBlockBasedDisk(physicalDisk.getPath(), devId, diskBusType);
 +                } else {
 +                    if (volume.getType() == Volume.Type.DATADISK) {
 +                        disk.defFileBasedDisk(physicalDisk.getPath(), devId, diskBusTypeData, DiskDef.DiskFmtType.QCOW2);
 +                    } else {
 +                        disk.defFileBasedDisk(physicalDisk.getPath(), devId, diskBusType, DiskDef.DiskFmtType.QCOW2);
 +                    }
 +
 +                }
 +
 +            }
 +
 +            if (data instanceof VolumeObjectTO) {
 +                final VolumeObjectTO volumeObjectTO = (VolumeObjectTO)data;
 +                disk.setSerial(diskUuidToSerial(volumeObjectTO.getUuid()));
 +                setBurstProperties(volumeObjectTO, disk);
 +
 +                if (volumeObjectTO.getCacheMode() != null) {
 +                    disk.setCacheMode(DiskDef.DiskCacheMode.valueOf(volumeObjectTO.getCacheMode().toString().toUpperCase()));
 +                }
 +            }
 +            if (vm.getDevices() == null) {
 +                s_logger.error("There is no devices for" + vm);
 +                throw new RuntimeException("There is no devices for" + vm);
 +            }
 +            vm.getDevices().addDevice(disk);
 +        }
 +
 +        if (vmSpec.getType() != VirtualMachine.Type.User) {
 +            if (_sysvmISOPath != null) {
 +                final DiskDef iso = new DiskDef();
 +                iso.defISODisk(_sysvmISOPath);
 +                vm.getDevices().addDevice(iso);
 +            }
 +        }
 +
 +        // For LXC, find and add the root filesystem, rbd data disks
 +        if (HypervisorType.LXC.toString().toLowerCase().equals(vm.getHvsType())) {
 +            for (final DiskTO volume : disks) {
 +                final DataTO data = volume.getData();
 +                final PrimaryDataStoreTO store = (PrimaryDataStoreTO)data.getDataStore();
 +                if (volume.getType() == Volume.Type.ROOT) {
 +                    final KVMPhysicalDisk physicalDisk = _storagePoolMgr.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
 +                    final FilesystemDef rootFs = new FilesystemDef(physicalDisk.getPath(), "/");
 +                    vm.getDevices().addDevice(rootFs);
 +                } else if (volume.getType() == Volume.Type.DATADISK) {
 +                    final KVMPhysicalDisk physicalDisk = _storagePoolMgr.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath());
 +                    final KVMStoragePool pool = physicalDisk.getPool();
 +                    if(StoragePoolType.RBD.equals(pool.getType())) {
 +                        final int devId = volume.getDiskSeq().intValue();
 +                        final String device = mapRbdDevice(physicalDisk);
 +                        if (device != null) {
 +                            s_logger.debug("RBD device on host is: " + device);
 +                            final DiskDef diskdef = new DiskDef();
 +                            diskdef.defBlockBasedDisk(device, devId, DiskDef.DiskBus.VIRTIO);
 +                            diskdef.setQemuDriver(false);
 +                            vm.getDevices().addDevice(diskdef);
 +                        } else {
 +                            throw new InternalErrorException("Error while mapping RBD device on host");
 +                        }
 +                    }
 +                }
 +            }
 +        }
 +
 +    }
 +
 +    private void setBurstProperties(final VolumeObjectTO volumeObjectTO, final DiskDef disk ) {
 +        if (volumeObjectTO.getBytesReadRate() != null && volumeObjectTO.getBytesReadRate() > 0) {
 +            disk.setBytesReadRate(volumeObjectTO.getBytesReadRate());
 +        }
 +        if (volumeObjectTO.getBytesReadRateMax() != null && volumeObjectTO.getBytesReadRateMax() > 0) {
 +            disk.setBytesReadRateMax(volumeObjectTO.getBytesReadRateMax());
 +        }
 +        if (volumeObjectTO.getBytesReadRateMaxLength() != null && volumeObjectTO.getBytesReadRateMaxLength() > 0) {
 +            disk.setBytesReadRateMaxLength(volumeObjectTO.getBytesReadRateMaxLength());
 +        }
 +        if (volumeObjectTO.getBytesWriteRate() != null && volumeObjectTO.getBytesWriteRate() > 0) {
 +            disk.setBytesWriteRate(volumeObjectTO.getBytesWriteRate());
 +        }
 +        if (volumeObjectTO.getBytesWriteRateMax() != null && volumeObjectTO.getBytesWriteRateMax() > 0) {
 +            disk.setBytesWriteRateMax(volumeObjectTO.getBytesWriteRateMax());
 +        }
 +        if (volumeObjectTO.getBytesWriteRateMaxLength() != null && volumeObjectTO.getBytesWriteRateMaxLength() > 0) {
 +            disk.setBytesWriteRateMaxLength(volumeObjectTO.getBytesWriteRateMaxLength());
 +        }
 +        if (volumeObjectTO.getIopsReadRate() != null && volumeObjectTO.getIopsReadRate() > 0) {
 +            disk.setIopsReadRate(volumeObjectTO.getIopsReadRate());
 +        }
 +        if (volumeObjectTO.getIopsReadRateMax() != null && volumeObjectTO.getIopsReadRateMax() > 0) {
 +            disk.setIopsReadRateMax(volumeObjectTO.getIopsReadRateMax());
 +        }
 +        if (volumeObjectTO.getIopsReadRateMaxLength() != null && volumeObjectTO.getIopsReadRateMaxLength() > 0) {
 +            disk.setIopsReadRateMaxLength(volumeObjectTO.getIopsReadRateMaxLength());
 +        }
 +        if (volumeObjectTO.getIopsWriteRate() != null && volumeObjectTO.getIopsWriteRate() > 0) {
 +            disk.setIopsWriteRate(volumeObjectTO.getIopsWriteRate());
 +        }
 +        if (volumeObjectTO.getIopsWriteRateMax() != null && volumeObjectTO.getIopsWriteRateMax() > 0) {
 +            disk.setIopsWriteRateMax(volumeObjectTO.getIopsWriteRateMax());
 +        }
 +        if (volumeObjectTO.getIopsWriteRateMaxLength() != null && volumeObjectTO.getIopsWriteRateMaxLength() > 0) {
 +            disk.setIopsWriteRateMaxLength(volumeObjectTO.getIopsWriteRateMaxLength());
 +        }
 +    }
 +
 +    private void createVif(final LibvirtVMDef vm, final NicTO nic, final String nicAdapter, Map<String, String> extraConfig) throws InternalErrorException, LibvirtException {
 +
 +        if (nic.getType().equals(TrafficType.Guest) && nic.getBroadcastType().equals(BroadcastDomainType.Vsp)) {
 +            String vrIp = nic.getBroadcastUri().getPath().substring(1);
 +            vm.getMetaData().getMetadataNode(LibvirtVMDef.NuageExtensionDef.class).addNuageExtension(nic.getMac(), vrIp);
 +
 +            if (s_logger.isDebugEnabled()) {
 +                s_logger.debug("NIC with MAC " + nic.getMac() + " and BroadcastDomainType " + nic.getBroadcastType() + " in network(" + nic.getGateway() + "/" + nic.getNetmask()
 +                        + ") is " + nic.getType() + " traffic type. So, vsp-vr-ip " + vrIp + " is set in the metadata");
 +            }
 +        }
 +        if (vm.getDevices() == null) {
 +            s_logger.error("LibvirtVMDef object get devices with null result");
 +            throw new InternalErrorException("LibvirtVMDef object get devices with null result");
 +        }
 +        vm.getDevices().addDevice(getVifDriver(nic.getType(), nic.getName()).plug(nic, vm.getPlatformEmulator(), nicAdapter, extraConfig));
 +    }
 +
 +    public boolean cleanupDisk(Map<String, String> volumeToDisconnect) {
 +        return _storagePoolMgr.disconnectPhysicalDisk(volumeToDisconnect);
 +    }
 +
 +    public boolean cleanupDisk(final DiskDef disk) {
 +        final String path = disk.getDiskPath();
 +
 +        if (path == null) {
 +            s_logger.debug("Unable to clean up disk with null path (perhaps empty cdrom drive):" + disk);
 +            return false;
 +        }
 +
 +        if (path.endsWith("systemvm.iso")) {
 +            // don't need to clean up system vm ISO as it's stored in local
 +            return true;
 +        }
 +
 +        return _storagePoolMgr.disconnectPhysicalDiskByPath(path);
 +    }
 +
 +    protected KVMStoragePoolManager getPoolManager() {
 +        return _storagePoolMgr;
 +    }
 +
 +    public synchronized String attachOrDetachISO(final Connect conn, final String vmName, String isoPath, final boolean isAttach, final Integer diskSeq) throws LibvirtException, URISyntaxException,
 +            InternalErrorException {
 +        final DiskDef iso = new DiskDef();
 +        if (isoPath != null && isAttach) {
 +            final int index = isoPath.lastIndexOf("/");
 +            final String path = isoPath.substring(0, index);
 +            final String name = isoPath.substring(index + 1);
 +            final KVMStoragePool secondaryPool = _storagePoolMgr.getStoragePoolByURI(path);
 +            final KVMPhysicalDisk isoVol = secondaryPool.getPhysicalDisk(name);
 +            isoPath = isoVol.getPath();
 +
 +            iso.defISODisk(isoPath, diskSeq);
 +        } else {
 +            iso.defISODisk(null, diskSeq);
 +        }
 +
 +        final String result = attachOrDetachDevice(conn, true, vmName, iso.toString());
 +        if (result == null && !isAttach) {
 +            final List<DiskDef> disks = getDisks(conn, vmName);
 +            for (final DiskDef disk : disks) {
 +                if (disk.getDeviceType() == DiskDef.DeviceType.CDROM
 +                        && (diskSeq == null || disk.getDiskLabel() == iso.getDiskLabel())) {
 +                    cleanupDisk(disk);
 +                }
 +            }
 +
 +        }
 +        return result;
 +    }
 +
 +    public synchronized String attachOrDetachDisk(final Connect conn,
 +                                                  final boolean attach, final String vmName, final KVMPhysicalDisk attachingDisk,
 +                                                  final int devId, final Long bytesReadRate, final Long bytesReadRateMax, final Long bytesReadRateMaxLength, final Long bytesWriteRate, final Long bytesWriteRateMax, final Long bytesWriteRateMaxLength, final Long iopsReadRate, final Long iopsReadRateMax, final Long iopsReadRateMaxLength, final Long iopsWriteRate, final Long iopsWriteRateMax, final Long iopsWriteRateMaxLength, final String cacheMode) throws LibvirtException, InternalErrorEx [...]
 +        List<DiskDef> disks = null;
 +        Domain dm = null;
 +        DiskDef diskdef = null;
 +        final KVMStoragePool attachingPool = attachingDisk.getPool();
 +        try {
 +            dm = conn.domainLookupByName(vmName);
 +            final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
 +            final String domXml = dm.getXMLDesc(0);
 +            parser.parseDomainXML(domXml);
 +            disks = parser.getDisks();
 +
 +            if (!attach) {
 +                for (final DiskDef disk : disks) {
 +                    final String file = disk.getDiskPath();
 +                    if (file != null && file.equalsIgnoreCase(attachingDisk.getPath())) {
 +                        diskdef = disk;
 +                        break;
 +                    }
 +                }
 +                if (diskdef == null) {
 +                    throw new InternalErrorException("disk: " + attachingDisk.getPath() + " is not attached before");
 +                }
 +            } else {
 +                DiskDef.DiskBus busT = DiskDef.DiskBus.VIRTIO;
 +                for (final DiskDef disk : disks) {
 +                    if (disk.getDeviceType() == DeviceType.DISK) {
 +                        if (disk.getBusType() == DiskDef.DiskBus.SCSI) {
 +                            busT = DiskDef.DiskBus.SCSI;
 +                        }
 +                        break;
 +                    }
 +                }
 +
 +                diskdef = new DiskDef();
 +                if (busT == DiskDef.DiskBus.SCSI) {
 +                    diskdef.setQemuDriver(true);
 +                    diskdef.setDiscard(DiscardType.UNMAP);
 +                }
 +                if (attachingPool.getType() == StoragePoolType.RBD) {
 +                    diskdef.defNetworkBasedDisk(attachingDisk.getPath(), attachingPool.getSourceHost(), attachingPool.getSourcePort(), attachingPool.getAuthUserName(),
 +                            attachingPool.getUuid(), devId, busT, DiskProtocol.RBD, DiskDef.DiskFmtType.RAW);
 +                } else if (attachingPool.getType() == StoragePoolType.Gluster) {
 +                    diskdef.defNetworkBasedDisk(attachingDisk.getPath(), attachingPool.getSourceHost(), attachingPool.getSourcePort(), null,
 +                            null, devId, busT, DiskProtocol.GLUSTER, DiskDef.DiskFmtType.QCOW2);
 +                } else if (attachingDisk.getFormat() == PhysicalDiskFormat.QCOW2) {
 +                    diskdef.defFileBasedDisk(attachingDisk.getPath(), devId, busT, DiskDef.DiskFmtType.QCOW2);
 +                } else if (attachingDisk.getFormat() == PhysicalDiskFormat.RAW) {
 +                    diskdef.defBlockBasedDisk(attachingDisk.getPath(), devId, busT);
 +                }
 +                if (bytesReadRate != null && bytesReadRate > 0) {
 +                    diskdef.setBytesReadRate(bytesReadRate);
 +                }
 +                if (bytesReadRateMax != null && bytesReadRateMax > 0) {
 +                    diskdef.setBytesReadRateMax(bytesReadRateMax);
 +                }
 +                if (bytesReadRateMaxLength != null && bytesReadRateMaxLength > 0) {
 +                    diskdef.setBytesReadRateMaxLength(bytesReadRateMaxLength);
 +                }
 +                if (bytesWriteRate != null && bytesWriteRate > 0) {
 +                    diskdef.setBytesWriteRate(bytesWriteRate);
 +                }
 +                if (bytesWriteRateMax != null && bytesWriteRateMax > 0) {
 +                    diskdef.setBytesWriteRateMax(bytesWriteRateMax);
 +                }
 +                if (bytesWriteRateMaxLength != null && bytesWriteRateMaxLength > 0) {
 +                    diskdef.setBytesWriteRateMaxLength(bytesWriteRateMaxLength);
 +                }
 +                if (iopsReadRate != null && iopsReadRate > 0) {
 +                    diskdef.setIopsReadRate(iopsReadRate);
 +                }
 +                if (iopsReadRateMax != null && iopsReadRateMax > 0) {
 +                    diskdef.setIopsReadRateMax(iopsReadRateMax);
 +                }
 +                if (iopsReadRateMaxLength != null && iopsReadRateMaxLength > 0) {
 +                    diskdef.setIopsReadRateMaxLength(iopsReadRateMaxLength);
 +                }
 +                if (iopsWriteRate != null && iopsWriteRate > 0) {
 +                    diskdef.setIopsWriteRate(iopsWriteRate);
 +                }
 +                if (iopsWriteRateMax != null && iopsWriteRateMax > 0) {
 +                    diskdef.setIopsWriteRateMax(iopsWriteRateMax);
 +                }
 +
 +                if (cacheMode != null) {
 +                    diskdef.setCacheMode(DiskDef.DiskCacheMode.valueOf(cacheMode.toUpperCase()));
 +                }
 +            }
 +
 +            final String xml = diskdef.toString();
 +            return attachOrDetachDevice(conn, attach, vmName, xml);
 +        } finally {
 +            if (dm != null) {
 +                dm.free();
 +            }
 +        }
 +    }
 +
 +    protected synchronized String attachOrDetachDevice(final Connect conn, final boolean attach, final String vmName, final String xml) throws LibvirtException, InternalErrorException {
 +        Domain dm = null;
 +        try {
 +            dm = conn.domainLookupByName(vmName);
 +            if (attach) {
 +                s_logger.debug("Attaching device: " + xml);
 +                dm.attachDevice(xml);
 +            } else {
 +                s_logger.debug("Detaching device: " + xml);
 +                dm.detachDevice(xml);
 +            }
 +        } catch (final LibvirtException e) {
 +            if (attach) {
 +                s_logger.warn("Failed to attach device to " + vmName + ": " + e.getMessage());
 +            } else {
 +                s_logger.warn("Failed to detach device from " + vmName + ": " + e.getMessage());
 +            }
 +            throw e;
 +        } finally {
 +            if (dm != null) {
 +                try {
 +                    dm.free();
 +                } catch (final LibvirtException l) {
 +                    s_logger.trace("Ignoring libvirt error.", l);
 +                }
 +            }
 +        }
 +
 +        return null;
 +    }
 +
 +    @Override
 +    public PingCommand getCurrentStatus(final long id) {
 +
 +        if (!_canBridgeFirewall) {
 +            return new PingRoutingCommand(com.cloud.host.Host.Type.Routing, id, this.getHostVmStateReport());
 +        } else {
 +            final HashMap<String, Pair<Long, Long>> nwGrpStates = syncNetworkGroups(id);
 +            return new PingRoutingWithNwGroupsCommand(getType(), id, this.getHostVmStateReport(), nwGrpStates);
 +        }
 +    }
 +
 +    @Override
 +    public Type getType() {
 +        return Type.Routing;
 +    }
 +
 +    private Map<String, String> getVersionStrings() {
 +        final Script command = new Script(_versionstringpath, _timeout, s_logger);
 +        final KeyValueInterpreter kvi = new KeyValueInterpreter();
 +        final String result = command.execute(kvi);
 +        if (result == null) {
 +            return kvi.getKeyValues();
 +        } else {
 +            return new HashMap<String, String>(1);
 +        }
 +    }
 +
 +    @Override
 +    public StartupCommand[] initialize() {
 +
 +        final KVMHostInfo info = new KVMHostInfo(_dom0MinMem, _dom0OvercommitMem);
 +
 +        final String capabilities = String.join(",", info.getCapabilities());
 +
 +        final StartupRoutingCommand cmd =
 +                new StartupRoutingCommand(info.getCpus(), info.getCpuSpeed(), info.getTotalMemory(), info.getReservedMemory(), capabilities, _hypervisorType,
 +                        RouterPrivateIpStrategy.HostLocal);
 +        cmd.setCpuSockets(info.getCpuSockets());
 +        fillNetworkInformation(cmd);
 +        _privateIp = cmd.getPrivateIpAddress();
 +        cmd.getHostDetails().putAll(getVersionStrings());
 +        cmd.getHostDetails().put(KeyStoreUtils.SECURED, String.valueOf(isHostSecured()).toLowerCase());
 +        cmd.setPool(_pool);
 +        cmd.setCluster(_clusterId);
 +        cmd.setGatewayIpAddress(_localGateway);
 +        cmd.setIqn(getIqn());
 +
 +        if (cmd.getHostDetails().containsKey("Host.OS")) {
 +            _hostDistro = cmd.getHostDetails().get("Host.OS");
 +        }
 +
 +        StartupStorageCommand sscmd = null;
 +        try {
 +
 +            final KVMStoragePool localStoragePool = _storagePoolMgr.createStoragePool(_localStorageUUID, "localhost", -1, _localStoragePath, "", StoragePoolType.Filesystem);
 +            final com.cloud.agent.api.StoragePoolInfo pi =
 +                    new com.cloud.agent.api.StoragePoolInfo(localStoragePool.getUuid(), cmd.getPrivateIpAddress(), _localStoragePath, _localStoragePath,
 +                            StoragePoolType.Filesystem, localStoragePool.getCapacity(), localStoragePool.getAvailable());
 +
 +            sscmd = new StartupStorageCommand();
 +            sscmd.setPoolInfo(pi);
 +            sscmd.setGuid(pi.getUuid());
 +            sscmd.setDataCenter(_dcId);
 +            sscmd.setResourceType(Storage.StorageResourceType.STORAGE_POOL);
 +        } catch (final CloudRuntimeException e) {
 +            s_logger.debug("Unable to initialize local storage pool: " + e);
 +        }
 +
 +        if (sscmd != null) {
 +            return new StartupCommand[] {cmd, sscmd};
 +        } else {
 +            return new StartupCommand[] {cmd};
 +        }
 +    }
 +
 +    public String diskUuidToSerial(String uuid) {
 +        String uuidWithoutHyphen = uuid.replace("-","");
 +        return uuidWithoutHyphen.substring(0, Math.min(uuidWithoutHyphen.length(), 20));
 +    }
 +
 +    private String getIqn() {
 +        try {
 +            final String textToFind = "InitiatorName=";
 +
 +            final Script iScsiAdmCmd = new Script(true, "grep", 0, s_logger);
 +
 +            iScsiAdmCmd.add(textToFind);
 +            iScsiAdmCmd.add("/etc/iscsi/initiatorname.iscsi");
 +
 +            final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
 +
 +            final String result = iScsiAdmCmd.execute(parser);
 +
 +            if (result != null) {
 +                return null;
 +            }
 +
 +            final String textFound = parser.getLine().trim();
 +
 +            return textFound.substring(textToFind.length());
 +        }
 +        catch (final Exception ex) {
 +            return null;
 +        }
 +    }
 +
 +    protected List<String> getAllVmNames(final Connect conn) {
 +        final ArrayList<String> la = new ArrayList<String>();
 +        try {
 +            final String names[] = conn.listDefinedDomains();
 +            for (int i = 0; i < names.length; i++) {
 +                la.add(names[i]);
 +            }
 +        } catch (final LibvirtException e) {
 +            s_logger.warn("Failed to list Defined domains", e);
 +        }
 +
 +        int[] ids = null;
 +        try {
 +            ids = conn.listDomains();
 +        } catch (final LibvirtException e) {
 +            s_logger.warn("Failed to list domains", e);
 +            return la;
 +        }
 +
 +        Domain dm = null;
 +        for (int i = 0; i < ids.length; i++) {
 +            try {
 +                dm = conn.domainLookupByID(ids[i]);
 +                la.add(dm.getName());
 +            } catch (final LibvirtException e) {
 +                s_logger.warn("Unable to get vms", e);
 +            } finally {
 +                try {
 +                    if (dm != null) {
 +                        dm.free();
 +                    }
 +                } catch (final LibvirtException e) {
 +                    s_logger.trace("Ignoring libvirt error.", e);
 +                }
 +            }
 +        }
 +
 +        return la;
 +    }
 +
 +    private HashMap<String, HostVmStateReportEntry> getHostVmStateReport() {
 +        final HashMap<String, HostVmStateReportEntry> vmStates = new HashMap<String, HostVmStateReportEntry>();
 +        Connect conn = null;
 +
 +        if (_hypervisorType == HypervisorType.LXC) {
 +            try {
 +                conn = LibvirtConnection.getConnectionByType(HypervisorType.LXC.toString());
 +                vmStates.putAll(getHostVmStateReport(conn));
 +                conn = LibvirtConnection.getConnectionByType(HypervisorType.KVM.toString());
 +                vmStates.putAll(getHostVmStateReport(conn));
 +            } catch (final LibvirtException e) {
 +                s_logger.debug("Failed to get connection: " + e.getMessage());
 +            }
 +        }
 +
 +        if (_hypervisorType == HypervisorType.KVM) {
 +            try {
 +                conn = LibvirtConnection.getConnectionByType(HypervisorType.KVM.toString());
 +                vmStates.putAll(getHostVmStateReport(conn));
 +            } catch (final LibvirtException e) {
 +                s_logger.debug("Failed to get connection: " + e.getMessage());
 +            }
 +        }
 +
 +        return vmStates;
 +    }
 +
 +    private HashMap<String, HostVmStateReportEntry> getHostVmStateReport(final Connect conn) {
 +        final HashMap<String, HostVmStateReportEntry> vmStates = new HashMap<String, HostVmStateReportEntry>();
 +
 +        String[] vms = null;
 +        int[] ids = null;
 +
 +        try {
 +            ids = conn.listDomains();
 +        } catch (final LibvirtException e) {
 +            s_logger.warn("Unable to listDomains", e);
 +            return null;
 +        }
 +        try {
 +            vms = conn.listDefinedDomains();
 +        } catch (final LibvirtException e) {
 +            s_logger.warn("Unable to listDomains", e);
 +            return null;
 +        }
 +
 +        Domain dm = null;
 +        for (int i = 0; i < ids.length; i++) {
 +            try {
 +                dm = conn.domainLookupByID(ids[i]);
 +
 +                final DomainState ps = dm.getInfo().state;
 +
 +                final PowerState state = convertToPowerState(ps);
 +
 +                s_logger.trace("VM " + dm.getName() + ": powerstate = " + ps + "; vm state=" + state.toString());
 +                final String vmName = dm.getName();
 +
 +                // TODO : for XS/KVM (host-based resource), we require to remove
 +                // VM completely from host, for some reason, KVM seems to still keep
 +                // Stopped VM around, to work-around that, reporting only powered-on VM
 +                //
 +                if (state == PowerState.PowerOn) {
 +                    vmStates.put(vmName, new HostVmStateReportEntry(state, conn.getHostName()));
 +                }
 +            } catch (final LibvirtException e) {
 +                s_logger.warn("Unable to get vms", e);
 +            } finally {
 +                try {
 +                    if (dm != null) {
 +                        dm.free();
 +                    }
 +                } catch (final LibvirtException e) {
 +                    s_logger.trace("Ignoring libvirt error.", e);
 +                }
 +            }
 +        }
 +
 +        for (int i = 0; i < vms.length; i++) {
 +            try {
 +
 +                dm = conn.domainLookupByName(vms[i]);
 +
 +                final DomainState ps = dm.getInfo().state;
 +                final PowerState state = convertToPowerState(ps);
 +                final String vmName = dm.getName();
 +                s_logger.trace("VM " + vmName + ": powerstate = " + ps + "; vm state=" + state.toString());
 +
 +                // TODO : for XS/KVM (host-based resource), we require to remove
 +                // VM completely from host, for some reason, KVM seems to still keep
 +                // Stopped VM around, to work-around that, reporting only powered-on VM
 +                //
 +                if (state == PowerState.PowerOn) {
 +                    vmStates.put(vmName, new HostVmStateReportEntry(state, conn.getHostName()));
 +                }
 +            } catch (final LibvirtException e) {
 +                s_logger.warn("Unable to get vms", e);
 +            } finally {
 +                try {
 +                    if (dm != null) {
 +                        dm.free();
 +                    }
 +                } catch (final LibvirtException e) {
 +                    s_logger.trace("Ignoring libvirt error.", e);
 +                }
 +            }
 +        }
 +
 +        return vmStates;
 +    }
 +
 +    public String rebootVM(final Connect conn, final String vmName) {
 +        Domain dm = null;
 +        String msg = null;
 +        try {
 +            dm = conn.domainLookupByName(vmName);
 +            // Get XML Dump including the secure information such as VNC password
 +            // By passing 1, or VIR_DOMAIN_XML_SECURE flag
 +            // https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainXMLFlags
 +            String vmDef = dm.getXMLDesc(1);
 +            final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
 +            parser.parseDomainXML(vmDef);
 +            for (final InterfaceDef nic : parser.getInterfaces()) {
 +                if (nic.getNetType() == GuestNetType.BRIDGE && nic.getBrName().startsWith("cloudVirBr")) {
 +                    try {
 +                        final int vnetId = Integer.parseInt(nic.getBrName().replaceFirst("cloudVirBr", ""));
 +                        final String pifName = getPif(_guestBridgeName);
 +                        final String newBrName = "br" + pifName + "-" + vnetId;
 +                        vmDef = vmDef.replaceAll("'" + nic.getBrName() + "'", "'" + newBrName + "'");
 +                        s_logger.debug("VM bridge name is changed from " + nic.getBrName() + " to " + newBrName);
 +                    } catch (final NumberFormatException e) {
 +                        continue;
 +                    }
 +                }
 +            }
 +            s_logger.debug(vmDef);
 +            msg = stopVM(conn, vmName, false);
 +            msg = startVM(conn, vmName, vmDef);
 +            return null;
 +        } catch (final LibvirtException e) {
 +            s_logger.warn("Failed to create vm", e);
 +            msg = e.getMessage();
 +        } catch (final InternalErrorException e) {
 +            s_logger.warn("Failed to create vm", e);
 +            msg = e.getMessage();
 +        } finally {
 +            try {
 +                if (dm != null) {
 +                    dm.free();
 +                }
 +            } catch (final LibvirtException e) {
 +                s_logger.trace("Ignoring libvirt error.", e);
 +            }
 +        }
 +
 +        return msg;
 +    }
 +
 +    public String stopVM(final Connect conn, final String vmName, final boolean forceStop) {
 +        DomainState state = null;
 +        Domain dm = null;
 +
 +        // delete the metadata of vm snapshots before stopping
 +        try {
 +            dm = conn.domainLookupByName(vmName);
 +            cleanVMSnapshotMetadata(dm);
 +        } catch (LibvirtException e) {
 +            s_logger.debug("Failed to get vm :" + e.getMessage());
 +        } finally {
 +            try {
 +                if (dm != null) {
 +                    dm.free();
 +                }
 +            } catch (LibvirtException l) {
 +                s_logger.trace("Ignoring libvirt error.", l);
 +            }
 +        }
 +
 +        s_logger.debug("Try to stop the vm at first");
 +        if (forceStop) {
 +            return stopVMInternal(conn, vmName, true);
 +        }
 +        String ret = stopVMInternal(conn, vmName, false);
 +        if (ret == Script.ERR_TIMEOUT) {
 +            ret = stopVMInternal(conn, vmName, true);
 +        } else if (ret != null) {
 +            /*
 +             * There is a race condition between libvirt and qemu: libvirt
 +             * listens on qemu's monitor fd. If qemu is shutdown, while libvirt
 +             * is reading on the fd, then libvirt will report an error.
 +             */
 +            /* Retry 3 times, to make sure we can get the vm's status */
 +            for (int i = 0; i < 3; i++) {
 +                try {
 +                    dm = conn.domainLookupByName(vmName);
 +                    state = dm.getInfo().state;
 +                    break;
 +                } catch (final LibvirtException e) {
 +                    s_logger.debug("Failed to get vm status:" + e.getMessage());
 +                } finally {
 +                    try {
 +                        if (dm != null) {
 +                            dm.free();
 +                        }
 +                    } catch (final LibvirtException l) {
 +                        s_logger.trace("Ignoring libvirt error.", l);
 +                    }
 +                }
 +            }
 +
 +            if (state == null) {
 +                s_logger.debug("Can't get vm's status, assume it's dead already");
 +                return null;
 +            }
 +
 +            if (state != DomainState.VIR_DOMAIN_SHUTOFF) {
 +                s_logger.debug("Try to destroy the vm");
 +                ret = stopVMInternal(conn, vmName, true);
 +                if (ret != null) {
 +                    return ret;
 +                }
 +            }
 +        }
 +
 +        return null;
 +    }
 +
 +    protected String stopVMInternal(final Connect conn, final String vmName, final boolean force) {
 +        Domain dm = null;
 +        try {
 +            dm = conn.domainLookupByName(vmName);
 +            final int persist = dm.isPersistent();
 +            if (force) {
 +                if (dm.isActive() == 1) {
 +                    dm.destroy();
 +                    if (persist == 1) {
 +                        dm.undefine();
 +                    }
 +                }
 +            } else {
 +                if (dm.isActive() == 0) {
 +                    return null;
 +                }
 +                dm.shutdown();
 +                int retry = _stopTimeout / 2000;
 +                /* Wait for the domain gets into shutoff state. When it does
 +                   the dm object will no longer work, so we need to catch it. */
 +                try {
 +                    while (dm.isActive() == 1 && retry >= 0) {
 +                        Thread.sleep(2000);
 +                        retry--;
 +                    }
 +                } catch (final LibvirtException e) {
 +                    final String error = e.toString();
 +                    if (error.contains("Domain not found")) {
 +                        s_logger.debug("successfully shut down vm " + vmName);
 +                    } else {
 +                        s_logger.debug("Error in waiting for vm shutdown:" + error);
 +                    }
 +                }
 +                if (retry < 0) {
 +                    s_logger.warn("Timed out waiting for domain " + vmName + " to shutdown gracefully");
 +                    return Script.ERR_TIMEOUT;
 +                } else {
 +                    if (persist == 1) {
 +                        dm.undefine();
 +                    }
 +                }
 +            }
 +        } catch (final LibvirtException e) {
 +            if (e.getMessage().contains("Domain not found")) {
 +                s_logger.debug("VM " + vmName + " doesn't exist, no need to stop it");
 +                return null;
 +            }
 +            s_logger.debug("Failed to stop VM :" + vmName + " :", e);
 +            return e.getMessage();
 +        } catch (final InterruptedException ie) {
 +            s_logger.debug("Interrupted sleep");
 +            return ie.getMessage();
 +        } finally {
 +            try {
 +                if (dm != null) {
 +                    dm.free();
 +                }
 +            } catch (final LibvirtException e) {
 +                s_logger.trace("Ignoring libvirt error.", e);
 +            }
 +        }
 +
 +        return null;
 +    }
 +
 +    public Integer getVncPort(final Connect conn, final String vmName) throws LibvirtException {
 +        final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
 +        Domain dm = null;
 +        try {
 +            dm = conn.domainLookupByName(vmName);
 +            final String xmlDesc = dm.getXMLDesc(0);
 +            parser.parseDomainXML(xmlDesc);
 +            return parser.getVncPort();
 +        } finally {
 +            try {
 +                if (dm != null) {
 +                    dm.free();
 +                }
 +            } catch (final LibvirtException l) {
 +                s_logger.trace("Ignoring libvirt error.", l);
 +            }
 +        }
 +    }
 +
 +    private boolean IsHVMEnabled(final Connect conn) {
 +        final LibvirtCapXMLParser parser = new LibvirtCapXMLParser();
 +        try {
 +            parser.parseCapabilitiesXML(conn.getCapabilities());
 +            final ArrayList<String> osTypes = parser.getGuestOsType();
 +            for (final String o : osTypes) {
 +                if (o.equalsIgnoreCase("hvm")) {
 +                    return true;
 +                }
 +            }
 +        } catch (final LibvirtException e) {
 +            s_logger.trace("Ignoring libvirt error.", e);
 +        }
 +        return false;
 +    }
 +
 +    private String getHypervisorPath(final Connect conn) {
 +        final LibvirtCapXMLParser parser = new LibvirtCapXMLParser();
 +        try {
 +            parser.parseCapabilitiesXML(conn.getCapabilities());
 +        } catch (final LibvirtException e) {
 +            s_logger.debug(e.getMessage());
 +        }
 +        return parser.getEmulator();
 +    }
 +
 +    boolean isGuestPVEnabled(final String guestOSName) {
 +        DiskDef.DiskBus db = getGuestDiskModel(guestOSName);
 +        return db != DiskDef.DiskBus.IDE;
 +    }
 +
 +    public boolean isCentosHost() {
 +        if (_hvVersion <= 9) {
 +            return true;
 +        } else {
 +            return false;
 +        }
 +    }
 +
 +    public DiskDef.DiskBus getDiskModelFromVMDetail(final VirtualMachineTO vmTO) {
 +        Map<String, String> details = vmTO.getDetails();
 +        if (details == null) {
 +            return null;
 +        }
 +
 +        final String rootDiskController = details.get(VmDetailConstants.ROOT_DISK_CONTROLLER);
 +        if (StringUtils.isNotBlank(rootDiskController)) {
 +            s_logger.debug("Passed custom disk bus " + rootDiskController);
 +            for (final DiskDef.DiskBus bus : DiskDef.DiskBus.values()) {
 +                if (bus.toString().equalsIgnoreCase(rootDiskController)) {
 +                    s_logger.debug("Found matching enum for disk bus " + rootDiskController);
 +                    return bus;
 +                }
 +            }
 +        }
 +        return null;
 +    }
 +
 +    private DiskDef.DiskBus getGuestDiskModel(final String platformEmulator) {
 +        if (platformEmulator == null) {
 +            return DiskDef.DiskBus.IDE;
 +        } else if (platformEmulator.startsWith("Other PV Virtio-SCSI")) {
 +            return DiskDef.DiskBus.SCSI;
 +        } else if (platformEmulator.startsWith("Ubuntu") || platformEmulator.startsWith("Fedora 13") || platformEmulator.startsWith("Fedora 12") || platformEmulator.startsWith("Fedora 11") ||
 +                platformEmulator.startsWith("Fedora 10") || platformEmulator.startsWith("Fedora 9") || platformEmulator.startsWith("CentOS 5.3") || platformEmulator.startsWith("CentOS 5.4") ||
 +                platformEmulator.startsWith("CentOS 5.5") || platformEmulator.startsWith("CentOS") || platformEmulator.startsWith("Fedora") ||
 +                platformEmulator.startsWith("Red Hat Enterprise Linux 5.3") || platformEmulator.startsWith("Red Hat Enterprise Linux 5.4") ||
 +                platformEmulator.startsWith("Red Hat Enterprise Linux 5.5") || platformEmulator.startsWith("Red Hat Enterprise Linux 6") || platformEmulator.startsWith("Debian GNU/Linux") ||
 +                platformEmulator.startsWith("FreeBSD 10") || platformEmulator.startsWith("Oracle") || platformEmulator.startsWith("Other PV")) {
 +            return DiskDef.DiskBus.VIRTIO;
 +        } else {
 +            return DiskDef.DiskBus.IDE;
 +        }
 +
 +    }
 +    private void cleanupVMNetworks(final Connect conn, final List<InterfaceDef> nics) {
 +        if (nics != null) {
 +            for (final InterfaceDef nic : nics) {
 +                for (final VifDriver vifDriver : getAllVifDrivers()) {
 +                    vifDriver.unplug(nic);
 +                }
 +            }
 +        }
 +    }
 +
 +    public Domain getDomain(final Connect conn, final String vmName) throws LibvirtException {
 +        return conn.domainLookupByName(vmName);
 +    }
 +
 +    public List<InterfaceDef> getInterfaces(final Connect conn, final String vmName) {
 +        final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
 +        Domain dm = null;
 +        try {
 +            dm = conn.domainLookupByName(vmName);
 +            parser.parseDomainXML(dm.getXMLDesc(0));
 +            return parser.getInterfaces();
 +
 +        } catch (final LibvirtException e) {
 +            s_logger.debug("Failed to get dom xml: " + e.toString());
 +            return new ArrayList<InterfaceDef>();
 +        } finally {
 +            try {
 +                if (dm != null) {
 +                    dm.free();
 +                }
 +            } catch (final LibvirtException e) {
 +                s_logger.trace("Ignoring libvirt error.", e);
 +            }
 +        }
 +    }
 +
 +    public List<DiskDef> getDisks(final Connect conn, final String vmName) {
 +        final LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser();
 +        Domain dm = null;
 +        try {
 +            dm = conn.domainLookupByName(vmName);
 +            parser.parseDomainXML(dm.getXMLDesc(0));
 +            return parser.getDisks();
 +
 +        } catch (final LibvirtException e) {
 +            s_logger.debug("Failed to get dom xml: " + e.toString());
 +            return new ArrayList<DiskDef>();
 +        } finally {
 +            try {
 +                if (dm != null) {
 +                    dm.free();
 +                }
 +            } catch (final LibvirtException e) {
 +                s_logger.trace("Ignoring libvirt error.", e);
 +            }
 +        }
 +    }
 +
 +    private String executeBashScript(final String script) {
 +        final Script command = new Script("/bin/bash", _timeout, s_logger);
 +        command.add("-c");
 +        command.add(script);
 +        return command.execute();
 +    }
 +
 +    public List<VmNetworkStatsEntry> getVmNetworkStat(Connect conn, String vmName) throws LibvirtException {
 +        Domain dm = null;
 +        try {
 +            dm = getDomain(conn, vmName);
 +
 +            List<VmNetworkStatsEntry> stats = new ArrayList<VmNetworkStatsEntry>();
 +
 +            List<InterfaceDef> nics = getInterfaces(conn, vmName);
 +
 +            for (InterfaceDef nic : nics) {
 +                DomainInterfaceStats nicStats = dm.interfaceStats(nic.getDevName());
 +                String macAddress = nic.getMacAddress();
 +                VmNetworkStatsEntry stat = new VmNetworkStatsEntry(vmName, macAddress, nicStats.tx_bytes, nicStats.rx_bytes);
 +                stats.add(stat);
 +            }
 +
 +            return stats;
 +        } finally {
 +            if (dm != null) {
 +                dm.free();
 +            }
 +        }
 +    }
 +
 +    public List<VmDiskStatsEntry> getVmDiskStat(final Connect conn, final String vmName) throws LibvirtException {
 +        Domain dm = null;
 +        try {
 +            dm = getDomain(conn, vmName);
 +
 +            final List<VmDiskStatsEntry> stats = new ArrayList<VmDiskStatsEntry>();
 +
 +            final List<DiskDef> disks = getDisks(conn, vmName);
 +
 +            for (final DiskDef disk : disks) {
 +                if (disk.getDeviceType() != DeviceType.DISK) {
 +                    break;
 +                }
 +                final DomainBlockStats blockStats = dm.blockStats(disk.getDiskLabel());
 +                final String path = disk.getDiskPath(); // for example, path = /mnt/pool_uuid/disk_path/
 +                String diskPath = null;
 +                if (path != null) {
 +                    final String[] token = path.split("/");
 +                    if (token.length > 3) {
 +                        diskPath = token[3];
 +                        final VmDiskStatsEntry stat = new VmDiskStatsEntry(vmName, diskPath, blockStats.wr_req, blockStats.rd_req, blockStats.wr_bytes, blockStats.rd_bytes);
 +                        stats.add(stat);
 +                    }
 +                }
 +            }
 +
 +            return stats;
 +        } finally {
 +            if (dm != null) {
 +                dm.free();
 +            }
 +        }
 +    }
 +
 +    private class VmStats {
 +        long _usedTime;
 +        long _tx;
 +        long _rx;
 +        long _ioRead;
 +        long _ioWrote;
 +        long _bytesRead;
 +        long _bytesWrote;
 +        Calendar _timestamp;
 +    }
 +
 +    public VmStatsEntry getVmStat(final Connect conn, final String vmName) throws LibvirtException {
 +        Domain dm = null;
 +        try {
 +            dm = getDomain(conn, vmName);
 +            if (dm == null) {
 +                return null;
 +            }
 +            DomainInfo info = dm.getInfo();
 +            final VmStatsEntry stats = new VmStatsEntry();
 +
 +            stats.setNumCPUs(info.nrVirtCpu);
 +            stats.setEntityType("vm");
 +
 +            stats.setMemoryKBs(info.maxMem);
 +            stats.setTargetMemoryKBs(info.memory);
 +            stats.setIntFreeMemoryKBs(getMemoryFreeInKBs(dm));
 +
 +            /* get cpu utilization */
 +            VmStats oldStats = null;
 +
 +            final Calendar now = Calendar.getInstance();
 +
 +            oldStats = _vmStats.get(vmName);
 +
 +            long elapsedTime = 0;
 +            if (oldStats != null) {
 +                elapsedTime = now.getTimeInMillis() - oldStats._timestamp.getTimeInMillis();
 +                double utilization = (info.cpuTime - oldStats._usedTime) / ((double)elapsedTime * 1000000);
 +
 +                final NodeInfo node = conn.nodeInfo();
 +                utilization = utilization / node.cpus;
 +                if (utilization > 0) {
 +                    stats.setCPUUtilization(utilization * 100);
 +                }
 +            }
 +
 +            /* get network stats */
 +
 +            final List<InterfaceDef> vifs = getInterfaces(conn, vmName);
 +            long rx = 0;
 +            long tx = 0;
 +            for (final InterfaceDef vif : vifs) {
 +                final DomainInterfaceStats ifStats = dm.interfaceStats(vif.getDevName());
 +                rx += ifStats.rx_bytes;
 +                tx += ifStats.tx_bytes;
 +            }
 +
 +            if (oldStats != null) {
 +                final double deltarx = rx - oldStats._rx;
 +                if (deltarx > 0) {
 +                    stats.setNetworkReadKBs(deltarx / 1024);
 +                }
 +                final double deltatx = tx - oldStats._tx;
 +                if (deltatx > 0) {
 +                    stats.setNetworkWriteKBs(deltatx / 1024);
 +                }
 +            }
 +
 +            /* get disk stats */
 +            final List<DiskDef> disks = getDisks(conn, vmName);
 +            long io_rd = 0;
 +            long io_wr = 0;
 +            long bytes_rd = 0;
 +            long bytes_wr = 0;
 +            for (final DiskDef disk : disks) {
 +                if (disk.getDeviceType() == DeviceType.CDROM || disk.getDeviceType() == DeviceType.FLOPPY) {
 +                    continue;
 +                }
 +                final DomainBlockStats blockStats = dm.blockStats(disk.getDiskLabel());
 +                io_rd += blockStats.rd_req;
 +                io_wr += blockStats.wr_req;
 +                bytes_rd += blockStats.rd_bytes;
 +                bytes_wr += blockStats.wr_bytes;
 +            }
 +
 +            if (oldStats != null) {
 +                final long deltaiord = io_rd - oldStats._ioRead;
 +                if (deltaiord > 0) {
 +                    stats.setDiskReadIOs(deltaiord);
 +                }
 +                final long deltaiowr = io_wr - oldStats._ioWrote;
 +                if (deltaiowr > 0) {
 +                    stats.setDiskWriteIOs(deltaiowr);
 +                }
 +                final double deltabytesrd = bytes_rd - oldStats._bytesRead;
 +                if (deltabytesrd > 0) {
 +                    stats.setDiskReadKBs(deltabytesrd / 1024);
 +                }
 +                final double deltabyteswr = bytes_wr - oldStats._bytesWrote;
 +                if (deltabyteswr > 0) {
 +                    stats.setDiskWriteKBs(deltabyteswr / 1024);
 +                }
 +            }
 +
 +            /* save to Hashmap */
 +            final VmStats newStat = new VmStats();
 +            newStat._usedTime = info.cpuTime;
 +            newStat._rx = rx;
 +            newStat._tx = tx;
 +            newStat._ioRead = io_rd;
 +            newStat._ioWrote = io_wr;
 +            newStat._bytesRead = bytes_rd;
 +            newStat._bytesWrote = bytes_wr;
 +            newStat._timestamp = now;
 +            _vmStats.put(vmName, newStat);
 +            return stats;
 +        } finally {
 +            if (dm != null) {
 +                dm.free();
 +            }
 +        }
 +    }
 +
 +    /**
 +     * This method retrieves the memory statistics from the domain given as parameters.
 +     * If no memory statistic is found, it will return {@link NumberUtils#LONG_ZERO} as the value of free memory in the domain.
 +     * If it can retrieve the domain memory statistics, it will return the free memory statistic; that means, it returns the value at the first position of the array returned by {@link Domain#memoryStats(int)}.
 +     *
 +     * @return the amount of free memory in KBs
 +     */
 +    protected long getMemoryFreeInKBs(Domain dm) throws LibvirtException {
 +        MemoryStatistic[] mems = dm.memoryStats(NUMMEMSTATS);
 +        if (ArrayUtils.isEmpty(mems)) {
 +            return NumberUtils.LONG_ZERO;
 +        }
 +        return mems[0].getValue();
 +    }
 +
 +    private boolean canBridgeFirewall(final String prvNic) {
 +        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
 +        cmd.add("can_bridge_firewall");
 +        cmd.add("--privnic", prvNic);
 +        final String result = cmd.execute();
 +        if (result != null) {
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    public boolean destroyNetworkRulesForVM(final Connect conn, final String vmName) {
 +        if (!_canBridgeFirewall) {
 +            return false;
 +        }
 +        String vif = null;
 +        final List<InterfaceDef> intfs = getInterfaces(conn, vmName);
 +        if (intfs.size() > 0) {
 +            final InterfaceDef intf = intfs.get(0);
 +            vif = intf.getDevName();
 +        }
 +        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
 +        cmd.add("destroy_network_rules_for_vm");
 +        cmd.add("--vmname", vmName);
 +        if (vif != null) {
 +            cmd.add("--vif", vif);
 +        }
 +        final String result = cmd.execute();
 +        if (result != null) {
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    public boolean defaultNetworkRules(final Connect conn, final String vmName, final NicTO nic, final Long vmId, final String secIpStr) {
 +        if (!_canBridgeFirewall) {
 +            return false;
 +        }
 +
 +        final List<InterfaceDef> intfs = getInterfaces(conn, vmName);
 +        if (intfs.size() == 0 || intfs.size() < nic.getDeviceId()) {
 +            return false;
 +        }
 +
 +        final InterfaceDef intf = intfs.get(nic.getDeviceId());
 +        final String brname = intf.getBrName();
 +        final String vif = intf.getDevName();
 +
 +        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
 +        cmd.add("default_network_rules");
 +        cmd.add("--vmname", vmName);
 +        cmd.add("--vmid", vmId.toString());
 +        if (nic.getIp() != null) {
 +            cmd.add("--vmip", nic.getIp());
 +        }
 +        if (nic.getIp6Address() != null) {
 +            cmd.add("--vmip6", nic.getIp6Address());
 +        }
 +        cmd.add("--vmmac", nic.getMac());
 +        cmd.add("--vif", vif);
 +        cmd.add("--brname", brname);
 +        cmd.add("--nicsecips", secIpStr);
 +        final String result = cmd.execute();
 +        if (result != null) {
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    protected boolean post_default_network_rules(final Connect conn, final String vmName, final NicTO nic, final Long vmId, final InetAddress dhcpServerIp, final String hostIp, final String hostMacAddr) {
 +        if (!_canBridgeFirewall) {
 +            return false;
 +        }
 +
 +        final List<InterfaceDef> intfs = getInterfaces(conn, vmName);
 +        if (intfs.size() < nic.getDeviceId()) {
 +            return false;
 +        }
 +
 +        final InterfaceDef intf = intfs.get(nic.getDeviceId());
 +        final String brname = intf.getBrName();
 +        final String vif = intf.getDevName();
 +
 +        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
 +        cmd.add("post_default_network_rules");
 +        cmd.add("--vmname", vmName);
 +        cmd.add("--vmid", vmId.toString());
 +        cmd.add("--vmip", nic.getIp());
 +        cmd.add("--vmmac", nic.getMac());
 +        cmd.add("--vif", vif);
 +        cmd.add("--brname", brname);
 +        if (dhcpServerIp != null) {
 +            cmd.add("--dhcpSvr", dhcpServerIp.getHostAddress());
 +        }
 +
 +        cmd.add("--hostIp", hostIp);
 +        cmd.add("--hostMacAddr", hostMacAddr);
 +        final String result = cmd.execute();
 +        if (result != null) {
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    public boolean configureDefaultNetworkRulesForSystemVm(final Connect conn, final String vmName) {
 +        if (!_canBridgeFirewall) {
 +            return false;
 +        }
 +
 +        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
 +        cmd.add("default_network_rules_systemvm");
 +        cmd.add("--vmname", vmName);
 +        cmd.add("--localbrname", _linkLocalBridgeName);
 +        final String result = cmd.execute();
 +        if (result != null) {
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    public boolean addNetworkRules(final String vmName, final String vmId, final String guestIP, final String guestIP6, final String sig, final String seq, final String mac, final String rules, final String vif, final String brname,
 +                                   final String secIps) {
 +        if (!_canBridgeFirewall) {
 +            return false;
 +        }
 +
 +        final String newRules = rules.replace(" ", ";");
 +        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
 +        cmd.add("add_network_rules");
 +        cmd.add("--vmname", vmName);
 +        cmd.add("--vmid", vmId);
 +        cmd.add("--vmip", guestIP);
 +        if (StringUtils.isNotBlank(guestIP6)) {
 +            cmd.add("--vmip6", guestIP6);
 +        }
 +        cmd.add("--sig", sig);
 +        cmd.add("--seq", seq);
 +        cmd.add("--vmmac", mac);
 +        cmd.add("--vif", vif);
 +        cmd.add("--brname", brname);
 +        cmd.add("--nicsecips", secIps);
 +        if (newRules != null && !newRules.isEmpty()) {
 +            cmd.add("--rules", newRules);
 +        }
 +        final String result = cmd.execute();
 +        if (result != null) {
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    public boolean configureNetworkRulesVMSecondaryIP(final Connect conn, final String vmName, final String secIp, final String action) {
 +
 +        if (!_canBridgeFirewall) {
 +            return false;
 +        }
 +
 +        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
 +        cmd.add("network_rules_vmSecondaryIp");
 +        cmd.add("--vmname", vmName);
 +        cmd.add("--nicsecips", secIp);
 +        cmd.add("--action", action);
 +
 +        final String result = cmd.execute();
 +        if (result != null) {
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    public boolean cleanupRules() {
 +        if (!_canBridgeFirewall) {
 +            return false;
 +        }
 +        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
 +        cmd.add("cleanup_rules");
 +        final String result = cmd.execute();
 +        if (result != null) {
 +            return false;
 +        }
 +        return true;
 +    }
 +
 +    public String getRuleLogsForVms() {
 +        final Script cmd = new Script(_securityGroupPath, _timeout, s_logger);
 +        cmd.add("get_rule_logs_for_vms");
 +        final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
 +        final String result = cmd.execute(parser);
 +        if (result == null) {
 +            return parser.getLine();
 +        }
 +        return null;
 +    }
 +
 +    private HashMap<String, Pair<Long, Long>> syncNetworkGroups(final long id) {
 +        final HashMap<String, Pair<Long, Long>> states = new HashMap<String, Pair<Long, Long>>();
 +
 +        final String result = getRuleLogsForVms();
 +        s_logger.trace("syncNetworkGroups: id=" + id + " got: " + result);
 +        final String[] rulelogs = result != null ? result.split(";") : new String[0];
 +        for (final String rulesforvm : rulelogs) {
 +            final String[] log = rulesforvm.split(",");
 +            if (log.length != 6) {
 +                continue;
 +            }
 +            try {
 +                states.put(log[0], new Pair<Long, Long>(Long.parseLong(log[1]), Long.parseLong(log[5])));
 +            } catch (final NumberFormatException nfe) {
 +                states.put(log[0], new Pair<Long, Long>(-1L, -1L));
 +            }
 +        }
 +        return states;
 +    }
 +
 +    /* online snapshot supported by enhanced qemu-kvm */
 +    private boolean isSnapshotSupported() {
 +        final String result = executeBashScript("qemu-img --help|grep convert");
 +        if (result != null) {
 +            return false;
 +        } else {
 +            return true;
 +        }
 +    }
 +
 +    public Pair<Double, Double> getNicStats(final String nicName) {
 +        return new Pair<Double, Double>(readDouble(nicName, "rx_bytes"), readDouble(nicName, "tx_bytes"));
 +    }
 +
 +    static double readDouble(final String nicName, final String fileName) {
 +        final String path = "/sys/class/net/" + nicName + "/statistics/" + fileName;
 +        try {
 +            return Double.parseDouble(FileUtils.readFileToString(new File(path)));
 +        } catch (final IOException ioe) {
 +            s_logger.warn("Failed to read the " + fileName + " for " + nicName + " from " + path, ioe);
 +            return 0.0;
 +        }
 +    }
 +
 +    private String prettyVersion(final long version) {
 +        final long major = version / 1000000;
 +        final long minor = version % 1000000 / 1000;
 +        final long release = version % 1000000 % 1000;
 +        return major + "." + minor + "." + release;
 +    }
 +
 +    @Override
 +    public void setName(final String name) {
 +        // TODO Auto-generated method stub
 +    }
 +
 +    @Override
 +    public void setConfigParams(final Map<String, Object> params) {
 +        // TODO Auto-generated method stub
 +    }
 +
 +    @Override
 +    public Map<String, Object> getConfigParams() {
 +        // TODO Auto-generated method stub
 +        return null;
 +    }
 +
 +    @Override
 +    public int getRunLevel() {
 +        // TODO Auto-generated method stub
 +        return 0;
 +    }
 +
 +    @Override
 +    public void setRunLevel(final int level) {
 +        // TODO Auto-generated method stub
 +    }
 +
 +    public HypervisorType getHypervisorType(){
 +        return _hypervisorType;
 +    }
 +
 +    public String mapRbdDevice(final KVMPhysicalDisk disk){
 +        final KVMStoragePool pool = disk.getPool();
 +        //Check if rbd image is already mapped
 +        final String[] splitPoolImage = disk.getPath().split("/");
 +        String device = Script.runSimpleBashScript("rbd showmapped | grep \""+splitPoolImage[0]+"[ ]*"+splitPoolImage[1]+"\" | grep -o \"[^ ]*[ ]*$\"");
 +        if(device == null) {
 +            //If not mapped, map and return mapped device
 +            Script.runSimpleBashScript("rbd map " + disk.getPath() + " --id " + pool.getAuthUserName());
 +            device = Script.runSimpleBashScript("rbd showmapped | grep \""+splitPoolImage[0]+"[ ]*"+splitPoolImage[1]+"\" | grep -o \"[^ ]*[ ]*$\"");
 +        }
 +        return device;
 +    }
 +
 +    public List<Ternary<String, Boolean, String>> cleanVMSnapshotMetadata(Domain dm) throws LibvirtException {
 +        s_logger.debug("Cleaning the metadata of vm snapshots of vm " + dm.getName());
 +        List<Ternary<String, Boolean, String>> vmsnapshots = new ArrayList<Ternary<String, Boolean, String>>();
 +        if (dm.snapshotNum() == 0) {
 +            return vmsnapshots;
 +        }
 +        String currentSnapshotName = null;
 +        try {
 +            DomainSnapshot snapshotCurrent = dm.snapshotCurrent();
 +            String snapshotXML = snapshotCurrent.getXMLDesc();
 +            snapshotCurrent.free();
 +            DocumentBuilder builder;
 +            try {
 +                builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 +
 +                InputSource is = new InputSource();
 +                is.setCharacterStream(new StringReader(snapshotXML));
 +                Document doc = builder.parse(is);
 +                Element rootElement = doc.getDocumentElement();
 +
 +                currentSnapshotName = getTagValue("name", rootElement);
 +            } catch (ParserConfigurationException e) {
 +                s_logger.debug(e.toString());
 +            } catch (SAXException e) {
 +                s_logger.debug(e.toString());
 +            } catch (IOException e) {
 +                s_logger.debug(e.toString());
 +            }
 +        } catch (LibvirtException e) {
 +            s_logger.debug("Fail to get the current vm snapshot for vm: " + dm.getName() + ", continue");
 +        }
 +        int flags = 2; // VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY = 2
 +        String[] snapshotNames = dm.snapshotListNames();
 +        Arrays.sort(snapshotNames);
 +        for (String snapshotName: snapshotNames) {
 +            DomainSnapshot snapshot = dm.snapshotLookupByName(snapshotName);
 +            Boolean isCurrent = (currentSnapshotName != null && currentSnapshotName.equals(snapshotName)) ? true: false;
 +            vmsnapshots.add(new Ternary<String, Boolean, String>(snapshotName, isCurrent, snapshot.getXMLDesc()));
 +        }
 +        for (String snapshotName: snapshotNames) {
 +            DomainSnapshot snapshot = dm.snapshotLookupByName(snapshotName);
 +            snapshot.delete(flags); // clean metadata of vm snapshot
 +        }
 +        return vmsnapshots;
 +    }
 +
 +    private static String getTagValue(String tag, Element eElement) {
 +        NodeList nlList = eElement.getElementsByTagName(tag).item(0).getChildNodes();
 +        Node nValue = nlList.item(0);
 +
 +        return nValue.getNodeValue();
 +    }
 +
 +    public void restoreVMSnapshotMetadata(Domain dm, String vmName, List<Ternary<String, Boolean, String>> vmsnapshots) {
 +        s_logger.debug("Restoring the metadata of vm snapshots of vm " + vmName);
 +        for (Ternary<String, Boolean, String> vmsnapshot: vmsnapshots) {
 +            String snapshotName = vmsnapshot.first();
 +            Boolean isCurrent = vmsnapshot.second();
 +            String snapshotXML = vmsnapshot.third();
 +            s_logger.debug("Restoring vm snapshot " + snapshotName + " on " + vmName + " with XML:\n " + snapshotXML);
 +            try {
 +                int flags = 1; // VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE = 1
 +                if (isCurrent) {
 +                    flags += 2; // VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT = 2
 +                }
 +                dm.snapshotCreateXML(snapshotXML, flags);
 +            } catch (LibvirtException e) {
 +                s_logger.debug("Failed to restore vm snapshot " + snapshotName + ", continue");
 +                continue;
 +            }
 +        }
 +    }
 +
 +    public String getHostDistro() {
 +        return _hostDistro;
 +    }
 +
 +    public boolean isHostSecured() {
 +        // Test for host certificates
 +        final File confFile = PropertiesUtil.findConfigFile(KeyStoreUtils.AGENT_PROPSFILE);
 +        if (confFile == null || !confFile.exists() || !Paths.get(confFile.getParent(), KeyStoreUtils.CERT_FILENAME).toFile().exists()) {
 +            return false;
 +        }
 +
 +        // Test for libvirt TLS configuration
 +        try {
 +            new Connect(String.format("qemu+tls://%s/system", _privateIp));
 +        } catch (final LibvirtException ignored) {
 +            return false;
 +        }
 +        return true;
 +    }
 +}
diff --cc plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java
index fd705e4,0000000..da9f1f3
mode 100644,000000..100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java
@@@ -1,156 -1,0 +1,157 @@@
 +//
 +// Licensed to the Apache Software Foundation (ASF) under one
 +// or more contributor license agreements.  See the NOTICE file
 +// distributed with this work for additional information
 +// regarding copyright ownership.  The ASF licenses this file
 +// to you under the Apache License, Version 2.0 (the
 +// "License"); you may not use this file except in compliance
 +// with the License.  You may obtain a copy of the License at
 +//
 +//   http://www.apache.org/licenses/LICENSE-2.0
 +//
 +// Unless required by applicable law or agreed to in writing,
 +// software distributed under the License is distributed on an
 +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 +// KIND, either express or implied.  See the License for the
 +// specific language governing permissions and limitations
 +// under the License.
 +//
 +
 +package com.cloud.hypervisor.kvm.resource.wrapper;
 +
 +import java.net.URISyntaxException;
 +import java.util.List;
 +
 +import org.apache.log4j.Logger;
 +import org.libvirt.Connect;
 +import org.libvirt.DomainInfo.DomainState;
 +import org.libvirt.LibvirtException;
 +
 +import com.cloud.agent.api.Answer;
 +import com.cloud.agent.api.StartAnswer;
 +import com.cloud.agent.api.StartCommand;
 +import com.cloud.agent.api.to.NicTO;
 +import com.cloud.agent.api.to.VirtualMachineTO;
 +import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource;
 +import com.cloud.exception.InternalErrorException;
 +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef;
 +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
 +import com.cloud.network.Networks.IsolationType;
 +import com.cloud.network.Networks.TrafficType;
 +import com.cloud.resource.CommandWrapper;
 +import com.cloud.resource.ResourceWrapper;
 +import com.cloud.vm.VirtualMachine;
 +
 +@ResourceWrapper(handles =  StartCommand.class)
 +public final class LibvirtStartCommandWrapper extends CommandWrapper<StartCommand, Answer, LibvirtComputingResource> {
 +
 +    private static final Logger s_logger = Logger.getLogger(LibvirtStartCommandWrapper.class);
 +
 +    @Override
 +    public Answer execute(final StartCommand command, final LibvirtComputingResource libvirtComputingResource) {
 +        final VirtualMachineTO vmSpec = command.getVirtualMachine();
 +        vmSpec.setVncAddr(command.getHostIp());
 +        final String vmName = vmSpec.getName();
 +        LibvirtVMDef vm = null;
 +
 +        DomainState  state = DomainState.VIR_DOMAIN_SHUTOFF;
 +        final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
 +        final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
 +        Connect conn = null;
 +        try {
 +
 +            vm = libvirtComputingResource.createVMFromSpec(vmSpec);
 +            conn = libvirtUtilitiesHelper.getConnectionByType(vm.getHvsType());
 +
 +            final NicTO[] nics = vmSpec.getNics();
 +
 +            for (final NicTO nic : nics) {
 +                if (vmSpec.getType() != VirtualMachine.Type.User) {
 +                    nic.setPxeDisable(true);
 +                    nic.setDpdkDisabled(true);
 +                }
 +            }
 +
 +            libvirtComputingResource.createVbd(conn, vmSpec, vmName, vm);
 +
 +            if (!storagePoolMgr.connectPhysicalDisksViaVmSpec(vmSpec)) {
 +                return new StartAnswer(command, "Failed to connect physical disks to host");
 +            }
 +
 +            libvirtComputingResource.createVifs(vmSpec, vm);
 +
 +            s_logger.debug("starting " + vmName + ": " + vm.toString());
 +            libvirtComputingResource.startVM(conn, vmName, vm.toString());
 +
 +            for (final NicTO nic : nics) {
 +                if (nic.isSecurityGroupEnabled() || nic.getIsolationUri() != null && nic.getIsolationUri().getScheme().equalsIgnoreCase(IsolationType.Ec2.toString())) {
 +                    if (vmSpec.getType() != VirtualMachine.Type.User) {
 +                        libvirtComputingResource.configureDefaultNetworkRulesForSystemVm(conn, vmName);
 +                        break;
 +                    } else {
 +                        final List<String> nicSecIps = nic.getNicSecIps();
 +                        String secIpsStr;
 +                        final StringBuilder sb = new StringBuilder();
 +                        if (nicSecIps != null) {
 +                            for (final String ip : nicSecIps) {
 +                                sb.append(ip).append(";");
 +                            }
 +                            secIpsStr = sb.toString();
 +                        } else {
 +                            secIpsStr = "0;";
 +                        }
 +                        libvirtComputingResource.defaultNetworkRules(conn, vmName, nic, vmSpec.getId(), secIpsStr);
 +                    }
 +                }
 +            }
 +
 +            // pass cmdline info to system vms
 +            if (vmSpec.getType() != VirtualMachine.Type.User) {
-                 //wait and try passCmdLine for 5 minutes at most for CLOUDSTACK-2823
 +                String controlIp = null;
 +                for (final NicTO nic : nics) {
 +                    if (nic.getType() == TrafficType.Control) {
 +                        controlIp = nic.getIp();
 +                        break;
 +                    }
 +                }
-                 for (int count = 0; count < 30; count++) {
++                // try to patch and SSH into the systemvm for up to 5 minutes
++                for (int count = 0; count < 10; count++) {
++                    // wait and try passCmdLine for 30 seconds at most for CLOUDSTACK-2823
 +                    libvirtComputingResource.passCmdLine(vmName, vmSpec.getBootArgs());
-                     //check router is up?
++                    // check router is up?
 +                    final VirtualRoutingResource virtRouterResource = libvirtComputingResource.getVirtRouterResource();
 +                    final boolean result = virtRouterResource.connect(controlIp, 1, 5000);
 +                    if (result) {
 +                        break;
 +                    }
 +                }
 +            }
 +
 +            state = DomainState.VIR_DOMAIN_RUNNING;
 +            return new StartAnswer(command);
 +        } catch (final LibvirtException e) {
 +            s_logger.warn("LibvirtException ", e);
 +            if (conn != null) {
 +                libvirtComputingResource.handleVmStartFailure(conn, vmName, vm);
 +            }
 +            return new StartAnswer(command, e.getMessage());
 +        } catch (final InternalErrorException e) {
 +            s_logger.warn("InternalErrorException ", e);
 +            if (conn != null) {
 +                libvirtComputingResource.handleVmStartFailure(conn, vmName, vm);
 +            }
 +            return new StartAnswer(command, e.getMessage());
 +        } catch (final URISyntaxException e) {
 +            s_logger.warn("URISyntaxException ", e);
 +            if (conn != null) {
 +                libvirtComputingResource.handleVmStartFailure(conn, vmName, vm);
 +            }
 +            return new StartAnswer(command, e.getMessage());
 +        } finally {
 +            if (state != DomainState.VIR_DOMAIN_RUNNING) {
 +                storagePoolMgr.disconnectPhysicalDisksViaVmSpec(vmSpec);
 +            }
 +        }
 +    }
 +}
diff --cc plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
index 414bc04,0000000..50b6afe
mode 100644,000000..100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
@@@ -1,278 -1,0 +1,278 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +
 +package com.cloud.hypervisor.kvm.resource;
 +
 +import java.io.File;
 +
 +import junit.framework.TestCase;
 +
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
 +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef;
 +
 +public class LibvirtVMDefTest extends TestCase {
 +
 +    public void testInterfaceEtehrnet() {
 +        LibvirtVMDef.InterfaceDef ifDef = new LibvirtVMDef.InterfaceDef();
 +        ifDef.defEthernet("targetDeviceName", "00:11:22:aa:bb:dd", LibvirtVMDef.InterfaceDef.NicModel.VIRTIO);
 +
 +        String expected =
 +            "<interface type='ethernet'>\n"
 +                    + "<target dev='targetDeviceName'/>\n"
 +                    + "<mac address='00:11:22:aa:bb:dd'/>\n"
 +                    + "<model type='virtio'/>\n"
 +                    + "<link state='up'/>\n"
 +                    + "</interface>\n";
 +
 +        assertEquals(expected, ifDef.toString());
 +    }
 +
 +    public void testInterfaceDirectNet() {
 +        LibvirtVMDef.InterfaceDef ifDef = new LibvirtVMDef.InterfaceDef();
 +        ifDef.defDirectNet("targetDeviceName", null, "00:11:22:aa:bb:dd", LibvirtVMDef.InterfaceDef.NicModel.VIRTIO, "private");
 +
 +        String expected =
 +            "<interface type='" + LibvirtVMDef.InterfaceDef.GuestNetType.DIRECT + "'>\n"
 +                    + "<source dev='targetDeviceName' mode='private'/>\n"
 +                    + "<mac address='00:11:22:aa:bb:dd'/>\n"
 +                    + "<model type='virtio'/>\n"
 +                    + "<link state='up'/>\n"
 +                    + "</interface>\n";
 +
 +        assertEquals(expected, ifDef.toString());
 +    }
 +
 +    public void testInterfaceBridgeSlot() {
 +        LibvirtVMDef.InterfaceDef ifDef = new LibvirtVMDef.InterfaceDef();
 +        ifDef.defBridgeNet("targetDeviceName", null, "00:11:22:aa:bb:dd", LibvirtVMDef.InterfaceDef.NicModel.VIRTIO);
 +        ifDef.setSlot(16);
 +
 +        String expected =
 +                "<interface type='" + LibvirtVMDef.InterfaceDef.GuestNetType.BRIDGE + "'>\n"
 +                        + "<source bridge='targetDeviceName'/>\n"
 +                        + "<mac address='00:11:22:aa:bb:dd'/>\n"
 +                        + "<model type='virtio'/>\n"
 +                        + "<link state='up'/>\n"
 +                        + "<address type='pci' domain='0x0000' bus='0x00' slot='0x10' function='0x0'/>\n"
 +                        + "</interface>\n";
 +
 +        assertEquals(expected, ifDef.toString());
 +
 +        ifDef.setLinkStateUp(false);
 +        ifDef.setDevName("vnet11");
 +
 +        expected =
 +                "<interface type='" + LibvirtVMDef.InterfaceDef.GuestNetType.BRIDGE + "'>\n"
 +                        + "<source bridge='targetDeviceName'/>\n"
 +                        + "<target dev='vnet11'/>\n"
 +                        + "<mac address='00:11:22:aa:bb:dd'/>\n"
 +                        + "<model type='virtio'/>\n"
 +                        + "<link state='down'/>\n"
 +                        + "<address type='pci' domain='0x0000' bus='0x00' slot='0x10' function='0x0'/>\n"
 +                        + "</interface>\n";
 +
 +        assertEquals(expected, ifDef.toString());
 +    }
 +
 +    public void testCpuModeDef() {
 +        LibvirtVMDef.CpuModeDef cpuModeDef = new LibvirtVMDef.CpuModeDef();
 +        cpuModeDef.setMode("custom");
 +        cpuModeDef.setModel("Nehalem");
 +
 +        String expected1 = "<cpu mode='custom' match='exact'><model fallback='allow'>Nehalem</model></cpu>";
 +
 +        assertEquals(expected1, cpuModeDef.toString());
 +
 +        cpuModeDef.setMode("host-model");
 +        String expected2 = "<cpu mode='host-model'><model fallback='allow'></model></cpu>";
 +
 +        assertEquals(expected2, cpuModeDef.toString());
 +
 +        cpuModeDef.setMode("host-passthrough");
 +        String expected3 = "<cpu mode='host-passthrough'></cpu>";
 +        assertEquals(expected3, cpuModeDef.toString());
 +
 +    }
 +
 +    public void testDiskDef() {
 +        String filePath = "/var/lib/libvirt/images/disk.qcow2";
 +        String diskLabel = "vda";
 +
 +        DiskDef disk = new DiskDef();
 +        DiskDef.DiskBus bus = DiskDef.DiskBus.VIRTIO;
 +        DiskDef.DiskFmtType type = DiskDef.DiskFmtType.QCOW2;
 +        DiskDef.DiskCacheMode cacheMode = DiskDef.DiskCacheMode.WRITEBACK;
 +
 +        disk.defFileBasedDisk(filePath, diskLabel, bus, type);
 +        disk.setCacheMode(cacheMode);
 +
 +        assertEquals(filePath, disk.getDiskPath());
 +        assertEquals(diskLabel, disk.getDiskLabel());
 +        assertEquals(bus, disk.getBusType());
 +        assertEquals(DiskDef.DeviceType.DISK, disk.getDeviceType());
 +
 +        String xmlDef = disk.toString();
 +        String expectedXml = "<disk  device='disk' type='file'>\n<driver name='qemu' type='" + type.toString() + "' cache='" + cacheMode.toString() + "' />\n" +
 +                             "<source file='" + filePath + "'/>\n<target dev='" + diskLabel + "' bus='" + bus.toString() + "'/>\n</disk>\n";
 +
 +        assertEquals(xmlDef, expectedXml);
 +    }
 +
 +    public void testDiskDefWithBurst() {
 +        String filePath = "/var/lib/libvirt/images/disk.qcow2";
 +        String diskLabel = "vda";
 +
 +        DiskDef disk = new DiskDef();
 +        DiskDef.DiskBus bus = DiskDef.DiskBus.VIRTIO;
 +        DiskDef.DiskFmtType type = DiskDef.DiskFmtType.QCOW2;
 +        disk.defFileBasedDisk(filePath, diskLabel, bus, type);
 +
 +
 +        Long iopsReadRate = 500L;
 +        Long iopsReadRateMax = 2000L;
 +        Long iopsReadRateMaxLength = 120L;
 +        Long iopsWriteRate = 501L;
 +        Long iopsWriteRateMax = 2001L;
 +        Long iopsWriteRateMaxLength = 121L;
 +        Long bytesReadRate = 1000L;
 +        Long bytesReadRateMax = 2500L;
 +        Long bytesReadRateMaxLength = 122L;
 +        Long bytesWriteRate = 1001L;
 +        Long bytesWriteRateMax = 2501L;
 +        Long bytesWriteRateMaxLength = 123L;
 +
 +
 +        disk.setIopsReadRate(iopsReadRate);
 +        disk.setIopsReadRateMax(iopsReadRateMax);
 +        disk.setIopsReadRateMaxLength(iopsReadRateMaxLength);
 +        disk.setIopsWriteRate(iopsWriteRate);
 +        disk.setIopsWriteRateMax(iopsWriteRateMax);
 +        disk.setIopsWriteRateMaxLength(iopsWriteRateMaxLength);
 +        disk.setBytesReadRate(bytesReadRate);
 +        disk.setBytesReadRateMax(bytesReadRateMax);
 +        disk.setBytesReadRateMaxLength(bytesReadRateMaxLength);
 +        disk.setBytesWriteRate(bytesWriteRate);
 +        disk.setBytesWriteRateMax(bytesWriteRateMax);
 +        disk.setBytesWriteRateMaxLength(bytesWriteRateMaxLength);
 +
 +        LibvirtVMDef.setGlobalQemuVersion(2006000L);
 +        LibvirtVMDef.setGlobalLibvirtVersion(9008L);
 +
 +        String xmlDef = disk.toString();
 +        String expectedXml = "<disk  device='disk' type='file'>\n<driver name='qemu' type='" + type.toString() + "' cache='none' />\n" +
 +                "<source file='" + filePath + "'/>\n<target dev='" + diskLabel + "' bus='" + bus.toString() + "'/>\n" +
 +                "<iotune>\n<read_bytes_sec>"+bytesReadRate+"</read_bytes_sec>\n<write_bytes_sec>"+bytesWriteRate+"</write_bytes_sec>\n" +
 +                "<read_iops_sec>"+iopsReadRate+"</read_iops_sec>\n<write_iops_sec>"+iopsWriteRate+"</write_iops_sec>\n" +
 +                "<read_bytes_sec_max>"+bytesReadRateMax+"</read_bytes_sec_max>\n<write_bytes_sec_max>"+bytesWriteRateMax+"</write_bytes_sec_max>\n" +
 +                "<read_iops_sec_max>"+iopsReadRateMax+"</read_iops_sec_max>\n<write_iops_sec_max>"+iopsWriteRateMax+"</write_iops_sec_max>\n" +
 +                "<read_bytes_sec_max_length>"+bytesReadRateMaxLength+"</read_bytes_sec_max_length>\n<write_bytes_sec_max_length>"+bytesWriteRateMaxLength+"</write_bytes_sec_max_length>\n" +
 +                "<read_iops_sec_max_length>"+iopsReadRateMaxLength+"</read_iops_sec_max_length>\n<write_iops_sec_max_length>"+iopsWriteRateMaxLength+"</write_iops_sec_max_length>\n</iotune>\n</disk>\n";
 +
 +                assertEquals(xmlDef, expectedXml);
 +    }
 +
 +    public void testHypervEnlightDef() {
 +        LibvirtVMDef.FeaturesDef featuresDef = new LibvirtVMDef.FeaturesDef();
 +        LibvirtVMDef.HyperVEnlightenmentFeatureDef hyperVEnlightenmentFeatureDef = new LibvirtVMDef.HyperVEnlightenmentFeatureDef();
 +        hyperVEnlightenmentFeatureDef.setFeature("relaxed", true);
 +        hyperVEnlightenmentFeatureDef.setFeature("vapic", true);
 +        hyperVEnlightenmentFeatureDef.setFeature("spinlocks", true);
 +        hyperVEnlightenmentFeatureDef.setRetries(8096);
 +        featuresDef.addHyperVFeature(hyperVEnlightenmentFeatureDef);
 +        String defs = featuresDef.toString();
 +        assertTrue(defs.contains("relaxed"));
 +        assertTrue(defs.contains("vapic"));
 +        assertTrue(defs.contains("spinlocks"));
 +
 +        featuresDef = new LibvirtVMDef.FeaturesDef();
 +        featuresDef.addFeatures("pae");
 +        defs = featuresDef.toString();
 +        assertFalse(defs.contains("relaxed"));
 +        assertFalse(defs.contains("vapic"));
 +        assertFalse(defs.contains("spinlocks"));
 +        assertTrue("Windows PV".contains("Windows PV"));
 +
 +    }
 +
 +    public void testRngDef() {
 +        LibvirtVMDef.RngDef.RngBackendModel backendModel = LibvirtVMDef.RngDef.RngBackendModel.RANDOM;
 +        String path = "/dev/random";
 +        int period = 2000;
 +        int bytes = 2048;
 +
 +        LibvirtVMDef.RngDef def = new LibvirtVMDef.RngDef(path, backendModel, bytes, period);
 +        assertEquals(def.getPath(), path);
 +        assertEquals(def.getRngBackendModel(), backendModel);
 +        assertEquals(def.getRngModel(), LibvirtVMDef.RngDef.RngModel.VIRTIO);
 +        assertEquals(def.getRngRateBytes(), bytes);
 +        assertEquals(def.getRngRatePeriod(), period);
 +    }
 +
 +    public void testChannelDef() {
 +        ChannelDef.ChannelType type = ChannelDef.ChannelType.UNIX;
 +        ChannelDef.ChannelState state = ChannelDef.ChannelState.CONNECTED;
-         String name = "v-136-VM.vport";
++        String name = "v-136-VM.org.qemu.guest_agent.0";
 +        File path = new File("/var/lib/libvirt/qemu/" + name);
 +
 +        ChannelDef channelDef = new ChannelDef(name, type, state, path);
 +
 +        assertEquals(state, channelDef.getChannelState());
 +        assertEquals(type, channelDef.getChannelType());
 +        assertEquals(name, channelDef.getName());
 +        assertEquals(path, channelDef.getPath());
 +    }
 +
 +    public void testWatchDogDef() {
 +        LibvirtVMDef.WatchDogDef.WatchDogModel model = LibvirtVMDef.WatchDogDef.WatchDogModel.I6300ESB;
 +        LibvirtVMDef.WatchDogDef.WatchDogAction action = LibvirtVMDef.WatchDogDef.WatchDogAction.RESET;
 +
 +        LibvirtVMDef.WatchDogDef def = new LibvirtVMDef.WatchDogDef(action, model);
 +        assertEquals(def.getModel(), model);
 +        assertEquals(def.getAction(), action);
 +    }
 +
 +    public void testSCSIDef() {
 +        SCSIDef def = new SCSIDef((short)0, 0, 0, 9, 0, 4);
 +        String str = def.toString();
 +        String expected = "<controller type='scsi' index='0' model='virtio-scsi'>\n" +
 +                "<address type='pci' domain='0x0000' bus='0x00' slot='0x09' function='0x0'/>\n" +
 +                "<driver queues='4'/>\n" +
 +                "</controller>\n";
 +        assertEquals(str, expected);
 +    }
 +
 +    public void testMetadataDef() {
 +        LibvirtVMDef.MetadataDef metadataDef = new LibvirtVMDef.MetadataDef();
 +
 +        metadataDef.getMetadataNode(LibvirtVMDef.NuageExtensionDef.class).addNuageExtension("mac1", "ip1");
 +        metadataDef.getMetadataNode(LibvirtVMDef.NuageExtensionDef.class).addNuageExtension("mac2", "ip2");
 +
 +        String xmlDef = metadataDef.toString();
 +        String expectedXml = "<metadata>\n" +
 +                "<nuage-extension xmlns='nuagenetworks.net/nuage/cna'>\n" +
 +                "  <interface mac='mac2' vsp-vr-ip='ip2'></interface>\n" +
 +                "  <interface mac='mac1' vsp-vr-ip='ip1'></interface>\n" +
 +                "</nuage-extension>\n" +
 +                "</metadata>\n";
 +
 +        assertEquals(xmlDef, expectedXml);
 +    }
 +
 +}
diff --cc server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
index 23b56e9,0000000..b2a395b
mode 100644,000000..100644
--- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
+++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
@@@ -1,3375 -1,0 +1,3382 @@@
 +// Licensed to the Apache Software Foundation (ASF) under one
 +// or more contributor license agreements.  See the NOTICE file
 +// distributed with this work for additional information
 +// regarding copyright ownership.  The ASF licenses this file
 +// to you under the Apache License, Version 2.0 (the
 +// "License"); you may not use this file except in compliance
 +// with the License.  You may obtain a copy of the License at
 +//
 +//   http://www.apache.org/licenses/LICENSE-2.0
 +//
 +// Unless required by applicable law or agreed to in writing,
 +// software distributed under the License is distributed on an
 +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 +// KIND, either express or implied.  See the License for the
 +// specific language governing permissions and limitations
 +// under the License.
 +package com.cloud.storage;
 +
 +import java.net.MalformedURLException;
 +import java.net.URL;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collections;
 +import java.util.Date;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.UUID;
 +import java.util.concurrent.ExecutionException;
 +
 +import javax.inject.Inject;
 +
 +import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
 +import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
 +import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
 +import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd;
 +import org.apache.cloudstack.api.command.user.volume.GetUploadParamsForVolumeCmd;
 +import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
 +import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
 +import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
 +import org.apache.cloudstack.api.response.GetUploadParamsResponse;
 +import org.apache.cloudstack.context.CallContext;
 +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
 +import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
 +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
 +import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
 +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult;
 +import org.apache.cloudstack.framework.async.AsyncCallFuture;
 +import org.apache.cloudstack.framework.config.ConfigKey;
 +import org.apache.cloudstack.framework.config.Configurable;
 +import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 +import org.apache.cloudstack.framework.jobs.AsyncJob;
 +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext;
 +import org.apache.cloudstack.framework.jobs.AsyncJobManager;
 +import org.apache.cloudstack.framework.jobs.Outcome;
 +import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao;
 +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
 +import org.apache.cloudstack.framework.jobs.impl.OutcomeImpl;
 +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO;
 +import org.apache.cloudstack.jobs.JobInfo;
 +import org.apache.cloudstack.storage.command.AttachAnswer;
 +import org.apache.cloudstack.storage.command.AttachCommand;
 +import org.apache.cloudstack.storage.command.DettachCommand;
 +import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
 +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
 +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
 +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
 +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
 +import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
 +import org.apache.cloudstack.utils.identity.ManagementServerNode;
 +import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
 +import org.apache.cloudstack.utils.volume.VirtualMachineDiskInfo;
 +import org.apache.commons.collections.CollectionUtils;
 +import org.apache.log4j.Logger;
 +import org.joda.time.DateTime;
 +import org.joda.time.DateTimeZone;
 +
 +import com.cloud.agent.AgentManager;
 +import com.cloud.agent.api.Answer;
 +import com.cloud.agent.api.ModifyTargetsCommand;
 +import com.cloud.agent.api.to.DataTO;
 +import com.cloud.agent.api.to.DiskTO;
 +import com.cloud.api.ApiDBUtils;
 +import com.cloud.configuration.Config;
 +import com.cloud.configuration.ConfigurationManager;
 +import com.cloud.configuration.Resource.ResourceType;
 +import com.cloud.dc.ClusterDetailsDao;
 +import com.cloud.dc.DataCenter;
 +import com.cloud.dc.DataCenterVO;
 +import com.cloud.dc.dao.DataCenterDao;
 +import com.cloud.domain.Domain;
 +import com.cloud.event.ActionEvent;
 +import com.cloud.event.EventTypes;
 +import com.cloud.event.UsageEventUtils;
 +import com.cloud.exception.ConcurrentOperationException;
 +import com.cloud.exception.InvalidParameterValueException;
 +import com.cloud.exception.PermissionDeniedException;
 +import com.cloud.exception.ResourceAllocationException;
 +import com.cloud.exception.StorageUnavailableException;
 +import com.cloud.gpu.GPU;
 +import com.cloud.host.HostVO;
 +import com.cloud.host.dao.HostDao;
 +import com.cloud.hypervisor.Hypervisor.HypervisorType;
 +import com.cloud.hypervisor.HypervisorCapabilitiesVO;
 +import com.cloud.hypervisor.dao.HypervisorCapabilitiesDao;
 +import com.cloud.org.Grouping;
 +import com.cloud.serializer.GsonHelper;
 +import com.cloud.service.dao.ServiceOfferingDetailsDao;
 +import com.cloud.storage.Storage.ImageFormat;
 +import com.cloud.storage.dao.DiskOfferingDao;
 +import com.cloud.storage.dao.SnapshotDao;
 +import com.cloud.storage.dao.VMTemplateDao;
 +import com.cloud.storage.dao.VolumeDao;
 +import com.cloud.storage.snapshot.SnapshotApiService;
 +import com.cloud.storage.snapshot.SnapshotManager;
 +import com.cloud.template.TemplateManager;
 +import com.cloud.user.Account;
 +import com.cloud.user.AccountManager;
 +import com.cloud.user.ResourceLimitService;
 +import com.cloud.user.User;
 +import com.cloud.user.VmDiskStatisticsVO;
 +import com.cloud.user.dao.AccountDao;
 +import com.cloud.user.dao.VmDiskStatisticsDao;
 +import com.cloud.utils.DateUtil;
 +import com.cloud.utils.EncryptionUtil;
 +import com.cloud.utils.EnumUtils;
 +import com.cloud.utils.NumbersUtil;
 +import com.cloud.utils.Pair;
 +import com.cloud.utils.Predicate;
 +import com.cloud.utils.ReflectionUse;
 +import com.cloud.utils.StringUtils;
 +import com.cloud.utils.UriUtils;
 +import com.cloud.utils.component.ManagerBase;
 +import com.cloud.utils.db.DB;
 +import com.cloud.utils.db.EntityManager;
 +import com.cloud.utils.db.Transaction;
 +import com.cloud.utils.db.TransactionCallback;
 +import com.cloud.utils.db.TransactionCallbackWithException;
 +import com.cloud.utils.db.TransactionStatus;
 +import com.cloud.utils.db.UUIDManager;
 +import com.cloud.utils.exception.CloudRuntimeException;
 +import com.cloud.utils.fsm.NoTransitionException;
 +import com.cloud.utils.fsm.StateMachine2;
 +import com.cloud.vm.UserVmManager;
 +import com.cloud.vm.UserVmService;
 +import com.cloud.vm.UserVmVO;
 +import com.cloud.vm.VMInstanceVO;
 +import com.cloud.vm.VirtualMachine;
 +import com.cloud.vm.VirtualMachine.State;
 +import com.cloud.vm.VmDetailConstants;
 +import com.cloud.vm.VmWork;
 +import com.cloud.vm.VmWorkAttachVolume;
 +import com.cloud.vm.VmWorkConstants;
 +import com.cloud.vm.VmWorkDetachVolume;
 +import com.cloud.vm.VmWorkExtractVolume;
 +import com.cloud.vm.VmWorkJobHandler;
 +import com.cloud.vm.VmWorkJobHandlerProxy;
 +import com.cloud.vm.VmWorkMigrateVolume;
 +import com.cloud.vm.VmWorkResizeVolume;
 +import com.cloud.vm.VmWorkSerializer;
 +import com.cloud.vm.VmWorkTakeVolumeSnapshot;
 +import com.cloud.vm.dao.UserVmDao;
 +import com.cloud.vm.dao.VMInstanceDao;
 +import com.cloud.vm.snapshot.VMSnapshotVO;
 +import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 +import com.google.common.base.Strings;
 +import com.google.gson.Gson;
 +import com.google.gson.GsonBuilder;
 +import com.google.gson.JsonParseException;
 +
 +public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiService, VmWorkJobHandler, Configurable {
 +    private final static Logger s_logger = Logger.getLogger(VolumeApiServiceImpl.class);
 +    public static final String VM_WORK_JOB_HANDLER = VolumeApiServiceImpl.class.getSimpleName();
 +
 +    @Inject
 +    private UserVmManager _userVmMgr;
 +    @Inject
 +    private VolumeOrchestrationService _volumeMgr;
 +    @Inject
 +    private EntityManager _entityMgr;
 +    @Inject
 +    private AgentManager _agentMgr;
 +    @Inject
 +    private TemplateManager _tmpltMgr;
 +    @Inject
 +    private SnapshotManager _snapshotMgr;
 +    @Inject
 +    private AccountManager _accountMgr;
 +    @Inject
 +    private ConfigurationManager _configMgr;
 +    @Inject
 +    private VolumeDao _volsDao;
 +    @Inject
 +    private HostDao _hostDao;
 +    @Inject
 +    private SnapshotDao _snapshotDao;
 +    @Inject
 +    private ServiceOfferingDetailsDao _serviceOfferingDetailsDao;
 +    @Inject
 +    private UserVmDao _userVmDao;
 +    @Inject
 +    private UserVmService _userVmService;
 +    @Inject
 +    private VolumeDataStoreDao _volumeStoreDao;
 +    @Inject
 +    private VMInstanceDao _vmInstanceDao;
 +    @Inject
 +    private PrimaryDataStoreDao _storagePoolDao;
 +    @Inject
 +    private DiskOfferingDao _diskOfferingDao;
 +    @Inject
 +    private AccountDao _accountDao;
 +    @Inject
 +    private DataCenterDao _dcDao;
 +    @Inject
 +    private VMTemplateDao _templateDao;
 +    @Inject
 +    private ResourceLimitService _resourceLimitMgr;
 +    @Inject
 +    private VmDiskStatisticsDao _vmDiskStatsDao;
 +    @Inject
 +    private VMSnapshotDao _vmSnapshotDao;
 +    @Inject
 +    private ConfigurationDao _configDao;
 +    @Inject
 +    private DataStoreManager dataStoreMgr;
 +    @Inject
 +    private VolumeService volService;
 +    @Inject
 +    private VolumeDataFactory volFactory;
 +    @Inject
 +    private SnapshotApiService snapshotMgr;
 +    @Inject
 +    private UUIDManager _uuidMgr;
 +    @Inject
 +    private HypervisorCapabilitiesDao _hypervisorCapabilitiesDao;
 +    @Inject
 +    private AsyncJobManager _jobMgr;
 +    @Inject
 +    private VmWorkJobDao _workJobDao;
 +    @Inject
 +    private ClusterDetailsDao _clusterDetailsDao;
 +    @Inject
 +    private StorageManager storageMgr;
 +    @Inject
 +    private StoragePoolDetailsDao storagePoolDetailsDao;
 +    @Inject
 +    private StorageUtil storageUtil;
 +
 +    protected Gson _gson;
 +
 +    private List<StoragePoolAllocator> _storagePoolAllocators;
 +
 +    VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this);
 +
 +    static final ConfigKey<Long> VmJobCheckInterval = new ConfigKey<Long>("Advanced", Long.class, "vm.job.check.interval", "3000", "Interval in milliseconds to check if the job is complete", false);
 +
 +    static final ConfigKey<Boolean> VolumeUrlCheck = new ConfigKey<Boolean>("Advanced", Boolean.class, "volume.url.check", "true",
 +            "Check the url for a volume before downloading it from the management server. Set to false when you managment has no internet access.", true);
 +
 +    private long _maxVolumeSizeInGb;
 +    private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine;
 +
 +    protected VolumeApiServiceImpl() {
 +        _volStateMachine = Volume.State.getStateMachine();
 +        _gson = GsonHelper.getGsonLogger();
 +    }
 +
 +    /*
 +     * Upload the volume to secondary storage.
 +     */
 +    @Override
 +    @DB
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_UPLOAD, eventDescription = "uploading volume", async = true)
 +    public VolumeVO uploadVolume(UploadVolumeCmd cmd) throws ResourceAllocationException {
 +        Account caller = CallContext.current().getCallingAccount();
 +        long ownerId = cmd.getEntityOwnerId();
 +        Account owner = _entityMgr.findById(Account.class, ownerId);
 +        Long zoneId = cmd.getZoneId();
 +        String volumeName = cmd.getVolumeName();
 +        String url = cmd.getUrl();
 +        String format = cmd.getFormat();
 +        Long diskOfferingId = cmd.getDiskOfferingId();
 +        String imageStoreUuid = cmd.getImageStoreUuid();
 +        DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId);
 +
 +        validateVolume(caller, ownerId, zoneId, volumeName, url, format, diskOfferingId);
 +
 +        VolumeVO volume = persistVolume(owner, zoneId, volumeName, url, cmd.getFormat(), diskOfferingId, Volume.State.Allocated);
 +
 +        VolumeInfo vol = volFactory.getVolume(volume.getId());
 +
 +        RegisterVolumePayload payload = new RegisterVolumePayload(cmd.getUrl(), cmd.getChecksum(), cmd.getFormat());
 +        vol.addPayload(payload);
 +
 +        volService.registerVolume(vol, store);
 +        return volume;
 +    }
 +
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_UPLOAD, eventDescription = "uploading volume for post upload", async = true)
 +    public GetUploadParamsResponse uploadVolume(final GetUploadParamsForVolumeCmd cmd) throws ResourceAllocationException, MalformedURLException {
 +        Account caller = CallContext.current().getCallingAccount();
 +        long ownerId = cmd.getEntityOwnerId();
 +        final Account owner = _entityMgr.findById(Account.class, ownerId);
 +        final Long zoneId = cmd.getZoneId();
 +        final String volumeName = cmd.getName();
 +        String format = cmd.getFormat();
 +        final Long diskOfferingId = cmd.getDiskOfferingId();
 +        String imageStoreUuid = cmd.getImageStoreUuid();
 +        final DataStore store = _tmpltMgr.getImageStore(imageStoreUuid, zoneId);
 +
 +        validateVolume(caller, ownerId, zoneId, volumeName, null, format, diskOfferingId);
 +
 +        return Transaction.execute(new TransactionCallbackWithException<GetUploadParamsResponse, MalformedURLException>() {
 +            @Override
 +            public GetUploadParamsResponse doInTransaction(TransactionStatus status) throws MalformedURLException {
 +
 +                VolumeVO volume = persistVolume(owner, zoneId, volumeName, null, cmd.getFormat(), diskOfferingId, Volume.State.NotUploaded);
 +
 +                VolumeInfo vol = volFactory.getVolume(volume.getId());
 +
 +                RegisterVolumePayload payload = new RegisterVolumePayload(null, cmd.getChecksum(), cmd.getFormat());
 +                vol.addPayload(payload);
 +
 +                Pair<EndPoint, DataObject> pair = volService.registerVolumeForPostUpload(vol, store);
 +                EndPoint ep = pair.first();
 +                DataObject dataObject = pair.second();
 +
 +                GetUploadParamsResponse response = new GetUploadParamsResponse();
 +
 +                String ssvmUrlDomain = _configDao.getValue(Config.SecStorageSecureCopyCert.key());
 +
 +                String url = ImageStoreUtil.generatePostUploadUrl(ssvmUrlDomain, ep.getPublicAddr(), vol.getUuid());
 +                response.setPostURL(new URL(url));
 +
 +                // set the post url, this is used in the monitoring thread to determine the SSVM
 +                VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(vol.getId());
 +                assert (volumeStore != null) : "sincle volume is registered, volumestore cannot be null at this stage";
 +                volumeStore.setExtractUrl(url);
 +                _volumeStoreDao.persist(volumeStore);
 +
 +                response.setId(UUID.fromString(vol.getUuid()));
 +
 +                int timeout = ImageStoreUploadMonitorImpl.getUploadOperationTimeout();
 +                DateTime currentDateTime = new DateTime(DateTimeZone.UTC);
 +                String expires = currentDateTime.plusMinutes(timeout).toString();
 +                response.setTimeout(expires);
 +
 +                String key = _configDao.getValue(Config.SSVMPSK.key());
 +                /*
 +                 * encoded metadata using the post upload config key
 +                 */
 +                TemplateOrVolumePostUploadCommand command = new TemplateOrVolumePostUploadCommand(vol.getId(), vol.getUuid(), volumeStore.getInstallPath(), cmd.getChecksum(), vol.getType().toString(),
 +                        vol.getName(), vol.getFormat().toString(), dataObject.getDataStore().getUri(), dataObject.getDataStore().getRole().toString());
 +                command.setLocalPath(volumeStore.getLocalDownloadPath());
 +                //using the existing max upload size configuration
 +                command.setProcessTimeout(NumbersUtil.parseLong(_configDao.getValue("vmware.package.ova.timeout"), 3600));
 +                command.setMaxUploadSize(_configDao.getValue(Config.MaxUploadVolumeSize.key()));
 +                command.setDefaultMaxAccountSecondaryStorage(_configDao.getValue(Config.DefaultMaxAccountSecondaryStorage.key()));
 +                command.setAccountId(vol.getAccountId());
 +                Gson gson = new GsonBuilder().create();
 +                String metadata = EncryptionUtil.encodeData(gson.toJson(command), key);
 +                response.setMetadata(metadata);
 +
 +                /*
 +                 * signature calculated on the url, expiry, metadata.
 +                 */
 +                response.setSignature(EncryptionUtil.generateSignature(metadata + url + expires, key));
 +                return response;
 +            }
 +        });
 +    }
 +
 +    private boolean validateVolume(Account caller, long ownerId, Long zoneId, String volumeName, String url, String format, Long diskOfferingId) throws ResourceAllocationException {
 +
 +        // permission check
 +        Account volumeOwner = _accountMgr.getActiveAccountById(ownerId);
 +        _accountMgr.checkAccess(caller, null, true, volumeOwner);
 +
 +        // Check that the resource limit for volumes won't be exceeded
 +        _resourceLimitMgr.checkResourceLimit(volumeOwner, ResourceType.volume);
 +
 +        // Verify that zone exists
 +        DataCenterVO zone = _dcDao.findById(zoneId);
 +        if (zone == null) {
 +            throw new InvalidParameterValueException("Unable to find zone by id " + zoneId);
 +        }
 +
 +        // Check if zone is disabled
 +        if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
 +            throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId);
 +        }
 +
 +        //validating the url only when url is not null. url can be null incase of form based post upload
 +        if (url != null) {
 +            if (url.toLowerCase().contains("file://")) {
 +                throw new InvalidParameterValueException("File:// type urls are currently unsupported");
 +            }
 +            UriUtils.validateUrl(format, url);
 +            if (VolumeUrlCheck.value()) { // global setting that can be set when their MS does not have internet access
 +                s_logger.debug("Checking url: " + url);
 +                UriUtils.checkUrlExistence(url);
 +            }
 +            // Check that the resource limit for secondary storage won't be exceeded
 +            _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.secondary_storage, UriUtils.getRemoteSize(url));
 +        } else {
 +            _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.secondary_storage);
 +        }
 +
 +        try {
 +            ImageFormat.valueOf(format.toUpperCase());
 +        } catch (IllegalArgumentException e) {
 +            s_logger.debug("ImageFormat IllegalArgumentException: " + e.getMessage());
 +            throw new IllegalArgumentException("Image format: " + format + " is incorrect. Supported formats are " + EnumUtils.listValues(ImageFormat.values()));
 +        }
 +
 +        // Check that the the disk offering specified is valid
 +        if (diskOfferingId != null) {
 +            DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId);
 +            if ((diskOffering == null) || diskOffering.getRemoved() != null || !DiskOfferingVO.Type.Disk.equals(diskOffering.getType())) {
 +                throw new InvalidParameterValueException("Please specify a valid disk offering.");
 +            }
 +            if (!diskOffering.isCustomized()) {
 +                throw new InvalidParameterValueException("Please specify a custom sized disk offering.");
 +            }
 +
 +            if (diskOffering.getDomainId() == null) {
 +                // do nothing as offering is public
 +            } else {
 +                _configMgr.checkDiskOfferingAccess(volumeOwner, diskOffering);
 +            }
 +        }
 +
 +        return false;
 +    }
 +
 +    public String getRandomVolumeName() {
 +        return UUID.randomUUID().toString();
 +    }
 +
 +    @DB
 +    protected VolumeVO persistVolume(final Account owner, final Long zoneId, final String volumeName, final String url, final String format, final Long diskOfferingId, final Volume.State state) {
 +        return Transaction.execute(new TransactionCallback<VolumeVO>() {
 +            @Override
 +            public VolumeVO doInTransaction(TransactionStatus status) {
 +                VolumeVO volume = new VolumeVO(volumeName, zoneId, -1, -1, -1, new Long(-1), null, null, Storage.ProvisioningType.THIN, 0, Volume.Type.DATADISK);
 +                volume.setPoolId(null);
 +                volume.setDataCenterId(zoneId);
 +                volume.setPodId(null);
 +                volume.setState(state); // initialize the state
 +                // to prevent a null pointer deref I put the system account id here when no owner is given.
 +                // TODO Decide if this is valid or whether  throwing a CloudRuntimeException is more appropriate
 +                volume.setAccountId((owner == null) ? Account.ACCOUNT_ID_SYSTEM : owner.getAccountId());
 +                volume.setDomainId((owner == null) ? Domain.ROOT_DOMAIN : owner.getDomainId());
 +
 +                if (diskOfferingId == null) {
 +                    DiskOfferingVO diskOfferingVO = _diskOfferingDao.findByUniqueName("Cloud.com-Custom");
 +                    if (diskOfferingVO != null) {
 +                        long defaultDiskOfferingId = diskOfferingVO.getId();
 +                        volume.setDiskOfferingId(defaultDiskOfferingId);
 +                    }
 +                } else {
 +                    volume.setDiskOfferingId(diskOfferingId);
 +
 +                    DiskOfferingVO diskOfferingVO = _diskOfferingDao.findById(diskOfferingId);
 +
 +                    Boolean isCustomizedIops = diskOfferingVO != null && diskOfferingVO.isCustomizedIops() != null ? diskOfferingVO.isCustomizedIops() : false;
 +
 +                    if (isCustomizedIops == null || !isCustomizedIops) {
 +                        volume.setMinIops(diskOfferingVO.getMinIops());
 +                        volume.setMaxIops(diskOfferingVO.getMaxIops());
 +                    }
 +                }
 +
 +                // volume.setSize(size);
 +                volume.setInstanceId(null);
 +                volume.setUpdated(new Date());
 +                volume.setDomainId((owner == null) ? Domain.ROOT_DOMAIN : owner.getDomainId());
 +                volume.setFormat(ImageFormat.valueOf(format));
 +                volume = _volsDao.persist(volume);
 +                CallContext.current().setEventDetails("Volume Id: " + volume.getUuid());
 +
 +                // Increment resource count during allocation; if actual creation fails,
 +                // decrement it
 +                _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.volume);
 +                //url can be null incase of postupload
 +                if (url != null) {
 +                    _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.secondary_storage, UriUtils.getRemoteSize(url));
 +                }
 +
 +                return volume;
 +            }
 +        });
 +    }
 +
 +    /**
 +     * Retrieves the volume name from CreateVolumeCmd object.
 +     *
 +     * If the retrieved volume name is null, empty or blank, then A random name
 +     * will be generated using getRandomVolumeName method.
 +     *
 +     * @param cmd
 +     * @return Either the retrieved name or a random name.
 +     */
 +    public String getVolumeNameFromCommand(CreateVolumeCmd cmd) {
 +        String userSpecifiedName = cmd.getVolumeName();
 +
 +        if (org.apache.commons.lang.StringUtils.isBlank(userSpecifiedName)) {
 +            userSpecifiedName = getRandomVolumeName();
 +        }
 +
 +        return userSpecifiedName;
 +    }
 +
 +    /*
 +     * Just allocate a volume in the database, don't send the createvolume cmd
 +     * to hypervisor. The volume will be finally created only when it's attached
 +     * to a VM.
 +     */
 +    @Override
 +    @DB
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", create = true)
 +    public VolumeVO allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationException {
 +        Account caller = CallContext.current().getCallingAccount();
 +
 +        long ownerId = cmd.getEntityOwnerId();
 +        Account owner = _accountMgr.getActiveAccountById(ownerId);
 +        Boolean displayVolume = cmd.getDisplayVolume();
 +
 +        // permission check
 +        _accountMgr.checkAccess(caller, null, true, _accountMgr.getActiveAccountById(ownerId));
 +
 +        if (displayVolume == null) {
 +            displayVolume = true;
 +        } else {
 +            if (!_accountMgr.isRootAdmin(caller.getId())) {
 +                throw new PermissionDeniedException("Cannot update parameter displayvolume, only admin permitted ");
 +            }
 +        }
 +
 +        // Check that the resource limit for volumes won't be exceeded
 +        _resourceLimitMgr.checkResourceLimit(owner, ResourceType.volume, displayVolume);
 +
 +        Long zoneId = cmd.getZoneId();
 +        Long diskOfferingId = null;
 +        DiskOfferingVO diskOffering = null;
 +        Storage.ProvisioningType provisioningType;
 +        Long size = null;
 +        Long minIops = null;
 +        Long maxIops = null;
 +        // Volume VO used for extracting the source template id
 +        VolumeVO parentVolume = null;
 +
 +        // validate input parameters before creating the volume
 +        if ((cmd.getSnapshotId() == null && cmd.getDiskOfferingId() == null) || (cmd.getSnapshotId() != null && cmd.getDiskOfferingId() != null)) {
 +            throw new InvalidParameterValueException("Either disk Offering Id or snapshot Id must be passed whilst creating volume");
 +        }
 +
 +        if (cmd.getSnapshotId() == null) {// create a new volume
 +
 +            diskOfferingId = cmd.getDiskOfferingId();
 +            size = cmd.getSize();
 +            Long sizeInGB = size;
 +            if (size != null) {
 +                if (size > 0) {
 +                    size = size * 1024 * 1024 * 1024; // user specify size in GB
 +                } else {
 +                    throw new InvalidParameterValueException("Disk size must be larger than 0");
 +                }
 +            }
 +
 +            // Check that the the disk offering is specified
 +            diskOffering = _diskOfferingDao.findById(diskOfferingId);
 +            if ((diskOffering == null) || diskOffering.getRemoved() != null || !DiskOfferingVO.Type.Disk.equals(diskOffering.getType())) {
 +                throw new InvalidParameterValueException("Please specify a valid disk offering.");
 +            }
 +
 +            if (diskOffering.isCustomized()) {
 +                if (size == null) {
 +                    throw new InvalidParameterValueException("This disk offering requires a custom size specified");
 +                }
 +                Long customDiskOfferingMaxSize = VolumeOrchestrationService.CustomDiskOfferingMaxSize.value();
 +                Long customDiskOfferingMinSize = VolumeOrchestrationService.CustomDiskOfferingMinSize.value();
 +
 +                if ((sizeInGB < customDiskOfferingMinSize) || (sizeInGB > customDiskOfferingMaxSize)) {
 +                    throw new InvalidParameterValueException("Volume size: " + sizeInGB + "GB is out of allowed range. Max: " + customDiskOfferingMaxSize + " Min:" + customDiskOfferingMinSize);
 +                }
 +            }
 +
 +            if (!diskOffering.isCustomized() && size != null) {
 +                throw new InvalidParameterValueException("This disk offering does not allow custom size");
 +            }
 +
 +            if (diskOffering.getDomainId() == null) {
 +                // do nothing as offering is public
 +            } else {
 +                _configMgr.checkDiskOfferingAccess(caller, diskOffering);
 +            }
 +
 +            if (diskOffering.getDiskSize() > 0) {
 +                size = diskOffering.getDiskSize();
 +            }
 +
 +            Boolean isCustomizedIops = diskOffering.isCustomizedIops();
 +
 +            if (isCustomizedIops != null) {
 +                if (isCustomizedIops) {
 +                    minIops = cmd.getMinIops();
 +                    maxIops = cmd.getMaxIops();
 +
 +                    if (minIops == null && maxIops == null) {
 +                        minIops = 0L;
 +                        maxIops = 0L;
 +                    } else {
 +                        if (minIops == null || minIops <= 0) {
 +                            throw new InvalidParameterValueException("The min IOPS must be greater than 0.");
 +                        }
 +
 +                        if (maxIops == null) {
 +                            maxIops = 0L;
 +                        }
 +
 +                        if (minIops > maxIops) {
 +                            throw new InvalidParameterValueException("The min IOPS must be less than or equal to the max IOPS.");
 +                        }
 +                    }
 +                } else {
 +                    minIops = diskOffering.getMinIops();
 +                    maxIops = diskOffering.getMaxIops();
 +                }
 +            }
 +
 +            provisioningType = diskOffering.getProvisioningType();
 +
 +            if (!validateVolumeSizeRange(size)) {// convert size from mb to gb
 +                // for validation
 +                throw new InvalidParameterValueException("Invalid size for custom volume creation: " + size + " ,max volume size is:" + _maxVolumeSizeInGb);
 +            }
 +        } else { // create volume from snapshot
 +            Long snapshotId = cmd.getSnapshotId();
 +            SnapshotVO snapshotCheck = _snapshotDao.findById(snapshotId);
 +            if (snapshotCheck == null) {
 +                throw new InvalidParameterValueException("unable to find a snapshot with id " + snapshotId);
 +            }
 +
 +            if (snapshotCheck.getState() != Snapshot.State.BackedUp) {
 +                throw new InvalidParameterValueException("Snapshot id=" + snapshotId + " is not in " + Snapshot.State.BackedUp + " state yet and can't be used for volume creation");
 +            }
 +            parentVolume = _volsDao.findByIdIncludingRemoved(snapshotCheck.getVolumeId());
 +
 +            diskOfferingId = snapshotCheck.getDiskOfferingId();
 +            diskOffering = _diskOfferingDao.findById(diskOfferingId);
 +            if (zoneId == null) {
 +                // if zoneId is not provided, we default to create volume in the same zone as the snapshot zone.
 +                zoneId = snapshotCheck.getDataCenterId();
 +            }
 +            size = snapshotCheck.getSize(); // ; disk offering is used for tags
 +            // purposes
 +
 +            minIops = snapshotCheck.getMinIops();
 +            maxIops = snapshotCheck.getMaxIops();
 +
 +            provisioningType = diskOffering.getProvisioningType();
 +            // check snapshot permissions
 +            _accountMgr.checkAccess(caller, null, true, snapshotCheck);
 +
 +            // one step operation - create volume in VM's cluster and attach it
 +            // to the VM
 +            Long vmId = cmd.getVirtualMachineId();
 +            if (vmId != null) {
 +                // Check that the virtual machine ID is valid and it's a user vm
 +                UserVmVO vm = _userVmDao.findById(vmId);
 +                if (vm == null || vm.getType() != VirtualMachine.Type.User) {
 +                    throw new InvalidParameterValueException("Please specify a valid User VM.");
 +                }
 +
 +                // Check that the VM is in the correct state
 +                if (vm.getState() != State.Running && vm.getState() != State.Stopped) {
 +                    throw new InvalidParameterValueException("Please specify a VM that is either running or stopped.");
 +                }
 +
 +                // permission check
 +                _accountMgr.checkAccess(caller, null, false, vm);
 +            }
 +
 +        }
 +
 +        // Check that the resource limit for primary storage won't be exceeded
 +        _resourceLimitMgr.checkResourceLimit(owner, ResourceType.primary_storage, displayVolume, new Long(size));
 +
 +        // Verify that zone exists
 +        DataCenterVO zone = _dcDao.findById(zoneId);
 +        if (zone == null) {
 +            throw new InvalidParameterValueException("Unable to find zone by id " + zoneId);
 +        }
 +
 +        // Check if zone is disabled
 +        if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
 +            throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId);
 +        }
 +
 +        // If local storage is disabled then creation of volume with local disk
 +        // offering not allowed
 +        if (!zone.isLocalStorageEnabled() && diskOffering.isUseLocalStorage()) {
 +            throw new InvalidParameterValueException("Zone is not configured to use local storage but volume's disk offering " + diskOffering.getName() + " uses it");
 +        }
 +
 +        String userSpecifiedName = getVolumeNameFromCommand(cmd);
 +
 +        VolumeVO volume = commitVolume(cmd, caller, owner, displayVolume, zoneId, diskOfferingId, provisioningType, size, minIops, maxIops, parentVolume, userSpecifiedName,
 +                _uuidMgr.generateUuid(Volume.class, cmd.getCustomId()));
 +
 +        return volume;
 +    }
 +
 +    private VolumeVO commitVolume(final CreateVolumeCmd cmd, final Account caller, final Account owner, final Boolean displayVolume, final Long zoneId, final Long diskOfferingId,
 +            final Storage.ProvisioningType provisioningType, final Long size, final Long minIops, final Long maxIops, final VolumeVO parentVolume, final String userSpecifiedName, final String uuid) {
 +        return Transaction.execute(new TransactionCallback<VolumeVO>() {
 +            @Override
 +            public VolumeVO doInTransaction(TransactionStatus status) {
 +                VolumeVO volume = new VolumeVO(userSpecifiedName, -1, -1, -1, -1, new Long(-1), null, null, provisioningType, 0, Volume.Type.DATADISK);
 +                volume.setPoolId(null);
 +                volume.setUuid(uuid);
 +                volume.setDataCenterId(zoneId);
 +                volume.setPodId(null);
 +                volume.setAccountId(owner.getId());
 +                volume.setDomainId(owner.getDomainId());
 +                volume.setDiskOfferingId(diskOfferingId);
 +                volume.setSize(size);
 +                volume.setMinIops(minIops);
 +                volume.setMaxIops(maxIops);
 +                volume.setInstanceId(null);
 +                volume.setUpdated(new Date());
 +                volume.setDisplayVolume(displayVolume);
 +                if (parentVolume != null) {
 +                    volume.setTemplateId(parentVolume.getTemplateId());
 +                    volume.setFormat(parentVolume.getFormat());
 +                } else {
 +                    volume.setTemplateId(null);
 +                }
 +
 +                volume = _volsDao.persist(volume);
 +                if (cmd.getSnapshotId() == null && displayVolume) {
 +                    // for volume created from snapshot, create usage event after volume creation
 +                    UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), diskOfferingId, null, size,
 +                            Volume.class.getName(), volume.getUuid(), displayVolume);
 +                }
 +
 +                CallContext.current().setEventDetails("Volume Id: " + volume.getUuid());
 +
 +                // Increment resource count during allocation; if actual creation fails,
 +                // decrement it
 +                _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.volume, displayVolume);
 +                _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, displayVolume, new Long(volume.getSize()));
 +                return volume;
 +            }
 +        });
 +    }
 +
 +    public boolean validateVolumeSizeRange(long size) {
 +        if (size < 0 || (size > 0 && size < (1024 * 1024 * 1024))) {
 +            throw new InvalidParameterValueException("Please specify a size of at least 1 GB.");
 +        } else if (size > (_maxVolumeSizeInGb * 1024 * 1024 * 1024)) {
 +            throw new InvalidParameterValueException("Requested volume size is " + size + ", but the maximum size allowed is " + _maxVolumeSizeInGb + " GB.");
 +        }
 +
 +        return true;
 +    }
 +
 +    @Override
 +    @DB
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", async = true)
 +    public VolumeVO createVolume(CreateVolumeCmd cmd) {
 +        VolumeVO volume = _volsDao.findById(cmd.getEntityId());
 +        boolean created = true;
 +
 +        try {
 +            if (cmd.getSnapshotId() != null) {
 +                volume = createVolumeFromSnapshot(volume, cmd.getSnapshotId(), cmd.getVirtualMachineId());
 +                if (volume.getState() != Volume.State.Ready) {
 +                    created = false;
 +                }
 +
 +                // if VM Id is provided, attach the volume to the VM
 +                if (cmd.getVirtualMachineId() != null) {
 +                    try {
 +                        attachVolumeToVM(cmd.getVirtualMachineId(), volume.getId(), volume.getDeviceId());
 +                    } catch (Exception ex) {
 +                        StringBuilder message = new StringBuilder("Volume: ");
 +                        message.append(volume.getUuid());
 +                        message.append(" created successfully, but failed to attach the newly created volume to VM: ");
 +                        message.append(cmd.getVirtualMachineId());
 +                        message.append(" due to error: ");
 +                        message.append(ex.getMessage());
 +                        if (s_logger.isDebugEnabled()) {
 +                            s_logger.debug(message, ex);
 +                        }
 +                        throw new CloudRuntimeException(message.toString());
 +                    }
 +                }
 +            }
 +            return volume;
 +        } catch (Exception e) {
 +            created = false;
 +            VolumeInfo vol = volFactory.getVolume(cmd.getEntityId());
 +            vol.stateTransit(Volume.Event.DestroyRequested);
 +            throw new CloudRuntimeException("Failed to create volume: " + volume.getId(), e);
 +        } finally {
 +            if (!created) {
 +                s_logger.trace("Decrementing volume resource count for account id=" + volume.getAccountId() + " as volume failed to create on the backend");
 +                _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume, cmd.getDisplayVolume());
 +                _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, cmd.getDisplayVolume(), new Long(volume.getSize()));
 +            }
 +        }
 +    }
 +
 +    protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId, Long vmId) throws StorageUnavailableException {
 +        VolumeInfo createdVolume = null;
 +        SnapshotVO snapshot = _snapshotDao.findById(snapshotId);
 +        snapshot.getVolumeId();
 +
 +        UserVmVO vm = null;
 +        if (vmId != null) {
 +            vm = _userVmDao.findById(vmId);
 +        }
 +
 +        // sync old snapshots to region store if necessary
 +
 +        createdVolume = _volumeMgr.createVolumeFromSnapshot(volume, snapshot, vm);
 +        VolumeVO volumeVo = _volsDao.findById(createdVolume.getId());
 +        UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, createdVolume.getAccountId(), createdVolume.getDataCenterId(), createdVolume.getId(), createdVolume.getName(),
 +                createdVolume.getDiskOfferingId(), null, createdVolume.getSize(), Volume.class.getName(), createdVolume.getUuid(), volumeVo.isDisplayVolume());
 +
 +        return volumeVo;
 +    }
 +
 +    @Override
 +    @DB
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_RESIZE, eventDescription = "resizing volume", async = true)
 +    public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationException {
 +        Long newSize;
 +        Long newMinIops;
 +        Long newMaxIops;
 +        Integer newHypervisorSnapshotReserve;
 +        boolean shrinkOk = cmd.isShrinkOk();
 +
 +        VolumeVO volume = _volsDao.findById(cmd.getEntityId());
 +        if (volume == null) {
 +            throw new InvalidParameterValueException("No such volume");
 +        }
 +
 +        // checking if there are any ongoing snapshots on the volume which is to be resized
 +        List<SnapshotVO> ongoingSnapshots = _snapshotDao.listByStatus(cmd.getId(), Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp);
 +        if (ongoingSnapshots.size() > 0) {
 +            throw new CloudRuntimeException("There is/are unbacked up snapshot(s) on this volume, resize volume is not permitted, please try again later.");
 +        }
 +
 +        /* Does the caller have authority to act on this volume? */
 +        _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume);
 +
 +        DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
 +        DiskOfferingVO newDiskOffering = null;
 +
 +        if (cmd.getNewDiskOfferingId() != null && volume.getDiskOfferingId() != cmd.getNewDiskOfferingId()) {
 +            newDiskOffering = _diskOfferingDao.findById(cmd.getNewDiskOfferingId());
 +        }
 +
 +        /* Only works for KVM/XenServer/VMware (or "Any") for now, and volumes with 'None' since they're just allocated in DB */
 +
 +        HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId());
 +
 +        if (hypervisorType != HypervisorType.KVM && hypervisorType != HypervisorType.XenServer && hypervisorType != HypervisorType.VMware && hypervisorType != HypervisorType.Any
 +                && hypervisorType != HypervisorType.None) {
 +            throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support  rootdisksize override");
 +        }
 +
 +        if (volume.getState() != Volume.State.Ready && volume.getState() != Volume.State.Allocated) {
 +            throw new InvalidParameterValueException("Volume should be in ready or allocated state before attempting a resize. Volume " + volume.getUuid() + " is in state " + volume.getState() + ".");
 +        }
 +
 +        // if we are to use the existing disk offering
 +        if (newDiskOffering == null) {
 +            newSize = cmd.getSize();
 +            newHypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve();
 +
 +            // if the caller is looking to change the size of the volume
 +            if (newSize != null) {
 +                if (!diskOffering.isCustomized() && !volume.getVolumeType().equals(Volume.Type.ROOT)) {
 +                    throw new InvalidParameterValueException("To change a volume's size without providing a new disk offering, its current disk offering must be "
 +                            + "customizable or it must be a root volume (if providing a disk offering, make sure it is different from the current disk offering).");
 +                }
 +
 +                // convert from bytes to GiB
 +                newSize = newSize << 30;
 +            } else {
 +                // no parameter provided; just use the original size of the volume
 +                newSize = volume.getSize();
 +            }
 +
 +            newMinIops = cmd.getMinIops();
 +
 +            if (newMinIops != null) {
 +                if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
 +                    throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Min IOPS' parameter.");
 +                }
 +            } else {
 +                // no parameter provided; just use the original min IOPS of the volume
 +                newMinIops = volume.getMinIops();
 +            }
 +
 +            newMaxIops = cmd.getMaxIops();
 +
 +            if (newMaxIops != null) {
 +                if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) {
 +                    throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Max IOPS' parameter.");
 +                }
 +            } else {
 +                // no parameter provided; just use the original max IOPS of the volume
 +                newMaxIops = volume.getMaxIops();
 +            }
 +
 +            validateIops(newMinIops, newMaxIops);
 +        } else {
 +            if (newDiskOffering.getRemoved() != null) {
 +                throw new InvalidParameterValueException("Requested disk offering has been removed.");
 +            }
 +
 +            if (!DiskOfferingVO.Type.Disk.equals(newDiskOffering.getType())) {
 +                throw new InvalidParameterValueException("Requested disk offering type is invalid.");
 +            }
 +
 +            if (diskOffering.getTags() != null) {
 +                if (!StringUtils.areTagsEqual(diskOffering.getTags(), newDiskOffering.getTags())) {
 +                    throw new InvalidParameterValueException("The tags on the new and old disk offerings must match.");
 +                }
 +            } else if (newDiskOffering.getTags() != null) {
 +                throw new InvalidParameterValueException("There are no tags on the current disk offering. The new disk offering needs to have no tags, as well.");
 +            }
 +
 +            if (newDiskOffering.getDomainId() != null) {
 +                // not a public offering; check access
 +                _configMgr.checkDiskOfferingAccess(CallContext.current().getCallingAccount(), newDiskOffering);
 +            }
 +
 +            if (newDiskOffering.isCustomized()) {
 +                newSize = cmd.getSize();
 +
 +                if (newSize == null) {
 +                    throw new InvalidParameterValueException("The new disk offering requires that a size be specified.");
 +                }
 +
 +                // convert from GiB to bytes
 +                newSize = newSize << 30;
 +            } else {
 +                if (cmd.getSize() != null) {
 +                    throw new InvalidParameterValueException("You cannnot pass in a custom disk size to a non-custom disk offering.");
 +                }
 +
 +                newSize = newDiskOffering.getDiskSize();
 +            }
 +
 +            if (!volume.getSize().equals(newSize) && !volume.getVolumeType().equals(Volume.Type.DATADISK)) {
 +                throw new InvalidParameterValueException("Only data volumes can be resized via a new disk offering.");
 +            }
 +
 +            if (newDiskOffering.isCustomizedIops() != null && newDiskOffering.isCustomizedIops()) {
 +                newMinIops = cmd.getMinIops() != null ? cmd.getMinIops() : volume.getMinIops();
 +                newMaxIops = cmd.getMaxIops() != null ? cmd.getMaxIops() : volume.getMaxIops();
 +
 +                validateIops(newMinIops, newMaxIops);
 +            } else {
 +                newMinIops = newDiskOffering.getMinIops();
 +                newMaxIops = newDiskOffering.getMaxIops();
 +            }
 +
 +            // if the hypervisor snapshot reserve value is null, it must remain null (currently only KVM uses null and null is all KVM uses for a value here)
 +            newHypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve() != null ? newDiskOffering.getHypervisorSnapshotReserve() : null;
 +        }
 +
 +        long currentSize = volume.getSize();
 +
 +        // if the caller is looking to change the size of the volume
 +        if (currentSize != newSize) {
 +            if (volume.getInstanceId() != null) {
 +                // Check that VM to which this volume is attached does not have VM snapshots
 +                if (_vmSnapshotDao.findByVm(volume.getInstanceId()).size() > 0) {
 +                    throw new InvalidParameterValueException("A volume that is attached to a VM with any VM snapshots cannot be resized.");
 +                }
 +            }
 +
 +            if (!validateVolumeSizeRange(newSize)) {
 +                throw new InvalidParameterValueException("Requested size out of range");
 +            }
 +
 +            Long storagePoolId = volume.getPoolId();
 +
 +            if (storagePoolId != null) {
 +                StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId);
 +
 +                if (storagePoolVO.isManaged()) {
 +                    Long instanceId = volume.getInstanceId();
 +
 +                    if (instanceId != null) {
 +                        VMInstanceVO vmInstanceVO = _vmInstanceDao.findById(instanceId);
 +
 +                        if (vmInstanceVO.getHypervisorType() == HypervisorType.KVM && vmInstanceVO.getState() != State.Stopped) {
 +                            throw new CloudRuntimeException("This kind of KVM disk cannot be resized while it is connected to a VM that's not in the Stopped state.");
 +                        }
 +                    }
 +                }
 +            }
 +
 +            /*
 +             * Let's make certain they (think they) know what they're doing if they
 +             * want to shrink by forcing them to provide the shrinkok parameter.
 +             * This will be checked again at the hypervisor level where we can see
 +             * the actual disk size.
 +             */
 +            if (currentSize > newSize && !shrinkOk) {
 +                throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize + " would shrink the volume."
 +                        + "Need to sign off by supplying the shrinkok parameter with value of true.");
 +            }
 +
 +            if (newSize > currentSize) {
 +                /* Check resource limit for this account on primary storage resource */
 +                _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), ResourceType.primary_storage, volume.isDisplayVolume(),
 +                        new Long(newSize - currentSize).longValue());
 +            }
 +        }
 +
 +        // Note: The storage plug-in in question should perform validation on the IOPS to check if a sufficient number of IOPS is available to perform
 +        // the requested change
 +
 +        /* If this volume has never been beyond allocated state, short circuit everything and simply update the database. */
 +        if (volume.getState() == Volume.State.Allocated) {
 +            s_logger.debug("Volume is in the allocated state, but has never been created. Simply updating database with new size and IOPS.");
 +
 +            volume.setSize(newSize);
 +            volume.setMinIops(newMinIops);
 +            volume.setMaxIops(newMaxIops);
 +            volume.setHypervisorSnapshotReserve(newHypervisorSnapshotReserve);
 +
 +            if (newDiskOffering != null) {
 +                volume.setDiskOfferingId(cmd.getNewDiskOfferingId());
 +            }
 +
 +            _volsDao.update(volume.getId(), volume);
 +
 +            return volume;
 +        }
 +
 +        UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
 +
 +        if (userVm != null) {
 +            if (volume.getVolumeType().equals(Volume.Type.ROOT) && userVm.getPowerState() != VirtualMachine.PowerState.PowerOff && hypervisorType == HypervisorType.VMware) {
 +                s_logger.error(" For ROOT volume resize VM should be in Power Off state.");
 +                throw new InvalidParameterValueException("VM current state is : " + userVm.getPowerState() + ". But VM should be in " + VirtualMachine.PowerState.PowerOff + " state.");
 +            }
 +            // serialize VM operation
 +            AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
 +
 +            if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
 +                // avoid re-entrance
 +
 +                VmWorkJobVO placeHolder = null;
 +
 +                placeHolder = createPlaceHolderWork(userVm.getId());
 +
 +                try {
 +                    return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve,
 +                            newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk);
 +                } finally {
 +                    _workJobDao.expunge(placeHolder.getId());
 +                }
 +            } else {
 +                Outcome<Volume> outcome = resizeVolumeThroughJobQueue(userVm.getId(), volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve,
 +                        newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk);
 +
 +                try {
 +                    outcome.get();
 +                } catch (InterruptedException e) {
 +                    throw new RuntimeException("Operation was interrupted", e);
 +                } catch (java.util.concurrent.ExecutionException e) {
 +                    throw new RuntimeException("Execution exception", e);
 +                }
 +
 +                Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
 +
 +                if (jobResult != null) {
 +                    if (jobResult instanceof ConcurrentOperationException) {
 +                        throw (ConcurrentOperationException)jobResult;
 +                    } else if (jobResult instanceof ResourceAllocationException) {
 +                        throw (ResourceAllocationException)jobResult;
 +                    } else if (jobResult instanceof RuntimeException) {
 +                        throw (RuntimeException)jobResult;
 +                    } else if (jobResult instanceof Throwable) {
 +                        throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
 +                    } else if (jobResult instanceof Long) {
 +                        return _volsDao.findById((Long)jobResult);
 +                    }
 +                }
 +
 +                return volume;
 +            }
 +        }
 +
 +        return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, newDiskOffering != null ? cmd.getNewDiskOfferingId() : null,
 +                shrinkOk);
 +    }
 +
 +    private void validateIops(Long minIops, Long maxIops) {
 +        if ((minIops == null && maxIops != null) || (minIops != null && maxIops == null)) {
 +            throw new InvalidParameterValueException("Either 'miniops' and 'maxiops' must both be provided or neither must be provided.");
 +        }
 +
 +        if (minIops != null && maxIops != null) {
 +            if (minIops > maxIops) {
 +                throw new InvalidParameterValueException("The 'miniops' parameter must be less than or equal to the 'maxiops' parameter.");
 +            }
 +        }
 +    }
 +
 +    private VolumeVO orchestrateResizeVolume(long volumeId, long currentSize, long newSize, Long newMinIops, Long newMaxIops, Integer newHypervisorSnapshotReserve, Long newDiskOfferingId,
 +            boolean shrinkOk) {
 +        VolumeVO volume = _volsDao.findById(volumeId);
 +        UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
 +        StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId());
 +        boolean isManaged = storagePool.isManaged();
 +
 +        if (!storageMgr.storagePoolHasEnoughSpaceForResize(storagePool, currentSize, newSize)) {
 +            throw new CloudRuntimeException("Storage pool " + storagePool.getName() + " does not have enough space to resize volume " + volume.getName());
 +        }
 +
 +        /*
 +         * get a list of hosts to send the commands to, try the system the
 +         * associated vm is running on first, then the last known place it ran.
 +         * If not attached to a userVm, we pass 'none' and resizevolume.sh is ok
 +         * with that since it only needs the vm name to live resize
 +         */
 +        long[] hosts = null;
 +        String instanceName = "none";
 +        if (userVm != null) {
 +            instanceName = userVm.getInstanceName();
 +            if (userVm.getHostId() != null) {
 +                hosts = new long[] {userVm.getHostId()};
 +            } else if (userVm.getLastHostId() != null) {
 +                hosts = new long[] {userVm.getLastHostId()};
 +            }
 +
 +            final String errorMsg = "The VM must be stopped or the disk detached in order to resize with the XenServer Hypervisor.";
 +
 +            if (storagePool.isManaged() && storagePool.getHypervisor() == HypervisorType.Any && hosts != null && hosts.length > 0) {
 +                HostVO host = _hostDao.findById(hosts[0]);
 +
 +                if (currentSize != newSize && host.getHypervisorType() == HypervisorType.XenServer && !userVm.getState().equals(State.Stopped)) {
 +                    throw new InvalidParameterValueException(errorMsg);
 +                }
 +            }
 +
 +            /* Xen only works offline, SR does not support VDI.resizeOnline */
 +            if (currentSize != newSize && _volsDao.getHypervisorType(volume.getId()) == HypervisorType.XenServer && !userVm.getState().equals(State.Stopped)) {
 +                throw new InvalidParameterValueException(errorMsg);
 +            }
 +        }
 +
 +        ResizeVolumePayload payload = new ResizeVolumePayload(newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, shrinkOk, instanceName, hosts, isManaged);
 +
 +        try {
 +            VolumeInfo vol = volFactory.getVolume(volume.getId());
 +            vol.addPayload(payload);
 +
 +            // this call to resize has a different impact depending on whether the
 +            // underlying primary storage is managed or not
 +            // if managed, this is the chance for the plug-in to change the size and/or IOPS values
 +            // if not managed, this is the chance for the plug-in to talk to the hypervisor layer
 +            // to change the size of the disk
 +            AsyncCallFuture<VolumeApiResult> future = volService.resize(vol);
 +            VolumeApiResult result = future.get();
 +
 +            if (result.isFailed()) {
 +                s_logger.warn("Failed to resize the volume " + volume);
 +                String details = "";
 +                if (result.getResult() != null && !result.getResult().isEmpty()) {
 +                    details = result.getResult();
 +                }
 +                throw new CloudRuntimeException(details);
 +            }
 +
 +            // managed storage is designed in such a way that the storage plug-in does not
 +            // talk to the hypervisor layer; as such, if the storage is managed and the
 +            // current and new sizes are different, then CloudStack (i.e. not a storage plug-in)
 +            // needs to tell the hypervisor to resize the disk
 +            if (storagePool.isManaged() && currentSize != newSize) {
 +                if (hosts != null && hosts.length > 0) {
 +                    HostVO hostVO = _hostDao.findById(hosts[0]);
 +
 +                    if (hostVO.getHypervisorType() != HypervisorType.KVM) {
 +                        volService.resizeVolumeOnHypervisor(volumeId, newSize, hosts[0], instanceName);
 +                    }
 +                }
 +
 +                volume.setSize(newSize);
 +
 +                _volsDao.update(volume.getId(), volume);
 +            }
 +
 +            volume = _volsDao.findById(volume.getId());
 +
 +            if (newDiskOfferingId != null) {
 +                volume.setDiskOfferingId(newDiskOfferingId);
 +            }
 +
 +            if (currentSize != newSize) {
 +                volume.setSize(newSize);
 +            }
 +
 +            _volsDao.update(volume.getId(), volume);
 +
 +            /* Update resource count for the account on primary storage resource */
 +            if (!shrinkOk) {
 +                _resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplayVolume(), new Long(newSize - currentSize));
 +            } else {
 +                _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplayVolume(), new Long(currentSize - newSize));
 +            }
 +            return volume;
 +        } catch (InterruptedException e) {
 +            s_logger.warn("failed get resize volume result", e);
 +            throw new CloudRuntimeException(e.getMessage());
 +        } catch (ExecutionException e) {
 +            s_logger.warn("failed get resize volume result", e);
 +            throw new CloudRuntimeException(e.getMessage());
 +        } catch (Exception e) {
 +            s_logger.warn("failed get resize volume result", e);
 +            throw new CloudRuntimeException(e.getMessage());
 +        }
 +    }
 +
 +    @DB
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DELETE, eventDescription = "deleting volume")
 +    /**
 +     * Executes the removal of the volume. If the volume is only allocated we do not try to remove it from primary and secondary storage.
 +     * Otherwise, after the removal in the database, we will try to remove the volume from both primary and secondary storage.
 +     */
 +    public boolean deleteVolume(long volumeId, Account caller) throws ConcurrentOperationException {
 +        VolumeVO volume = retrieveAndValidateVolume(volumeId, caller);
 +        try {
 +            destroyVolumeIfPossible(volume);
 +            // Mark volume as removed if volume has not been created on primary or secondary
 +            if (volume.getState() == Volume.State.Allocated) {
 +                _volsDao.remove(volumeId);
 +                stateTransitTo(volume, Volume.Event.DestroyRequested);
 +                return true;
 +            }
 +            expungeVolumesInPrimaryStorageIfNeeded(volume);
 +            expungeVolumesInSecondaryStorageIfNeeded(volume);
 +            cleanVolumesCache(volume);
 +            return true;
 +        } catch (InterruptedException | ExecutionException | NoTransitionException e) {
 +            s_logger.warn("Failed to expunge volume: " + volume.getUuid(), e);
 +            return false;
 +        }
 +    }
 +
 +    /**
 +     *  Retrieves and validates the volume for the {@link #deleteVolume(long, Account)} method. The following validation are executed.
 +     *  <ul>
 +     *      <li> if no volume is found in the database, we throw an {@link InvalidParameterValueException};
 +     *      <li> if there are snapshots operation on the volume we cannot delete it. Therefore, an {@link InvalidParameterValueException} is thrown;
 +     *      <li> if the volume is still attached to a VM we throw an {@link InvalidParameterValueException};
 +     *      <li> if volume state is in {@link Volume.State#UploadOp}, we check the {@link VolumeDataStoreVO}. Then, if the {@link VolumeDataStoreVO} for the given volume has download status of {@link VMTemplateStorageResourceAssoc.Status#DOWNLOAD_IN_PROGRESS}, an exception is throw;
 +     *      <li> if the volume state is in {@link Volume.State#NotUploaded} or if the state is {@link Volume.State#UploadInProgress}, an {@link InvalidParameterValueException} is thrown;
 +     *      <li> we also check if the user has access to the given volume using {@link AccountManager#checkAccess(Account, org.apache.cloudstack.acl.SecurityChecker.AccessType, boolean, String)}.
 +     *  </ul>
 +     *
 +     *  After all validations we return the volume object.
 +     */
 +    protected VolumeVO retrieveAndValidateVolume(long volumeId, Account caller) {
 +        VolumeVO volume = _volsDao.findById(volumeId);
 +        if (volume == null) {
 +            throw new InvalidParameterValueException("Unable to find volume with ID: " + volumeId);
 +        }
 +        if (!_snapshotMgr.canOperateOnVolume(volume)) {
 +            throw new InvalidParameterValueException("There are snapshot operations in progress on the volume, unable to delete it");
 +        }
 +        if (volume.getInstanceId() != null) {
 +            throw new InvalidParameterValueException("Please specify a volume that is not attached to any VM.");
 +        }
 +        if (volume.getState() == Volume.State.UploadOp) {
 +            VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume.getId());
 +            if (volumeStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) {
 +                throw new InvalidParameterValueException("Please specify a volume that is not uploading");
 +            }
 +        }
 +        if (volume.getState() == Volume.State.NotUploaded || volume.getState() == Volume.State.UploadInProgress) {
 +            throw new InvalidParameterValueException("The volume is either getting uploaded or it may be initiated shortly, please wait for it to be completed");
 +        }
 +        _accountMgr.checkAccess(caller, null, true, volume);
 +        return volume;
 +    }
 +
 +    /**
 +     * Destroy the volume if possible and then decrement the following resource types.
 +     * <ul>
 +     *  <li> {@link ResourceType#volume};
 +     *  <li> {@link ResourceType#primary_storage}
 +     * </ul>
 +     *
 +     * A volume can be destroyed if it is not in any of the following states.
 +     * <ul>
 +     *  <li> {@value Volume.State#Destroy};
 +     *  <li> {@value Volume.State#Expunging};
 +     *  <li> {@value Volume.State#Expunged}.
 +     * </ul>
 +     *
 +     * The volume is destroyed via {@link VolumeService#destroyVolume(long)} method.
 +     */
 +    protected void destroyVolumeIfPossible(VolumeVO volume) {
 +        if (volume.getState() != Volume.State.Destroy && volume.getState() != Volume.State.Expunging && volume.getState() != Volume.State.Expunged) {
 +            volService.destroyVolume(volume.getId());
 +
 +            // Decrement the resource count for volumes and primary storage belonging user VM's only
 +            _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume, volume.isDisplayVolume());
 +            _resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.primary_storage, volume.isDisplayVolume(), volume.getSize());
 +        }
 +    }
 +
 +    /**
 +     * We will check if the given volume is in the primary storage. If it is, we will execute an asynchronous call to delete it there.
 +     * If the volume is not in the primary storage, we do nothing here.
 +     */
 +    protected void expungeVolumesInPrimaryStorageIfNeeded(VolumeVO volume) throws InterruptedException, ExecutionException {
 +        VolumeInfo volOnPrimary = volFactory.getVolume(volume.getId(), DataStoreRole.Primary);
 +        if (volOnPrimary != null) {
 +            s_logger.info("Expunging volume " + volume.getId() + " from primary data store");
 +            AsyncCallFuture<VolumeApiResult> future = volService.expungeVolumeAsync(volOnPrimary);
 +            future.get();
 +        }
 +    }
 +
 +    /**
 +     * We will check if the given volume is in the secondary storage. If the volume is not in the primary storage, we do nothing here.
 +     * If it is, we will execute an asynchronous call to delete it there. Then, we decrement the {@link ResourceType#secondary_storage} for the account that owns the volume.
 +     */
 +    protected void expungeVolumesInSecondaryStorageIfNeeded(VolumeVO volume) throws InterruptedException, ExecutionException {
 +        VolumeInfo volOnSecondary = volFactory.getVolume(volume.getId(), DataStoreRole.Image);
 +        if (volOnSecondary != null) {
 +            s_logger.info("Expunging volume " + volume.getId() + " from secondary data store");
 +            AsyncCallFuture<VolumeApiResult> future2 = volService.expungeVolumeAsync(volOnSecondary);
 +            future2.get();
 +
 +            _resourceLimitMgr.decrementResourceCount(volOnSecondary.getAccountId(), ResourceType.secondary_storage, volOnSecondary.getSize());
 +        }
 +    }
 +
 +    /**
 +     * Clean volumes cache entries (if they exist).
 +     */
 +    protected void cleanVolumesCache(VolumeVO volume) {
 +        List<VolumeInfo> cacheVols = volFactory.listVolumeOnCache(volume.getId());
 +        if (CollectionUtils.isEmpty(cacheVols)) {
 +            return;
 +        }
 +        for (VolumeInfo volOnCache : cacheVols) {
 +            s_logger.info("Delete volume from image cache store: " + volOnCache.getDataStore().getName());
 +            volOnCache.delete();
 +        }
 +    }
 +
 +    protected boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException {
 +        return _volStateMachine.transitTo(vol, event, null, _volsDao);
 +    }
 +
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_ATTACH, eventDescription = "attaching volume", async = true)
 +    public Volume attachVolumeToVM(AttachVolumeCmd command) {
 +        return attachVolumeToVM(command.getVirtualMachineId(), command.getId(), command.getDeviceId());
 +    }
 +
 +    private Volume orchestrateAttachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
 +        VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);
 +
 +        if (volumeToAttach.isAttachedVM()) {
 +            throw new CloudRuntimeException("This volume is already attached to a VM.");
 +        }
 +
 +        UserVmVO vm = _userVmDao.findById(vmId);
 +        VolumeVO exstingVolumeOfVm = null;
 +        List<VolumeVO> rootVolumesOfVm = _volsDao.findByInstanceAndType(vmId, Volume.Type.ROOT);
 +        if (rootVolumesOfVm.size() > 1) {
 +            throw new CloudRuntimeException("The VM " + vm.getHostName() + " has more than one ROOT volume and is in an invalid state.");
 +        } else {
 +            if (!rootVolumesOfVm.isEmpty()) {
 +                exstingVolumeOfVm = rootVolumesOfVm.get(0);
 +            } else {
 +                // locate data volume of the vm
 +                List<VolumeVO> diskVolumesOfVm = _volsDao.findByInstanceAndType(vmId, Volume.Type.DATADISK);
 +                for (VolumeVO diskVolume : diskVolumesOfVm) {
 +                    if (diskVolume.getState() != Volume.State.Allocated) {
 +                        exstingVolumeOfVm = diskVolume;
 +                        break;
 +                    }
 +                }
 +            }
 +        }
 +
 +        HypervisorType rootDiskHyperType = vm.getHypervisorType();
 +        HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId());
 +
 +        VolumeInfo newVolumeOnPrimaryStorage = volumeToAttach;
 +
 +        //don't create volume on primary storage if its being attached to the vm which Root's volume hasn't been created yet
 +        StoragePoolVO destPrimaryStorage = null;
 +        if (exstingVolumeOfVm != null && !exstingVolumeOfVm.getState().equals(Volume.State.Allocated)) {
 +            destPrimaryStorage = _storagePoolDao.findById(exstingVolumeOfVm.getPoolId());
 +        }
 +
 +        boolean volumeOnSecondary = volumeToAttach.getState() == Volume.State.Uploaded;
 +
 +        if (destPrimaryStorage != null && (volumeToAttach.getState() == Volume.State.Allocated || volumeOnSecondary)) {
 +            try {
 +                newVolumeOnPrimaryStorage = _volumeMgr.createVolumeOnPrimaryStorage(vm, volumeToAttach, rootDiskHyperType, destPrimaryStorage);
 +            } catch (NoTransitionException e) {
 +                s_logger.debug("Failed to create volume on primary storage", e);
 +                throw new CloudRuntimeException("Failed to create volume on primary storage", e);
 +            }
 +        }
 +
 +        // reload the volume from db
 +        newVolumeOnPrimaryStorage = volFactory.getVolume(newVolumeOnPrimaryStorage.getId());
 +        boolean moveVolumeNeeded = needMoveVolume(exstingVolumeOfVm, newVolumeOnPrimaryStorage);
 +
 +        if (moveVolumeNeeded) {
 +            PrimaryDataStoreInfo primaryStore = (PrimaryDataStoreInfo)newVolumeOnPrimaryStorage.getDataStore();
 +            if (primaryStore.isLocal()) {
 +                throw new CloudRuntimeException(
 +                        "Failed to attach local data volume " + volumeToAttach.getName() + " to VM " + vm.getDisplayName() + " as migration of local data volume is not allowed");
 +            }
 +            StoragePoolVO vmRootVolumePool = _storagePoolDao.findById(exstingVolumeOfVm.getPoolId());
 +
 +            try {
 +                newVolumeOnPrimaryStorage = _volumeMgr.moveVolume(newVolumeOnPrimaryStorage, vmRootVolumePool.getDataCenterId(), vmRootVolumePool.getPodId(), vmRootVolumePool.getClusterId(),
 +                        volumeToAttachHyperType);
 +            } catch (ConcurrentOperationException e) {
 +                s_logger.debug("move volume failed", e);
 +                throw new CloudRuntimeException("move volume failed", e);
 +            } catch (StorageUnavailableException e) {
 +                s_logger.debug("move volume failed", e);
 +                throw new CloudRuntimeException("move volume failed", e);
 +            }
 +        }
 +        VolumeVO newVol = _volsDao.findById(newVolumeOnPrimaryStorage.getId());
 +        // Getting the fresh vm object in case of volume migration to check the current state of VM
 +        if (moveVolumeNeeded || volumeOnSecondary) {
 +            vm = _userVmDao.findById(vmId);
 +            if (vm == null) {
 +                throw new InvalidParameterValueException("VM not found.");
 +            }
 +        }
 +        newVol = sendAttachVolumeCommand(vm, newVol, deviceId);
 +        return newVol;
 +    }
 +
 +    public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) {
 +        Account caller = CallContext.current().getCallingAccount();
 +
 +        // Check that the volume ID is valid
 +        VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);
 +        // Check that the volume is a data volume
 +        if (volumeToAttach == null || !(volumeToAttach.getVolumeType() == Volume.Type.DATADISK || volumeToAttach.getVolumeType() == Volume.Type.ROOT)) {
 +            throw new InvalidParameterValueException("Please specify a volume with the valid type: " + Volume.Type.ROOT.toString() + " or " + Volume.Type.DATADISK.toString());
 +        }
 +
 +        // Check that the volume is not currently attached to any VM
 +        if (volumeToAttach.getInstanceId() != null) {
 +            throw new InvalidParameterValueException("Please specify a volume that is not attached to any VM.");
 +        }
 +
 +        // Check that the volume is not destroyed
 +        if (volumeToAttach.getState() == Volume.State.Destroy) {
 +            throw new InvalidParameterValueException("Please specify a volume that is not destroyed.");
 +        }
 +
 +        // Check that the virtual machine ID is valid and it's a user vm
 +        UserVmVO vm = _userVmDao.findById(vmId);
 +        if (vm == null || vm.getType() != VirtualMachine.Type.User) {
 +            throw new InvalidParameterValueException("Please specify a valid User VM.");
 +        }
 +
 +        // Check that the VM is in the correct state
 +        if (vm.getState() != State.Running && vm.getState() != State.Stopped) {
 +            throw new InvalidParameterValueException("Please specify a VM that is either running or stopped.");
 +        }
 +
 +        // Check that the VM and the volume are in the same zone
 +        if (vm.getDataCenterId() != volumeToAttach.getDataCenterId()) {
 +            throw new InvalidParameterValueException("Please specify a VM that is in the same zone as the volume.");
 +        }
 +
 +        // Check that the device ID is valid
 +        if (deviceId != null) {
 +            // validate ROOT volume type
 +            if (deviceId.longValue() == 0) {
 +                validateRootVolumeDetachAttach(_volsDao.findById(volumeToAttach.getId()), vm);
 +                // vm shouldn't have any volume with deviceId 0
 +                if (!_volsDao.findByInstanceAndDeviceId(vm.getId(), 0).isEmpty()) {
 +                    throw new InvalidParameterValueException("Vm already has root volume attached to it");
 +                }
 +                // volume can't be in Uploaded state
 +                if (volumeToAttach.getState() == Volume.State.Uploaded) {
 +                    throw new InvalidParameterValueException("No support for Root volume attach in state " + Volume.State.Uploaded);
 +                }
 +            }
 +        }
 +
 +        // Check that the number of data volumes attached to VM is less than
 +        // that supported by hypervisor
 +        if (deviceId == null || deviceId.longValue() != 0) {
 +            List<VolumeVO> existingDataVolumes = _volsDao.findByInstanceAndType(vmId, Volume.Type.DATADISK);
 +            int maxAttachableDataVolumesSupported = getMaxDataVolumesSupported(vm);
 +            if (existingDataVolumes.size() >= maxAttachableDataVolumesSupported) {
 +                throw new InvalidParameterValueException(
 +                        "The specified VM already has the maximum number of data disks (" + maxAttachableDataVolumesSupported + ") attached. Please specify another VM.");
 +            }
 +        }
 +
 +        // If local storage is disabled then attaching a volume with local disk
 +        // offering not allowed
 +        DataCenterVO dataCenter = _dcDao.findById(volumeToAttach.getDataCenterId());
 +        if (!dataCenter.isLocalStorageEnabled()) {
 +            DiskOfferingVO diskOffering = _diskOfferingDao.findById(volumeToAttach.getDiskOfferingId());
 +            if (diskOffering.isUseLocalStorage()) {
 +                throw new InvalidParameterValueException("Zone is not configured to use local storage but volume's disk offering " + diskOffering.getName() + " uses it");
 +            }
 +        }
 +
 +        // if target VM has associated VM snapshots
 +        List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vmId);
 +        if (vmSnapshots.size() > 0) {
 +            throw new InvalidParameterValueException("Unable to attach volume, please specify a VM that does not have VM snapshots");
 +        }
 +
 +        // permission check
 +        _accountMgr.checkAccess(caller, null, true, volumeToAttach, vm);
 +
 +        if (!(Volume.State.Allocated.equals(volumeToAttach.getState()) || Volume.State.Ready.equals(volumeToAttach.getState()) || Volume.State.Uploaded.equals(volumeToAttach.getState()))) {
 +            throw new InvalidParameterValueException("Volume state must be in Allocated, Ready or in Uploaded state");
 +        }
 +
 +        Account owner = _accountDao.findById(volumeToAttach.getAccountId());
 +
 +        if (!(volumeToAttach.getState() == Volume.State.Allocated || volumeToAttach.getState() == Volume.State.Ready)) {
 +            try {
 +                _resourceLimitMgr.checkResourceLimit(owner, ResourceType.primary_storage, volumeToAttach.getSize());
 +            } catch (ResourceAllocationException e) {
 +                s_logger.error("primary storage resource limit check failed", e);
 +                throw new InvalidParameterValueException(e.getMessage());
 +            }
 +        }
 +
 +        HypervisorType rootDiskHyperType = vm.getHypervisorType();
 +        HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId());
 +
 +        StoragePoolVO volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId());
 +
 +        // managed storage can be used for different types of hypervisors
 +        // only perform this check if the volume's storage pool is not null and not managed
 +        if (volumeToAttachStoragePool != null && !volumeToAttachStoragePool.isManaged()) {
 +            if (volumeToAttachHyperType != HypervisorType.None && rootDiskHyperType != volumeToAttachHyperType) {
 +                throw new InvalidParameterValueException("Can't attach a volume created by: " + volumeToAttachHyperType + " to a " + rootDiskHyperType + " vm");
 +            }
 +        }
 +
 +        AsyncJobExecutionContext asyncExecutionContext = AsyncJobExecutionContext.getCurrentExecutionContext();
 +
 +        if (asyncExecutionContext != null) {
 +            AsyncJob job = asyncExecutionContext.getJob();
 +
 +            if (s_logger.isInfoEnabled()) {
 +                s_logger.info("Trying to attaching volume " + volumeId + " to vm instance:" + vm.getId() + ", update async job-" + job.getId() + " progress status");
 +            }
 +
 +            _jobMgr.updateAsyncJobAttachment(job.getId(), "Volume", volumeId);
 +        }
 +
 +        AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
 +        if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
 +            // avoid re-entrance
 +
 +            VmWorkJobVO placeHolder = null;
 +            placeHolder = createPlaceHolderWork(vmId);
 +            try {
 +                return orchestrateAttachVolumeToVM(vmId, volumeId, deviceId);
 +            } finally {
 +                _workJobDao.expunge(placeHolder.getId());
 +            }
 +
 +        } else {
 +            Outcome<Volume> outcome = attachVolumeToVmThroughJobQueue(vmId, volumeId, deviceId);
 +
 +            Volume vol = null;
 +            try {
 +                outcome.get();
 +            } catch (InterruptedException e) {
 +                throw new RuntimeException("Operation is interrupted", e);
 +            } catch (java.util.concurrent.ExecutionException e) {
 +                throw new RuntimeException("Execution excetion", e);
 +            }
 +
 +            Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
 +            if (jobResult != null) {
 +                if (jobResult instanceof ConcurrentOperationException) {
 +                    throw (ConcurrentOperationException)jobResult;
 +                } else if (jobResult instanceof InvalidParameterValueException) {
 +                    throw (InvalidParameterValueException)jobResult;
 +                } else if (jobResult instanceof RuntimeException) {
 +                    throw (RuntimeException)jobResult;
 +                } else if (jobResult instanceof Throwable) {
 +                    throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
 +                } else if (jobResult instanceof Long) {
 +                    vol = _volsDao.findById((Long)jobResult);
 +                }
 +            }
 +            return vol;
 +        }
 +    }
 +
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_UPDATE, eventDescription = "updating volume", async = true)
 +    public Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long entityOwnerId, String chainInfo) {
 +
 +        VolumeVO volume = _volsDao.findById(volumeId);
 +
 +        if (volume == null) {
 +            throw new InvalidParameterValueException("The volume id doesn't exist");
 +        }
 +
 +        if (path != null) {
 +            volume.setPath(path);
 +        }
 +
 +        if (chainInfo != null) {
 +            volume.setChainInfo(chainInfo);
 +        }
 +
 +        if (state != null) {
 +            try {
 +                Volume.State volumeState = Volume.State.valueOf(state);
 +                volume.setState(volumeState);
 +            } catch (IllegalArgumentException ex) {
 +                throw new InvalidParameterValueException("Invalid volume state specified");
 +            }
 +        }
 +
 +        if (storageId != null) {
 +            StoragePool pool = _storagePoolDao.findById(storageId);
 +            if (pool.getDataCenterId() != volume.getDataCenterId()) {
 +                throw new InvalidParameterValueException("Invalid storageId specified; refers to the pool outside of the volume's zone");
 +            }
 +            volume.setPoolId(pool.getId());
 +        }
 +
 +        if (customId != null) {
 +            volume.setUuid(customId);
 +        }
 +
 +        updateDisplay(volume, displayVolume);
 +
 +        _volsDao.update(volumeId, volume);
 +
 +        return volume;
 +    }
 +
 +    @Override
 +    public void updateDisplay(Volume volume, Boolean displayVolume) {
 +        // 1. Resource limit changes
 +        updateResourceCount(volume, displayVolume);
 +
 +        // 2. generate usage event if not in destroyed state
 +        saveUsageEvent(volume, displayVolume);
 +
 +        // 3. Set the flag
 +        if (displayVolume != null && displayVolume != volume.isDisplayVolume()) {
 +            // FIXME - Confused - typecast for now.
 +            ((VolumeVO)volume).setDisplayVolume(displayVolume);
 +            _volsDao.update(volume.getId(), (VolumeVO)volume);
 +        }
 +
 +    }
 +
 +    private void updateResourceCount(Volume volume, Boolean displayVolume) {
 +        // Update only when the flag has changed.
 +        if (displayVolume != null && displayVolume != volume.isDisplayVolume()) {
 +            _resourceLimitMgr.changeResourceCount(volume.getAccountId(), ResourceType.volume, displayVolume);
 +            _resourceLimitMgr.changeResourceCount(volume.getAccountId(), ResourceType.primary_storage, displayVolume, new Long(volume.getSize()));
 +        }
 +    }
 +
 +    private void saveUsageEvent(Volume volume, Boolean displayVolume) {
 +
 +        // Update only when the flag has changed  &&  only when volume in a non-destroyed state.
 +        if ((displayVolume != null && displayVolume != volume.isDisplayVolume()) && !isVolumeDestroyed(volume)) {
 +            if (displayVolume) {
 +                // flag turned 1 equivalent to freshly created volume
 +                UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), volume.getDiskOfferingId(),
 +                        volume.getTemplateId(), volume.getSize(), Volume.class.getName(), volume.getUuid());
 +            } else {
 +                // flag turned 0 equivalent to deleting a volume
 +                UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), Volume.class.getName(),
 +                        volume.getUuid());
 +            }
 +        }
 +    }
 +
 +    private boolean isVolumeDestroyed(Volume volume) {
 +        if (volume.getState() == Volume.State.Destroy || volume.getState() == Volume.State.Expunging && volume.getState() == Volume.State.Expunged) {
 +            return true;
 +        }
 +        return false;
 +    }
 +
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DETACH, eventDescription = "detaching volume", async = true)
 +    public Volume detachVolumeFromVM(DetachVolumeCmd cmmd) {
 +        Account caller = CallContext.current().getCallingAccount();
 +        if ((cmmd.getId() == null && cmmd.getDeviceId() == null && cmmd.getVirtualMachineId() == null) || (cmmd.getId() != null && (cmmd.getDeviceId() != null || cmmd.getVirtualMachineId() != null))
 +                || (cmmd.getId() == null && (cmmd.getDeviceId() == null || cmmd.getVirtualMachineId() == null))) {
 +            throw new InvalidParameterValueException("Please provide either a volume id, or a tuple(device id, instance id)");
 +        }
 +
 +        Long volumeId = cmmd.getId();
 +        VolumeVO volume = null;
 +
 +        if (volumeId != null) {
 +            volume = _volsDao.findById(volumeId);
 +        } else {
 +            volume = _volsDao.findByInstanceAndDeviceId(cmmd.getVirtualMachineId(), cmmd.getDeviceId()).get(0);
 +        }
 +
 +        // Check that the volume ID is valid
 +        if (volume == null) {
 +            throw new InvalidParameterValueException("Unable to find volume with ID: " + volumeId);
 +        }
 +
 +        Long vmId = null;
 +
 +        if (cmmd.getVirtualMachineId() == null) {
 +            vmId = volume.getInstanceId();
 +        } else {
 +            vmId = cmmd.getVirtualMachineId();
 +        }
 +
 +        // Permissions check
 +        _accountMgr.checkAccess(caller, null, true, volume);
 +
 +        // Check that the volume is currently attached to a VM
 +        if (vmId == null) {
 +            throw new InvalidParameterValueException("The specified volume is not attached to a VM.");
 +        }
 +
 +        // Check that the VM is in the correct state
 +        UserVmVO vm = _userVmDao.findById(vmId);
 +        if (vm.getState() != State.Running && vm.getState() != State.Stopped && vm.getState() != State.Destroyed) {
 +            throw new InvalidParameterValueException("Please specify a VM that is either running or stopped.");
 +        }
 +
 +        // Check that the volume is a data/root volume
 +        if (!(volume.getVolumeType() == Volume.Type.ROOT || volume.getVolumeType() == Volume.Type.DATADISK)) {
 +            throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK.toString() + " or " + Volume.Type.ROOT.toString());
 +        }
 +
 +        // Root volume detach is allowed for following hypervisors: Xen/KVM/VmWare
 +        if (volume.getVolumeType() == Volume.Type.ROOT) {
 +            validateRootVolumeDetachAttach(volume, vm);
 +        }
 +
 +        // Don't allow detach if target VM has associated VM snapshots
 +        List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vmId);
 +        if (vmSnapshots.size() > 0) {
 +            throw new InvalidParameterValueException("Unable to detach volume, please specify a VM that does not have VM snapshots");
 +        }
 +
 +        AsyncJobExecutionContext asyncExecutionContext = AsyncJobExecutionContext.getCurrentExecutionContext();
 +        if (asyncExecutionContext != null) {
 +            AsyncJob job = asyncExecutionContext.getJob();
 +
 +            if (s_logger.isInfoEnabled()) {
 +                s_logger.info("Trying to attaching volume " + volumeId + "to vm instance:" + vm.getId() + ", update async job-" + job.getId() + " progress status");
 +            }
 +
 +            _jobMgr.updateAsyncJobAttachment(job.getId(), "Volume", volumeId);
 +        }
 +
 +        AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
 +        if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
 +            // avoid re-entrance
 +            VmWorkJobVO placeHolder = null;
 +            placeHolder = createPlaceHolderWork(vmId);
 +            try {
 +                return orchestrateDetachVolumeFromVM(vmId, volumeId);
 +            } finally {
 +                _workJobDao.expunge(placeHolder.getId());
 +            }
 +        } else {
 +            Outcome<Volume> outcome = detachVolumeFromVmThroughJobQueue(vmId, volumeId);
 +
 +            Volume vol = null;
 +            try {
 +                outcome.get();
 +            } catch (InterruptedException e) {
 +                throw new RuntimeException("Operation is interrupted", e);
 +            } catch (java.util.concurrent.ExecutionException e) {
 +                throw new RuntimeException("Execution excetion", e);
 +            }
 +
 +            Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
 +            if (jobResult != null) {
 +                if (jobResult instanceof ConcurrentOperationException) {
 +                    throw (ConcurrentOperationException)jobResult;
 +                } else if (jobResult instanceof RuntimeException) {
 +                    throw (RuntimeException)jobResult;
 +                } else if (jobResult instanceof Throwable) {
 +                    throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
 +                } else if (jobResult instanceof Long) {
 +                    vol = _volsDao.findById((Long)jobResult);
 +                }
 +            }
 +            return vol;
 +        }
 +    }
 +
 +    private void validateRootVolumeDetachAttach(VolumeVO volume, UserVmVO vm) {
 +        if (!(vm.getHypervisorType() == HypervisorType.XenServer || vm.getHypervisorType() == HypervisorType.VMware || vm.getHypervisorType() == HypervisorType.KVM
 +                || vm.getHypervisorType() == HypervisorType.Simulator)) {
 +            throw new InvalidParameterValueException("Root volume detach is not supported for hypervisor type " + vm.getHypervisorType());
 +        }
 +        if (!(vm.getState() == State.Stopped) || (vm.getState() == State.Destroyed)) {
 +            throw new InvalidParameterValueException("Root volume detach can happen only when vm is in states: " + State.Stopped.toString() + " or " + State.Destroyed.toString());
 +        }
 +
 +        if (volume.getPoolId() != null) {
 +            StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId());
 +            if (pool.isManaged()) {
 +                throw new InvalidParameterValueException("Root volume detach is not supported for Managed DataStores");
 +            }
 +        }
 +    }
 +
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DETACH, eventDescription = "detaching volume")
 +    public Volume detachVolumeViaDestroyVM(long vmId, long volumeId) {
 +        return orchestrateDetachVolumeFromVM(vmId, volumeId);
 +    }
 +
 +    private Volume orchestrateDetachVolumeFromVM(long vmId, long volumeId) {
 +        Volume volume = _volsDao.findById(volumeId);
 +        VMInstanceVO vm = _vmInstanceDao.findById(vmId);
 +
 +        String errorMsg = "Failed to detach volume " + volume.getName() + " from VM " + vm.getHostName();
 +        boolean sendCommand = vm.getState() == State.Running;
 +
 +        Long hostId = vm.getHostId();
 +
 +        if (hostId == null) {
 +            hostId = vm.getLastHostId();
 +            HostVO host = _hostDao.findById(hostId);
 +
 +            if (host != null && host.getHypervisorType() == HypervisorType.VMware) {
 +                sendCommand = true;
 +            }
 +        }
 +
 +        HostVO host = null;
 +        StoragePoolVO volumePool = _storagePoolDao.findByIdIncludingRemoved(volume.getPoolId());
 +
 +        if (hostId != null) {
 +            host = _hostDao.findById(hostId);
 +
 +            if (host != null && host.getHypervisorType() == HypervisorType.XenServer && volumePool != null && volumePool.isManaged()) {
 +                sendCommand = true;
 +            }
 +        }
 +
++        if (volumePool == null) {
++            sendCommand = false;
++        }
++
 +        Answer answer = null;
 +
 +        if (sendCommand) {
 +            // collect vm disk statistics before detach a volume
 +            UserVmVO userVm = _userVmDao.findById(vmId);
 +            if (userVm != null && userVm.getType() == VirtualMachine.Type.User) {
 +                _userVmService.collectVmDiskStatistics(userVm);
 +            }
 +
 +            DataTO volTO = volFactory.getVolume(volume.getId()).getTO();
 +            DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), volume.getPath(), volume.getVolumeType());
 +
 +            DettachCommand cmd = new DettachCommand(disk, vm.getInstanceName());
 +
 +            cmd.setManaged(volumePool.isManaged());
 +
 +            cmd.setStorageHost(volumePool.getHostAddress());
 +            cmd.setStoragePort(volumePool.getPort());
 +
 +            cmd.set_iScsiName(volume.get_iScsiName());
 +
 +            try {
 +                answer = _agentMgr.send(hostId, cmd);
 +            } catch (Exception e) {
 +                throw new CloudRuntimeException(errorMsg + " due to: " + e.getMessage());
 +            }
 +        }
 +
 +        if (!sendCommand || (answer != null && answer.getResult())) {
 +            // Mark the volume as detached
 +            _volsDao.detachVolume(volume.getId());
 +
 +            // volume.getPoolId() should be null if the VM we are detaching the disk from has never been started before
-             DataStore dataStore = volume.getPoolId() != null ? dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary) : null;
- 
-             volService.revokeAccess(volFactory.getVolume(volume.getId()), host, dataStore);
- 
-             handleTargetsForVMware(hostId, volumePool.getHostAddress(), volumePool.getPort(), volume.get_iScsiName());
- 
++            if (volume.getPoolId() != null) {
++                DataStore dataStore = dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary);
++                volService.revokeAccess(volFactory.getVolume(volume.getId()), host, dataStore);
++            }
++            if (volumePool != null && hostId != null) {
++                handleTargetsForVMware(hostId, volumePool.getHostAddress(), volumePool.getPort(), volume.get_iScsiName());
++            }
 +            return _volsDao.findById(volumeId);
 +        } else {
 +
 +            if (answer != null) {
 +                String details = answer.getDetails();
 +                if (details != null && !details.isEmpty()) {
 +                    errorMsg += "; " + details;
 +                }
 +            }
 +
 +            throw new CloudRuntimeException(errorMsg);
 +        }
 +    }
 +
 +    public void updateMissingRootDiskController(final VMInstanceVO vm, final String rootVolChainInfo) {
 +        if (vm == null || !VirtualMachine.Type.User.equals(vm.getType()) || Strings.isNullOrEmpty(rootVolChainInfo)) {
 +            return;
 +        }
 +        String rootDiskController = null;
 +        try {
 +            final VirtualMachineDiskInfo infoInChain = _gson.fromJson(rootVolChainInfo, VirtualMachineDiskInfo.class);
 +            if (infoInChain != null) {
 +                rootDiskController = infoInChain.getControllerFromDeviceBusName();
 +            }
 +            final UserVmVO userVmVo = _userVmDao.findById(vm.getId());
 +            if ((rootDiskController != null) && (!rootDiskController.isEmpty())) {
 +                _userVmDao.loadDetails(userVmVo);
 +                _userVmMgr.persistDeviceBusInfo(userVmVo, rootDiskController);
 +            }
 +        } catch (JsonParseException e) {
 +            s_logger.debug("Error parsing chain info json: " + e.getMessage());
 +        }
 +    }
 +
 +    private void handleTargetsForVMware(long hostId, String storageAddress, int storagePort, String iScsiName) {
 +        HostVO host = _hostDao.findById(hostId);
 +
 +        if (host.getHypervisorType() == HypervisorType.VMware) {
 +            ModifyTargetsCommand cmd = new ModifyTargetsCommand();
 +
 +            List<Map<String, String>> targets = new ArrayList<>();
 +
 +            Map<String, String> target = new HashMap<>();
 +
 +            target.put(ModifyTargetsCommand.STORAGE_HOST, storageAddress);
 +            target.put(ModifyTargetsCommand.STORAGE_PORT, String.valueOf(storagePort));
 +            target.put(ModifyTargetsCommand.IQN, iScsiName);
 +
 +            targets.add(target);
 +
 +            cmd.setTargets(targets);
 +            cmd.setApplyToAllHostsInCluster(true);
 +            cmd.setAdd(false);
 +            cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC);
 +
 +            sendModifyTargetsCommand(cmd, hostId);
 +        }
 +    }
 +
 +    private void sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) {
 +        Answer answer = _agentMgr.easySend(hostId, cmd);
 +
 +        if (answer == null) {
 +            String msg = "Unable to get an answer to the modify targets command";
 +
 +            s_logger.warn(msg);
 +        } else if (!answer.getResult()) {
 +            String msg = "Unable to modify target on the following host: " + hostId;
 +
 +            s_logger.warn(msg);
 +        }
 +    }
 +
 +    @DB
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_MIGRATE, eventDescription = "migrating volume", async = true)
 +    public Volume migrateVolume(MigrateVolumeCmd cmd) {
 +        Long volumeId = cmd.getVolumeId();
 +        Long storagePoolId = cmd.getStoragePoolId();
 +
 +        VolumeVO vol = _volsDao.findById(volumeId);
 +        if (vol == null) {
 +            throw new InvalidParameterValueException("Failed to find the volume id: " + volumeId);
 +        }
 +
 +        if (vol.getState() != Volume.State.Ready) {
 +            throw new InvalidParameterValueException("Volume must be in ready state");
 +        }
 +
 +        boolean liveMigrateVolume = false;
 +        Long instanceId = vol.getInstanceId();
 +        Long srcClusterId = null;
 +        VMInstanceVO vm = null;
 +        if (instanceId != null) {
 +            vm = _vmInstanceDao.findById(instanceId);
 +        }
 +
 +        // Check that Vm to which this volume is attached does not have VM Snapshots
 +        // OfflineVmwareMigration: considder if this is needed and desirable
 +        if (vm != null && _vmSnapshotDao.findByVm(vm.getId()).size() > 0) {
 +            throw new InvalidParameterValueException("Volume cannot be migrated, please remove all VM snapshots for VM to which this volume is attached");
 +        }
 +
 +        // OfflineVmwareMigration: extract this block as method and check if it is subject to regression
 +        if (vm != null && vm.getState() == State.Running) {
 +            // Check if the VM is GPU enabled.
 +            if (_serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) {
 +                throw new InvalidParameterValueException("Live Migration of GPU enabled VM is not supported");
 +            }
 +            // Check if the underlying hypervisor supports storage motion.
 +            Long hostId = vm.getHostId();
 +            if (hostId != null) {
 +                HostVO host = _hostDao.findById(hostId);
 +                HypervisorCapabilitiesVO capabilities = null;
 +                if (host != null) {
 +                    capabilities = _hypervisorCapabilitiesDao.findByHypervisorTypeAndVersion(host.getHypervisorType(), host.getHypervisorVersion());
 +                    srcClusterId = host.getClusterId();
 +                }
 +
 +                if (capabilities != null) {
 +                    liveMigrateVolume = capabilities.isStorageMotionSupported();
 +                }
 +            }
 +
 +            // If vm is running, and hypervisor doesn't support live migration, then return error
 +            if (!liveMigrateVolume) {
 +                throw new InvalidParameterValueException("Volume needs to be detached from VM");
 +            }
 +        }
 +
 +        if (liveMigrateVolume && !cmd.isLiveMigrate()) {
 +            throw new InvalidParameterValueException("The volume " + vol + "is attached to a vm and for migrating it " + "the parameter livemigrate should be specified");
 +        }
 +
 +        StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary);
 +        if (destPool == null) {
 +            throw new InvalidParameterValueException("Failed to find the destination storage pool: " + storagePoolId);
 +        } else if (destPool.isInMaintenance()) {
 +            throw new InvalidParameterValueException("Cannot migrate volume " + vol + "to the destination storage pool " + destPool.getName() + " as the storage pool is in maintenance mode.");
 +        }
 +
 +        if (!storageMgr.storagePoolHasEnoughSpace(Collections.singletonList(vol), destPool)) {
 +            throw new CloudRuntimeException("Storage pool " + destPool.getName() + " does not have enough space to migrate volume " + vol.getName());
 +        }
 +
 +        // OfflineVmwareMigration: check storage tags on disk(offering)s in comparison to destination storage pool
 +        // OfflineVmwareMigration: if no match return a proper error now
 +        DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId());
 +        if(diskOffering.equals(null)) {
 +            throw new CloudRuntimeException("volume '" + vol.getUuid() +"', has no diskoffering. Migration target cannot be checked.");
 +        }
 +        if(! doesTargetStorageSupportDiskOffering(destPool, diskOffering)) {
 +            throw new CloudRuntimeException("Migration target has no matching tags for volume '" +vol.getName() + "(" + vol.getUuid() + ")'");
 +        }
 +
 +        if (liveMigrateVolume && destPool.getClusterId() != null && srcClusterId != null) {
 +            if (!srcClusterId.equals(destPool.getClusterId())) {
 +                throw new InvalidParameterValueException("Cannot migrate a volume of a virtual machine to a storage pool in a different cluster");
 +            }
 +        }
 +        // In case of VMware, if ROOT volume is being cold-migrated, then ensure destination storage pool is in the same Datacenter as the VM.
 +        if (vm != null && vm.getHypervisorType().equals(HypervisorType.VMware)) {
 +            if (!liveMigrateVolume && vol.volumeType.equals(Volume.Type.ROOT)) {
 +                Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId();
 +                HostVO host = _hostDao.findById(hostId);
 +                if (host != null) {
 +                    srcClusterId = host.getClusterId();
 +                }
 +                if (srcClusterId != null && destPool.getClusterId() != null && !srcClusterId.equals(destPool.getClusterId())) {
 +                    String srcDcName = _clusterDetailsDao.getVmwareDcName(srcClusterId);
 +                    String destDcName = _clusterDetailsDao.getVmwareDcName(destPool.getClusterId());
 +                    if (srcDcName != null && destDcName != null && !srcDcName.equals(destDcName)) {
 +                        throw new InvalidParameterValueException("Cannot migrate ROOT volume of a stopped VM to a storage pool in a different VMware datacenter");
 +                    }
 +                }
 +                updateMissingRootDiskController(vm, vol.getChainInfo());
 +            }
 +        }
 +        DiskOfferingVO newDiskOffering = retrieveAndValidateNewDiskOffering(cmd);
 +        validateConditionsToReplaceDiskOfferingOfVolume(vol, newDiskOffering, destPool);
 +        if (vm != null) {
 +            // serialize VM operation
 +            AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
 +            if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
 +                // avoid re-entrance
 +
 +                VmWorkJobVO placeHolder = null;
 +                placeHolder = createPlaceHolderWork(vm.getId());
 +                try {
 +                    return orchestrateMigrateVolume(vol, destPool, liveMigrateVolume, newDiskOffering);
 +                } finally {
 +                    _workJobDao.expunge(placeHolder.getId());
 +                }
 +
 +            } else {
 +                Outcome<Volume> outcome = migrateVolumeThroughJobQueue(vm, vol, destPool, liveMigrateVolume, newDiskOffering);
 +
 +                try {
 +                    outcome.get();
 +                } catch (InterruptedException e) {
 +                    throw new RuntimeException("Operation is interrupted", e);
 +                } catch (java.util.concurrent.ExecutionException e) {
 +                    throw new RuntimeException("Execution excetion", e);
 +                }
 +
 +                Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
 +                if (jobResult != null) {
 +                    if (jobResult instanceof ConcurrentOperationException) {
 +                        throw (ConcurrentOperationException)jobResult;
 +                    } else if (jobResult instanceof RuntimeException) {
 +                        throw (RuntimeException)jobResult;
 +                    } else if (jobResult instanceof Throwable) {
 +                        throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
 +                    }
 +                }
 +
 +                // retrieve the migrated new volume from job result
 +                if (jobResult != null && jobResult instanceof Long) {
 +                    return _entityMgr.findById(VolumeVO.class, ((Long)jobResult));
 +                }
 +                return null;
 +            }
 +        }
 +
 +        return orchestrateMigrateVolume(vol, destPool, liveMigrateVolume, newDiskOffering);
 +    }
 +
 +    /**
 +     * Retrieves the new disk offering UUID that might be sent to replace the current one in the volume being migrated.
 +     * If no disk offering UUID is provided we return null. Otherwise, we perform the following checks.
 +     * <ul>
 +     *  <li>Is the disk offering UUID entered valid? If not, an  {@link InvalidParameterValueException} is thrown;
 +     *  <li>If the disk offering was already removed, we thrown an {@link InvalidParameterValueException} is thrown;
 +     *  <li>We then check if the user executing the operation has access to the given disk offering.
 +     * </ul>
 +     *
 +     * If all checks pass, we move forward returning the disk offering object.
 +     */
 +    private DiskOfferingVO retrieveAndValidateNewDiskOffering(MigrateVolumeCmd cmd) {
 +        String newDiskOfferingUuid = cmd.getNewDiskOfferingUuid();
 +        if (org.apache.commons.lang.StringUtils.isBlank(newDiskOfferingUuid)) {
 +            return null;
 +        }
 +        DiskOfferingVO newDiskOffering = _diskOfferingDao.findByUuid(newDiskOfferingUuid);
 +        if (newDiskOffering == null) {
 +            throw new InvalidParameterValueException(String.format("The disk offering informed is not valid [id=%s].", newDiskOfferingUuid));
 +        }
 +        if (newDiskOffering.getRemoved() != null) {
 +            throw new InvalidParameterValueException(String.format("We cannot assign a removed disk offering [id=%s] to a volume. ", newDiskOffering.getUuid()));
 +        }
 +        Account caller = CallContext.current().getCallingAccount();
 +        _accountMgr.checkAccess(caller, newDiskOffering);
 +        return newDiskOffering;
 +    }
 +
 +    /**
 +     * Performs the validations required for replacing the disk offering while migrating the volume of storage. If no new disk offering is provided, we do not execute any validation.
 +     * If a disk offering is informed, we then proceed with the following checks.
 +     * <ul>
 +     *  <li>We check if the given volume is of ROOT type. We cannot change the disk offering of a ROOT volume. Therefore, we thrown an {@link InvalidParameterValueException};
 +     *  <li>We the disk is being migrated to shared storage and the new disk offering is for local storage (or vice versa), we throw an {@link InvalidParameterValueException}. Bear in mind that we are validating only the new disk offering. If none is provided we can override the current disk offering. This means, placing a volume with shared disk offering in local storage and vice versa;
 +     *  <li>We then proceed checking the target storage pool supports the new disk offering {@link #doesTargetStorageSupportNewDiskOffering(StoragePool, DiskOfferingVO)}.
 +     * </ul>
 +     *
 +     * If all of the above validations pass, we check if the size of the new disk offering is different from the volume. If it is, we log a warning message.
 +     */
 +    protected void validateConditionsToReplaceDiskOfferingOfVolume(VolumeVO volume, DiskOfferingVO newDiskOffering, StoragePool destPool) {
 +        if (newDiskOffering == null) {
 +            return;
 +        }
 +        if ((destPool.isShared() && newDiskOffering.isUseLocalStorage()) || destPool.isLocal() && newDiskOffering.isShared()) {
 +            throw new InvalidParameterValueException("You cannot move the volume to a shared storage and assing a disk offering for local storage and vice versa.");
 +        }
 +        if (!doesTargetStorageSupportDiskOffering(destPool, newDiskOffering)) {
 +            throw new InvalidParameterValueException(String.format("Target Storage [id=%s] tags [%s] does not match new disk offering [id=%s] tags [%s].", destPool.getUuid(),
 +                    getStoragePoolTags(destPool), newDiskOffering.getUuid(), newDiskOffering.getTags()));
 +        }
 +        if (volume.getSize() != newDiskOffering.getDiskSize()) {
 +            DiskOfferingVO oldDiskOffering = this._diskOfferingDao.findById(volume.getDiskOfferingId());
 +            s_logger.warn(String.format(
 +                    "You are migrating a volume [id=%s] and changing the disk offering[from id=%s to id=%s] to reflect this migration. However, the sizes of the volume and the new disk offering are different.",
 +                    volume.getUuid(), oldDiskOffering.getUuid(), newDiskOffering.getUuid()));
 +        }
 +        s_logger.info(String.format("Changing disk offering to [uuid=%s] while migrating volume [uuid=%s, name=%s].", newDiskOffering.getUuid(), volume.getUuid(), volume.getName()));
 +    }
 +
 +    /**
 +     *  Checks if the target storage supports the new disk offering.
 +     *  This validation is consistent with the mechanism used to select a storage pool to deploy a volume when a virtual machine is deployed or when a new data disk is allocated.
 +     *
 +     *  The scenarios when this method returns true or false is presented in the following table.
 +     *
 +     *   <table border="1">
 +     *      <tr>
 +     *          <th>#</th><th>Disk offering tags</th><th>Storage tags</th><th>Does the storage support the disk offering?</th>
 +     *      </tr>
 +     *      <body>
 +     *      <tr>
 +     *          <td>1</td><td>A,B</td><td>A</td><td>NO</td>
 +     *      </tr>
 +     *      <tr>
 +     *          <td>2</td><td>A,B,C</td><td>A,B,C,D,X</td><td>YES</td>
 +     *      </tr>
 +     *      <tr>
 +     *          <td>3</td><td>A,B,C</td><td>X,Y,Z</td><td>NO</td>
 +     *      </tr>
 +     *      <tr>
 +     *          <td>4</td><td>null</td><td>A,S,D</td><td>YES</td>
 +     *      </tr>
 +     *      <tr>
 +     *          <td>5</td><td>A</td><td>null</td><td>NO</td>
 +     *      </tr>
 +     *      <tr>
 +     *          <td>6</td><td>null</td><td>null</td><td>YES</td>
 +     *      </tr>
 +     *      </body>
 +     *   </table>
 +     */
 +    protected boolean doesTargetStorageSupportDiskOffering(StoragePool destPool, DiskOfferingVO diskOffering) {
 +        String targetStoreTags = diskOffering.getTags();
 +        return doesTargetStorageSupportDiskOffering(destPool, targetStoreTags);
 +    }
 +
 +    @Override
 +    public boolean doesTargetStorageSupportDiskOffering(StoragePool destPool, String diskOfferingTags) {
 +        if (org.apache.commons.lang.StringUtils.isBlank(diskOfferingTags)) {
 +            return true;
 +        }
 +        String storagePoolTags = getStoragePoolTags(destPool);
 +        if (org.apache.commons.lang.StringUtils.isBlank(storagePoolTags)) {
 +            return false;
 +        }
 +        String[] storageTagsAsStringArray = org.apache.commons.lang.StringUtils.split(storagePoolTags, ",");
 +        String[] newDiskOfferingTagsAsStringArray = org.apache.commons.lang.StringUtils.split(diskOfferingTags, ",");
 +
 +        return CollectionUtils.isSubCollection(Arrays.asList(newDiskOfferingTagsAsStringArray), Arrays.asList(storageTagsAsStringArray));
 +    }
 +
 +    /**
 +     *  Retrieves the storage pool tags as a {@link String}. If the storage pool does not have tags we return a null value.
 +     */
 +    protected String getStoragePoolTags(StoragePool destPool) {
 +        List<StoragePoolDetailVO> storagePoolDetails = storagePoolDetailsDao.listDetails(destPool.getId());
 +        if (CollectionUtils.isEmpty(storagePoolDetails)) {
 +            return null;
 +        }
 +        String storageTags = "";
 +        for (StoragePoolDetailVO storagePoolDetailVO : storagePoolDetails) {
 +            storageTags = storageTags + storagePoolDetailVO.getName() + ",";
 +        }
 +        return storageTags.substring(0, storageTags.length() - 1);
 +    }
 +
 +    private Volume orchestrateMigrateVolume(VolumeVO volume, StoragePool destPool, boolean liveMigrateVolume, DiskOfferingVO newDiskOffering) {
 +        Volume newVol = null;
 +        try {
 +            if (liveMigrateVolume) {
 +                newVol = liveMigrateVolume(volume, destPool);
 +            } else {
 +                newVol = _volumeMgr.migrateVolume(volume, destPool);
 +            }
 +            if (newDiskOffering != null) {
 +                _volsDao.updateDiskOffering(newVol.getId(), newDiskOffering.getId());
 +            }
 +        } catch (StorageUnavailableException e) {
 +            s_logger.debug("Failed to migrate volume", e);
 +            throw new CloudRuntimeException(e.getMessage());
 +        } catch (Exception e) {
 +            s_logger.debug("Failed to migrate volume", e);
 +            throw new CloudRuntimeException(e.getMessage());
 +        }
 +        return newVol;
 +    }
 +
 +    @DB
 +    protected Volume liveMigrateVolume(Volume volume, StoragePool destPool) throws StorageUnavailableException {
 +        VolumeInfo vol = volFactory.getVolume(volume.getId());
 +
 +        DataStore dataStoreTarget = dataStoreMgr.getDataStore(destPool.getId(), DataStoreRole.Primary);
 +        AsyncCallFuture<VolumeApiResult> future = volService.migrateVolume(vol, dataStoreTarget);
 +        try {
 +            VolumeApiResult result = future.get();
 +            if (result.isFailed()) {
 +                s_logger.debug("migrate volume failed:" + result.getResult());
 +                throw new StorageUnavailableException("Migrate volume failed: " + result.getResult(), destPool.getId());
 +            }
 +            return result.getVolume();
 +        } catch (InterruptedException e) {
 +            s_logger.debug("migrate volume failed", e);
 +            throw new CloudRuntimeException(e.getMessage());
 +        } catch (ExecutionException e) {
 +            s_logger.debug("migrate volume failed", e);
 +            throw new CloudRuntimeException(e.getMessage());
 +        }
 +    }
 +
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "taking snapshot", async = true)
 +    public Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup)
 +            throws ResourceAllocationException {
 +        VolumeInfo volume = volFactory.getVolume(volumeId);
 +        if (volume == null) {
 +            throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist");
 +        }
 +
 +        if (volume.getState() != Volume.State.Ready) {
 +            throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot.");
 +        }
 +
 +        StoragePoolVO storagePoolVO = _storagePoolDao.findById(volume.getPoolId());
 +
 +        if (storagePoolVO.isManaged() && locationType == null) {
 +            locationType = Snapshot.LocationType.PRIMARY;
 +        }
 +
 +        VMInstanceVO vm = null;
 +        if (volume.getInstanceId() != null) {
 +            vm = _vmInstanceDao.findById(volume.getInstanceId());
 +        }
 +
 +        if (vm != null) {
 +            // serialize VM operation
 +            AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
 +            if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
 +                // avoid re-entrance
 +
 +                VmWorkJobVO placeHolder = null;
 +                placeHolder = createPlaceHolderWork(vm.getId());
 +                try {
 +                    return orchestrateTakeVolumeSnapshot(volumeId, policyId, snapshotId, account, quiescevm, locationType, asyncBackup);
 +                } finally {
 +                    _workJobDao.expunge(placeHolder.getId());
 +                }
 +
 +            } else {
 +                Outcome<Snapshot> outcome = takeVolumeSnapshotThroughJobQueue(vm.getId(), volumeId, policyId, snapshotId, account.getId(), quiescevm, locationType, asyncBackup);
 +
 +                try {
 +                    outcome.get();
 +                } catch (InterruptedException e) {
 +                    throw new RuntimeException("Operation is interrupted", e);
 +                } catch (java.util.concurrent.ExecutionException e) {
 +                    throw new RuntimeException("Execution excetion", e);
 +                }
 +
 +                Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
 +                if (jobResult != null) {
 +                    if (jobResult instanceof ConcurrentOperationException) {
 +                        throw (ConcurrentOperationException)jobResult;
 +                    } else if (jobResult instanceof ResourceAllocationException) {
 +                        throw (ResourceAllocationException)jobResult;
 +                    } else if (jobResult instanceof Throwable) {
 +                        throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
 +                    }
 +                }
 +
 +                return _snapshotDao.findById(snapshotId);
 +            }
 +        } else {
 +            CreateSnapshotPayload payload = new CreateSnapshotPayload();
 +            payload.setSnapshotId(snapshotId);
 +            payload.setSnapshotPolicyId(policyId);
 +            payload.setAccount(account);
 +            payload.setQuiescevm(quiescevm);
 +            payload.setAsyncBackup(asyncBackup);
 +            volume.addPayload(payload);
 +            return volService.takeSnapshot(volume);
 +        }
 +    }
 +
 +    private Snapshot orchestrateTakeVolumeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup)
 +            throws ResourceAllocationException {
 +
 +        VolumeInfo volume = volFactory.getVolume(volumeId);
 +
 +        if (volume == null) {
 +            throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist");
 +        }
 +
 +        if (volume.getState() != Volume.State.Ready) {
 +            throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot.");
 +        }
 +
 +        CreateSnapshotPayload payload = new CreateSnapshotPayload();
 +
 +        payload.setSnapshotId(snapshotId);
 +        payload.setSnapshotPolicyId(policyId);
 +        payload.setAccount(account);
 +        payload.setQuiescevm(quiescevm);
 +        payload.setLocationType(locationType);
 +        payload.setAsyncBackup(asyncBackup);
 +        volume.addPayload(payload);
 +
 +        return volService.takeSnapshot(volume);
 +    }
 +
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_SNAPSHOT_CREATE, eventDescription = "allocating snapshot", create = true)
 +    public Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException {
 +        Account caller = CallContext.current().getCallingAccount();
 +
 +        VolumeInfo volume = volFactory.getVolume(volumeId);
 +        if (volume == null) {
 +            throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist");
 +        }
 +        DataCenter zone = _dcDao.findById(volume.getDataCenterId());
 +        if (zone == null) {
 +            throw new InvalidParameterValueException("Can't find zone by id " + volume.getDataCenterId());
 +        }
 +
 +        if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
 +            throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zone.getName());
 +        }
 +
 +        if (volume.getState() != Volume.State.Ready) {
 +            throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot.");
 +        }
 +
 +        if (ImageFormat.DIR.equals(volume.getFormat())) {
 +            throw new InvalidParameterValueException("Snapshot not supported for volume:" + volumeId);
 +        }
 +
 +        if (volume.getTemplateId() != null) {
 +            VMTemplateVO template = _templateDao.findById(volume.getTemplateId());
 +            if (template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM) {
 +                throw new InvalidParameterValueException("VolumeId: " + volumeId + " is for System VM , Creating snapshot against System VM volumes is not supported");
 +            }
 +        }
 +
 +        StoragePoolVO storagePoolVO = _storagePoolDao.findById(volume.getPoolId());
 +
 +        if (!storagePoolVO.isManaged() && locationType != null) {
 +            throw new InvalidParameterValueException("VolumeId: " + volumeId + " LocationType is supported only for managed storage");
 +        }
 +
 +        if (storagePoolVO.isManaged() && locationType == null) {
 +            locationType = Snapshot.LocationType.PRIMARY;
 +        }
 +
 +        StoragePool storagePool = (StoragePool)volume.getDataStore();
 +        if (storagePool == null) {
 +            throw new InvalidParameterValueException("VolumeId: " + volumeId + " please attach this volume to a VM before create snapshot for it");
 +        }
 +
 +        return snapshotMgr.allocSnapshot(volumeId, policyId, snapshotName, locationType);
 +    }
 +
 +    @Override
 +    public Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName) throws ResourceAllocationException {
 +        Account caller = CallContext.current().getCallingAccount();
 +        VMInstanceVO vm = _vmInstanceDao.findById(vmId);
 +        if (vm == null) {
 +            throw new InvalidParameterValueException("Creating snapshot failed due to vm:" + vmId + " doesn't exist");
 +        }
 +        _accountMgr.checkAccess(caller, null, true, vm);
 +
 +        VolumeInfo volume = volFactory.getVolume(volumeId);
 +        if (volume == null) {
 +            throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist");
 +        }
 +        _accountMgr.checkAccess(caller, null, true, volume);
 +        VirtualMachine attachVM = volume.getAttachedVM();
 +        if (attachVM == null || attachVM.getId() != vm.getId()) {
 +            throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't attach to vm :" + vm);
 +        }
 +
 +        DataCenter zone = _dcDao.findById(volume.getDataCenterId());
 +        if (zone == null) {
 +            throw new InvalidParameterValueException("Can't find zone by id " + volume.getDataCenterId());
 +        }
 +
 +        if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
 +            throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zone.getName());
 +        }
 +
 +        if (volume.getState() != Volume.State.Ready) {
 +            throw new InvalidParameterValueException("VolumeId: " + volumeId + " is not in " + Volume.State.Ready + " state but " + volume.getState() + ". Cannot take snapshot.");
 +        }
 +
 +        if (volume.getTemplateId() != null) {
 +            VMTemplateVO template = _templateDao.findById(volume.getTemplateId());
 +            if (template != null && template.getTemplateType() == Storage.TemplateType.SYSTEM) {
 +                throw new InvalidParameterValueException("VolumeId: " + volumeId + " is for System VM , Creating snapshot against System VM volumes is not supported");
 +            }
 +        }
 +
 +        StoragePool storagePool = (StoragePool)volume.getDataStore();
 +        if (storagePool == null) {
 +            throw new InvalidParameterValueException("VolumeId: " + volumeId + " please attach this volume to a VM before create snapshot for it");
 +        }
 +
 +        return snapshotMgr.allocSnapshot(volumeId, Snapshot.MANUAL_POLICY_ID, snapshotName, null);
 +    }
 +
 +    @Override
 +    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_EXTRACT, eventDescription = "extracting volume", async = true)
 +    public String extractVolume(ExtractVolumeCmd cmd) {
 +        Long volumeId = cmd.getId();
 +        Long zoneId = cmd.getZoneId();
 +        String mode = cmd.getMode();
 +        Account account = CallContext.current().getCallingAccount();
 +
 +        if (!_accountMgr.isRootAdmin(account.getId()) && ApiDBUtils.isExtractionDisabled()) {
 +            throw new PermissionDeniedException("Extraction has been disabled by admin");
 +        }
 +
 +        VolumeVO volume = _volsDao.findById(volumeId);
 +        if (volume == null) {
 +            InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find volume with specified volumeId");
 +            ex.addProxyObject(volumeId.toString(), "volumeId");
 +            throw ex;
 +        }
 +
 +        // perform permission check
 +        _accountMgr.checkAccess(account, null, true, volume);
 +
 +        if (_dcDao.findById(zoneId) == null) {
 +            throw new InvalidParameterValueException("Please specify a valid zone.");
 +        }
 +        if (volume.getPoolId() == null) {
 +            throw new InvalidParameterValueException("The volume doesn't belong to a storage pool so can't extract it");
 +        }
 +        // Extract activity only for detached volumes or for volumes whose
 +        // instance is stopped
 +        if (volume.getInstanceId() != null && ApiDBUtils.findVMInstanceById(volume.getInstanceId()).getState() != State.Stopped) {
 +            s_logger.debug("Invalid state of the volume with ID: " + volumeId + ". It should be either detached or the VM should be in stopped state.");
 +            PermissionDeniedException ex = new PermissionDeniedException("Invalid state of the volume with specified ID. It should be either detached or the VM should be in stopped state.");
 +            ex.addProxyObject(volume.getUuid(), "volumeId");
 +            throw ex;
 +        }
 +
 +        if (volume.getVolumeType() != Volume.Type.DATADISK) {
 +            // Datadisk dont have any template dependence.
 +
 +            VMTemplateVO template = ApiDBUtils.findTemplateById(volume.getTemplateId());
 +            if (template != null) { // For ISO based volumes template = null and
 +                // we allow extraction of all ISO based
 +                // volumes
 +                boolean isExtractable = template.isExtractable() && template.getTemplateType() != Storage.TemplateType.SYSTEM;
 +                if (!isExtractable && account != null && !_accountMgr.isRootAdmin(account.getId())) {
 +                    // Global admins are always allowed to extract
 +                    PermissionDeniedException ex = new PermissionDeniedException("The volume with specified volumeId is not allowed to be extracted");
 +                    ex.addProxyObject(volume.getUuid(), "volumeId");
 +                    throw ex;
 +                }
 +            }
 +        }
 +
 +        if (mode == null || (!mode.equals(Upload.Mode.FTP_UPLOAD.toString()) && !mode.equals(Upload.Mode.HTTP_DOWNLOAD.toString()))) {
 +            throw new InvalidParameterValueException("Please specify a valid extract Mode ");
 +        }
 +
 +        // Check if the url already exists
 +        VolumeDataStoreVO volumeStoreRef = _volumeStoreDao.findByVolume(volumeId);
 +        if (volumeStoreRef != null && volumeStoreRef.getExtractUrl() != null) {
 +            return volumeStoreRef.getExtractUrl();
 +        }
 +
 +        VMInstanceVO vm = null;
 +        if (volume.getInstanceId() != null) {
 +            vm = _vmInstanceDao.findById(volume.getInstanceId());
 +        }
 +
 +        if (vm != null) {
 +            // serialize VM operation
 +            AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
 +            if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
 +                // avoid re-entrance
 +
 +                VmWorkJobVO placeHolder = null;
 +                placeHolder = createPlaceHolderWork(vm.getId());
 +                try {
 +                    return orchestrateExtractVolume(volume.getId(), zoneId);
 +                } finally {
 +                    _workJobDao.expunge(placeHolder.getId());
 +                }
 +
 +            } else {
 +                Outcome<String> outcome = extractVolumeThroughJobQueue(vm.getId(), volume.getId(), zoneId);
 +
 +                try {
 +                    outcome.get();
 +                } catch (InterruptedException e) {
 +                    throw new RuntimeException("Operation is interrupted", e);
 +                } catch (java.util.concurrent.ExecutionException e) {
 +                    throw new RuntimeException("Execution excetion", e);
 +                }
 +
 +                Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
 +                if (jobResult != null) {
 +                    if (jobResult instanceof ConcurrentOperationException) {
 +                        throw (ConcurrentOperationException)jobResult;
 +                    } else if (jobResult instanceof RuntimeException) {
 +                        throw (RuntimeException)jobResult;
 +                    } else if (jobResult instanceof Throwable) {
 +                        throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
 +                    }
 +                }
 +
 +                // retrieve the entity url from job result
 +                if (jobResult != null && jobResult instanceof String) {
 +                    return (String)jobResult;
 +                }
 +                return null;
 +            }
 +        }
 +
 +        return orchestrateExtractVolume(volume.getId(), zoneId);
 +    }
 +
 +    private String orchestrateExtractVolume(long volumeId, long zoneId) {
 +        // get latest volume state to make sure that it is not updated by other parallel operations
 +        VolumeVO volume = _volsDao.findById(volumeId);
 +        if (volume == null || volume.getState() != Volume.State.Ready) {
 +            throw new InvalidParameterValueException("Volume to be extracted has been removed or not in right state!");
 +        }
 +        // perform extraction
 +        ImageStoreEntity secStore = (ImageStoreEntity)dataStoreMgr.getImageStore(zoneId);
 +        String value = _configDao.getValue(Config.CopyVolumeWait.toString());
 +        NumbersUtil.parseInt(value, Integer.parseInt(Config.CopyVolumeWait.getDefaultValue()));
 +
 +        // Copy volume from primary to secondary storage
 +        VolumeInfo srcVol = volFactory.getVolume(volumeId);
 +        AsyncCallFuture<VolumeApiResult> cvAnswer = volService.copyVolume(srcVol, secStore);
 +        // Check if you got a valid answer.
 +        VolumeApiResult cvResult = null;
 +        try {
 +            cvResult = cvAnswer.get();
 +        } catch (InterruptedException e1) {
 +            s_logger.debug("failed copy volume", e1);
 +            throw new CloudRuntimeException("Failed to copy volume", e1);
 +        } catch (ExecutionException e1) {
 +            s_logger.debug("failed copy volume", e1);
 +            throw new CloudRuntimeException("Failed to copy volume", e1);
 +        }
 +        if (cvResult == null || cvResult.isFailed()) {
 +            String errorString = "Failed to copy the volume from the source primary storage pool to secondary storage.";
 +            throw new CloudRuntimeException(errorString);
 +        }
 +
 +        VolumeInfo vol = cvResult.getVolume();
 +
 +        String extractUrl = secStore.createEntityExtractUrl(vol.getPath(), vol.getFormat(), vol);
 +        VolumeDataStoreVO volumeStoreRef = _volumeStoreDao.findByVolume(volumeId);
 +
 +        volumeStoreRef.setExtractUrl(extractUrl);
 +        volumeStoreRef.setExtractUrlCreated(DateUtil.now());
 +        volumeStoreRef.setDownloadState(VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
 +        volumeStoreRef.setDownloadPercent(100);
 +        volumeStoreRef.setZoneId(zoneId);
 +
 +        _volumeStoreDao.update(volumeStoreRef.getId(), volumeStoreRef);
 +
 +        return extractUrl;
 +    }
 +
 +    @Override
 +    public boolean isDisplayResourceEnabled(Long id) {
 +        Volume volume = _volsDao.findById(id);
 +        if (volume == null) {
 +            return true; // bad id given, default to true
 +        }
 +        return volume.isDisplayVolume();
 +    }
 +
 +    private boolean needMoveVolume(VolumeVO existingVolume, VolumeInfo newVolume) {
 +        if (existingVolume == null || existingVolume.getPoolId() == null || newVolume.getPoolId() == null) {
 +            return false;
 +        }
 +
 +        DataStore storeForExistingVol = dataStoreMgr.getPrimaryDataStore(existingVolume.getPoolId());
 +        DataStore storeForNewVol = dataStoreMgr.getPrimaryDataStore(newVolume.getPoolId());
 +
 +        Scope storeForExistingStoreScope = storeForExistingVol.getScope();
 +        if (storeForExistingStoreScope == null) {
 +            throw new CloudRuntimeException("Can't get scope of data store: " + storeForExistingVol.getId());
 +        }
 +
 +        Scope storeForNewStoreScope = storeForNewVol.getScope();
 +        if (storeForNewStoreScope == null) {
 +            throw new CloudRuntimeException("Can't get scope of data store: " + storeForNewVol.getId());
 +        }
 +
 +        if (storeForNewStoreScope.getScopeType() == ScopeType.ZONE) {
 +            return false;
 +        }
 +
 +        if (storeForExistingStoreScope.getScopeType() != storeForNewStoreScope.getScopeType()) {
 +            if (storeForNewStoreScope.getScopeType() == ScopeType.CLUSTER) {
 +                Long vmClusterId = null;
 +                if (storeForExistingStoreScope.getScopeType() == ScopeType.HOST) {
 +                    HostScope hs = (HostScope)storeForExistingStoreScope;
 +                    vmClusterId = hs.getClusterId();
 +                } else if (storeForExistingStoreScope.getScopeType() == ScopeType.ZONE) {
 +                    Long hostId = _vmInstanceDao.findById(existingVolume.getInstanceId()).getHostId();
 +                    if (hostId != null) {
 +                        HostVO host = _hostDao.findById(hostId);
 +                        vmClusterId = host.getClusterId();
 +                    }
 +                }
 +                if (storeForNewStoreScope.getScopeId().equals(vmClusterId)) {
 +                    return false;
 +                } else {
 +                    return true;
 +                }
 +            } else if (storeForNewStoreScope.getScopeType() == ScopeType.HOST
 +                    && (storeForExistingStoreScope.getScopeType() == ScopeType.CLUSTER || storeForExistingStoreScope.getScopeType() == ScopeType.ZONE)) {
 +                Long hostId = _vmInstanceDao.findById(existingVolume.getInstanceId()).getHostId();
 +                if (storeForNewStoreScope.getScopeId().equals(hostId)) {
 +                    return false;
 +                }
 +            }
 +            throw new InvalidParameterValueException("Can't move volume between scope: " + storeForNewStoreScope.getScopeType() + " and " + storeForExistingStoreScope.getScopeType());
 +        }
 +
 +        return !storeForExistingStoreScope.isSameScope(storeForNewStoreScope);
 +    }
 +
-     private synchronized void checkAndSetAttaching(Long volumeId, Long hostId) {
++    private synchronized void checkAndSetAttaching(Long volumeId) {
 +        VolumeInfo volumeToAttach = volFactory.getVolume(volumeId);
 +
 +        if (volumeToAttach.isAttachedVM()) {
 +            throw new CloudRuntimeException("volume: " + volumeToAttach.getName() + " is already attached to a VM: " + volumeToAttach.getAttachedVmName());
 +        }
-         if (volumeToAttach.getState().equals(Volume.State.Ready)) {
++
++        if (Volume.State.Allocated.equals(volumeToAttach.getState())) {
++            return;
++        }
++
++        if (Volume.State.Ready.equals(volumeToAttach.getState())) {
 +            volumeToAttach.stateTransit(Volume.Event.AttachRequested);
-         } else {
-             String error = null;
-             if (hostId == null) {
-                 error = "Please try attach operation after starting VM once";
-             } else {
-                 error = "Volume: " + volumeToAttach.getName() + " is in " + volumeToAttach.getState() + ". It should be in Ready state";
-             }
-             s_logger.error(error);
-             throw new CloudRuntimeException(error);
++            return;
 +        }
++
++        final String error = "Volume: " + volumeToAttach.getName() + " is in " + volumeToAttach.getState() + ". It should be in Ready or Allocated state";
++        s_logger.error(error);
++        throw new CloudRuntimeException(error);
 +    }
 +
 +    private void verifyManagedStorage(Long storagePoolId, Long hostId) {
 +        if (storagePoolId == null || hostId == null) {
 +            return;
 +        }
 +
 +        StoragePoolVO storagePoolVO = _storagePoolDao.findById(storagePoolId);
 +
 +        if (storagePoolVO == null || !storagePoolVO.isManaged()) {
 +            return;
 +        }
 +
 +        HostVO hostVO = _hostDao.findById(hostId);
 +
 +        if (hostVO == null) {
 +            return;
 +        }
 +
 +        if (!storageUtil.managedStoragePoolCanScale(storagePoolVO, hostVO.getClusterId(), hostVO.getId())) {
 +            throw new CloudRuntimeException("Insufficient number of available " + getNameOfClusteredFileSystem(hostVO));
 +        }
 +    }
 +
 +    private String getNameOfClusteredFileSystem(HostVO hostVO) {
 +        HypervisorType hypervisorType = hostVO.getHypervisorType();
 +
 +        if (HypervisorType.XenServer.equals(hypervisorType)) {
 +            return "SRs";
 +        }
 +
 +        if (HypervisorType.VMware.equals(hypervisorType)) {
 +            return "datastores";
 +        }
 +
 +        return "clustered file systems";
 +    }
 +
 +    private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, Long deviceId) {
 +        String errorMsg = "Failed to attach volume " + volumeToAttach.getName() + " to VM " + vm.getHostName();
 +        boolean sendCommand = vm.getState() == State.Running;
 +        AttachAnswer answer = null;
 +        Long hostId = vm.getHostId();
 +
 +        if (hostId == null) {
 +            hostId = vm.getLastHostId();
 +
 +            HostVO host = _hostDao.findById(hostId);
 +
 +            if (host != null && host.getHypervisorType() == HypervisorType.VMware) {
 +                sendCommand = true;
 +            }
 +        }
 +
 +        HostVO host = null;
 +        StoragePoolVO volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId());
 +
 +        if (hostId != null) {
 +            host = _hostDao.findById(hostId);
 +
 +            if (host != null && host.getHypervisorType() == HypervisorType.XenServer && volumeToAttachStoragePool != null && volumeToAttachStoragePool.isManaged()) {
 +                sendCommand = true;
 +            }
 +        }
 +
 +        verifyManagedStorage(volumeToAttachStoragePool.getId(), hostId);
 +
 +        // volumeToAttachStoragePool should be null if the VM we are attaching the disk to has never been started before
 +        DataStore dataStore = volumeToAttachStoragePool != null ? dataStoreMgr.getDataStore(volumeToAttachStoragePool.getId(), DataStoreRole.Primary) : null;
 +
-         checkAndSetAttaching(volumeToAttach.getId(), hostId);
++        checkAndSetAttaching(volumeToAttach.getId());
 +
 +        boolean attached = false;
 +        try {
 +            // if we don't have a host, the VM we are attaching the disk to has never been started before
 +            if (host != null) {
 +                try {
 +                    volService.grantAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
 +                } catch (Exception e) {
 +                    volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
 +
 +                    throw new CloudRuntimeException(e.getMessage());
 +                }
 +            }
 +
 +            if (sendCommand) {
 +                if (host != null && host.getHypervisorType() == HypervisorType.KVM && volumeToAttachStoragePool.isManaged() && volumeToAttach.getPath() == null) {
 +                    volumeToAttach.setPath(volumeToAttach.get_iScsiName());
 +
 +                    _volsDao.update(volumeToAttach.getId(), volumeToAttach);
 +                }
 +
 +                DataTO volTO = volFactory.getVolume(volumeToAttach.getId()).getTO();
 +
 +                deviceId = getDeviceId(vm, deviceId);
 +
 +                DiskTO disk = storageMgr.getDiskWithThrottling(volTO, volumeToAttach.getVolumeType(), deviceId, volumeToAttach.getPath(), vm.getServiceOfferingId(),
 +                        volumeToAttach.getDiskOfferingId());
 +
 +                AttachCommand cmd = new AttachCommand(disk, vm.getInstanceName());
 +
 +                ChapInfo chapInfo = volService.getChapInfo(volFactory.getVolume(volumeToAttach.getId()), dataStore);
 +
 +                Map<String, String> details = new HashMap<String, String>();
 +
 +                disk.setDetails(details);
 +
 +                details.put(DiskTO.MANAGED, String.valueOf(volumeToAttachStoragePool.isManaged()));
 +                details.put(DiskTO.STORAGE_HOST, volumeToAttachStoragePool.getHostAddress());
 +                details.put(DiskTO.STORAGE_PORT, String.valueOf(volumeToAttachStoragePool.getPort()));
 +                details.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeToAttach.getSize()));
 +                details.put(DiskTO.IQN, volumeToAttach.get_iScsiName());
 +                details.put(DiskTO.MOUNT_POINT, volumeToAttach.get_iScsiName());
 +                details.put(DiskTO.PROTOCOL_TYPE, (volumeToAttach.getPoolType() != null) ? volumeToAttach.getPoolType().toString() : null);
 +
 +                if (chapInfo != null) {
 +                    details.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername());
 +                    details.put(DiskTO.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret());
 +                    details.put(DiskTO.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername());
 +                    details.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret());
 +                }
 +                _userVmDao.loadDetails(vm);
 +                Map<String, String> controllerInfo = new HashMap<String, String>();
 +                controllerInfo.put(VmDetailConstants.ROOT_DISK_CONTROLLER, vm.getDetail(VmDetailConstants.ROOT_DISK_CONTROLLER));
 +                controllerInfo.put(VmDetailConstants.DATA_DISK_CONTROLLER, vm.getDetail(VmDetailConstants.DATA_DISK_CONTROLLER));
 +                cmd.setControllerInfo(controllerInfo);
 +                s_logger.debug("Attach volume id:" + volumeToAttach.getId() + " on VM id:" + vm.getId() + " has controller info:" + controllerInfo);
 +
 +                try {
 +                    answer = (AttachAnswer)_agentMgr.send(hostId, cmd);
 +                } catch (Exception e) {
 +                    if (host != null) {
 +                        volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
 +                    }
 +                    throw new CloudRuntimeException(errorMsg + " due to: " + e.getMessage());
 +                }
 +            }
 +
 +            if (!sendCommand || (answer != null && answer.getResult())) {
 +                // Mark the volume as attached
 +                if (sendCommand) {
 +                    DiskTO disk = answer.getDisk();
 +
 +                    _volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), disk.getDiskSeq());
 +
 +                    volumeToAttach = _volsDao.findById(volumeToAttach.getId());
 +
 +                    if (volumeToAttachStoragePool.isManaged() && volumeToAttach.getPath() == null) {
 +                        volumeToAttach.setPath(answer.getDisk().getPath());
 +
 +                        _volsDao.update(volumeToAttach.getId(), volumeToAttach);
 +                    }
 +                } else {
 +                    deviceId = getDeviceId(vm, deviceId);
 +
 +                    _volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), deviceId);
 +
 +                    volumeToAttach = _volsDao.findById(volumeToAttach.getId());
 +
-                     if (vm.getHypervisorType() == HypervisorType.KVM && volumeToAttachStoragePool.isManaged() && volumeToAttach.getPath() == null) {
++                    if (vm.getHypervisorType() == HypervisorType.KVM &&
++                            volumeToAttachStoragePool != null && volumeToAttachStoragePool.isManaged() &&
++                            volumeToAttach.getPath() == null && volumeToAttach.get_iScsiName() != null) {
 +                        volumeToAttach.setPath(volumeToAttach.get_iScsiName());
- 
 +                        _volsDao.update(volumeToAttach.getId(), volumeToAttach);
 +                    }
 +                }
 +
 +                // insert record for disk I/O statistics
 +                VmDiskStatisticsVO diskstats = _vmDiskStatsDao.findBy(vm.getAccountId(), vm.getDataCenterId(), vm.getId(), volumeToAttach.getId());
 +                if (diskstats == null) {
 +                    diskstats = new VmDiskStatisticsVO(vm.getAccountId(), vm.getDataCenterId(), vm.getId(), volumeToAttach.getId());
 +                    _vmDiskStatsDao.persist(diskstats);
 +                }
 +
 +                attached = true;
 +            } else {
 +                if (answer != null) {
 +                    String details = answer.getDetails();
 +                    if (details != null && !details.isEmpty()) {
 +                        errorMsg += "; " + details;
 +                    }
 +                }
 +                if (host != null) {
 +                    volService.revokeAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
 +                }
 +                throw new CloudRuntimeException(errorMsg);
 +            }
 +        } finally {
 +            Volume.Event ev = Volume.Event.OperationFailed;
 +            VolumeInfo volInfo = volFactory.getVolume(volumeToAttach.getId());
 +            if (attached) {
 +                ev = Volume.Event.OperationSucceeded;
 +                s_logger.debug("Volume: " + volInfo.getName() + " successfully attached to VM: " + volInfo.getAttachedVmName());
 +            } else {
 +                s_logger.debug("Volume: " + volInfo.getName() + " failed to attach to VM: " + volInfo.getAttachedVmName());
 +            }
 +            volInfo.stateTransit(ev);
 +        }
 +        return _volsDao.findById(volumeToAttach.getId());
 +    }
 +
 +    private int getMaxDataVolumesSupported(UserVmVO vm) {
 +        Long hostId = vm.getHostId();
 +        if (hostId == null) {
 +            hostId = vm.getLastHostId();
 +        }
 +        HostVO host = _hostDao.findById(hostId);
 +        Integer maxDataVolumesSupported = null;
 +        if (host != null) {
 +            _hostDao.loadDetails(host);
 +            maxDataVolumesSupported = _hypervisorCapabilitiesDao.getMaxDataVolumesLimit(host.getHypervisorType(), host.getDetail("product_version"));
 +        }
 +        if (maxDataVolumesSupported == null || maxDataVolumesSupported.intValue() <= 0) {
 +            maxDataVolumesSupported = 6; // 6 data disks by default if nothing
 +            // is specified in
 +            // 'hypervisor_capabilities' table
 +        }
 +
 +        return maxDataVolumesSupported.intValue();
 +    }
 +
 +    private Long getDeviceId(UserVmVO vm, Long deviceId) {
 +        // allocate deviceId
 +        int maxDevices = getMaxDataVolumesSupported(vm) + 2; // add 2 to consider devices root volume and cdrom
 +        int maxDeviceId = maxDevices - 1;
 +        List<VolumeVO> vols = _volsDao.findByInstance(vm.getId());
 +        if (deviceId != null) {
 +            if (deviceId.longValue() < 0 || deviceId.longValue() > maxDeviceId || deviceId.longValue() == 3) {
 +                throw new RuntimeException("deviceId should be 0,1,2,4-" + maxDeviceId);
 +            }
 +            for (VolumeVO vol : vols) {
 +                if (vol.getDeviceId().equals(deviceId)) {
 +                    throw new RuntimeException("deviceId " + deviceId + " is used by vm " + vm.getId());
 +                }
 +            }
 +        } else {
 +            // allocate deviceId here
 +            List<String> devIds = new ArrayList<String>();
 +            for (int i = 1; i <= maxDeviceId; i++) {
 +                devIds.add(String.valueOf(i));
 +            }
 +            devIds.remove("3");
 +            for (VolumeVO vol : vols) {
 +                devIds.remove(vol.getDeviceId().toString().trim());
 +            }
 +            if (devIds.isEmpty()) {
 +                throw new RuntimeException("All device Ids are used by vm " + vm.getId());
 +            }
 +            deviceId = Long.parseLong(devIds.iterator().next());
 +        }
 +
 +        return deviceId;
 +    }
 +
 +    @Override
 +    public boolean configure(String name, Map<String, Object> params) {
 +        String maxVolumeSizeInGbString = _configDao.getValue(Config.MaxVolumeSize.toString());
 +        _maxVolumeSizeInGb = NumbersUtil.parseLong(maxVolumeSizeInGbString, 2000);
 +        return true;
 +    }
 +
 +    public List<StoragePoolAllocator> getStoragePoolAllocators() {
 +        return _storagePoolAllocators;
 +    }
 +
 +    @Inject
 +    public void setStoragePoolAllocators(List<StoragePoolAllocator> storagePoolAllocators) {
 +        _storagePoolAllocators = storagePoolAllocators;
 +    }
 +
 +    public class VmJobVolumeUrlOutcome extends OutcomeImpl<String> {
 +        public VmJobVolumeUrlOutcome(final AsyncJob job) {
 +            super(String.class, job, VmJobCheckInterval.value(), new Predicate() {
 +                @Override
 +                public boolean checkCondition() {
 +                    AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId());
 +                    assert (jobVo != null);
 +                    if (jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS) {
 +                        return true;
 +                    }
 +
 +                    return false;
 +                }
 +            }, AsyncJob.Topics.JOB_STATE);
 +        }
 +    }
 +
 +    public class VmJobVolumeOutcome extends OutcomeImpl<Volume> {
 +        private long _volumeId;
 +
 +        public VmJobVolumeOutcome(final AsyncJob job, final long volumeId) {
 +            super(Volume.class, job, VmJobCheckInterval.value(), new Predicate() {
 +                @Override
 +                public boolean checkCondition() {
 +                    AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId());
 +                    assert (jobVo != null);
 +                    if (jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS) {
 +                        return true;
 +                    }
 +
 +                    return false;
 +                }
 +            }, AsyncJob.Topics.JOB_STATE);
 +            _volumeId = volumeId;
 +        }
 +
 +        @Override
 +        protected Volume retrieve() {
 +            return _volsDao.findById(_volumeId);
 +        }
 +    }
 +
 +    public class VmJobSnapshotOutcome extends OutcomeImpl<Snapshot> {
 +        private long _snapshotId;
 +
 +        public VmJobSnapshotOutcome(final AsyncJob job, final long snapshotId) {
 +            super(Snapshot.class, job, VmJobCheckInterval.value(), new Predicate() {
 +                @Override
 +                public boolean checkCondition() {
 +                    AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId());
 +                    assert (jobVo != null);
 +                    if (jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS) {
 +                        return true;
 +                    }
 +
 +                    return false;
 +                }
 +            }, AsyncJob.Topics.JOB_STATE);
 +            _snapshotId = snapshotId;
 +        }
 +
 +        @Override
 +        protected Snapshot retrieve() {
 +            return _snapshotDao.findById(_snapshotId);
 +        }
 +    }
 +
 +    public Outcome<Volume> attachVolumeToVmThroughJobQueue(final Long vmId, final Long volumeId, final Long deviceId) {
 +
 +        final CallContext context = CallContext.current();
 +        final User callingUser = context.getCallingUser();
 +        final Account callingAccount = context.getCallingAccount();
 +
 +        final VMInstanceVO vm = _vmInstanceDao.findById(vmId);
 +
 +        VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId());
 +
 +        workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER);
 +        workJob.setCmd(VmWorkAttachVolume.class.getName());
 +
 +        workJob.setAccountId(callingAccount.getId());
 +        workJob.setUserId(callingUser.getId());
 +        workJob.setStep(VmWorkJobVO.Step.Starting);
 +        workJob.setVmType(VirtualMachine.Type.Instance);
 +        workJob.setVmInstanceId(vm.getId());
 +        workJob.setRelated(AsyncJobExecutionContext.getOriginJobId());
 +
 +        // save work context info (there are some duplications)
 +        VmWorkAttachVolume workInfo = new VmWorkAttachVolume(callingUser.getId(), callingAccount.getId(), vm.getId(), VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, deviceId);
 +        workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo));
 +
 +        _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId());
 +
 +        AsyncJobVO jobVo = _jobMgr.getAsyncJob(workJob.getId());
 +        s_logger.debug("New job " + workJob.getId() + ", result field: " + jobVo.getResult());
 +
 +        AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId());
 +
 +        return new VmJobVolumeOutcome(workJob, volumeId);
 +    }
 +
 +    public Outcome<Volume> detachVolumeFromVmThroughJobQueue(final Long vmId, final Long volumeId) {
 +
 +        final CallContext context = CallContext.current();
 +        final User callingUser = context.getCallingUser();
 +        final Account callingAccount = context.getCallingAccount();
 +
 +        final VMInstanceVO vm = _vmInstanceDao.findById(vmId);
 +
 +        VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId());
 +
 +        workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER);
 +        workJob.setCmd(VmWorkDetachVolume.class.getName());
 +
 +        workJob.setAccountId(callingAccount.getId());
 +        workJob.setUserId(callingUser.getId());
 +        workJob.setStep(VmWorkJobVO.Step.Starting);
 +        workJob.setVmType(VirtualMachine.Type.Instance);
 +        workJob.setVmInstanceId(vm.getId());
 +        workJob.setRelated(AsyncJobExecutionContext.getOriginJobId());
 +
 +        // save work context info (there are some duplications)
 +        VmWorkDetachVolume workInfo = new VmWorkDetachVolume(callingUser.getId(), callingAccount.getId(), vm.getId(), VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId);
 +        workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo));
 +
 +        _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId());
 +
 +        AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId());
 +
 +        return new VmJobVolumeOutcome(workJob, volumeId);
 +    }
 +
 +    public Outcome<Volume> resizeVolumeThroughJobQueue(final Long vmId, final long volumeId, final long currentSize, final long newSize, final Long newMinIops, final Long newMaxIops,
 +            final Integer newHypervisorSnapshotReserve, final Long newServiceOfferingId, final boolean shrinkOk) {
 +        final CallContext context = CallContext.current();
 +        final User callingUser = context.getCallingUser();
 +        final Account callingAccount = context.getCallingAccount();
 +
 +        final VMInstanceVO vm = _vmInstanceDao.findById(vmId);
 +
 +        VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId());
 +
 +        workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER);
 +        workJob.setCmd(VmWorkResizeVolume.class.getName());
 +
 +        workJob.setAccountId(callingAccount.getId());
 +        workJob.setUserId(callingUser.getId());
 +        workJob.setStep(VmWorkJobVO.Step.Starting);
 +        workJob.setVmType(VirtualMachine.Type.Instance);
 +        workJob.setVmInstanceId(vm.getId());
 +        workJob.setRelated(AsyncJobExecutionContext.getOriginJobId());
 +
 +        // save work context info (there are some duplications)
 +        VmWorkResizeVolume workInfo = new VmWorkResizeVolume(callingUser.getId(), callingAccount.getId(), vm.getId(), VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, currentSize, newSize,
 +                newMinIops, newMaxIops, newHypervisorSnapshotReserve, newServiceOfferingId, shrinkOk);
 +        workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo));
 +
 +        _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId());
 +
 +        AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId());
 +
 +        return new VmJobVolumeOutcome(workJob, volumeId);
 +    }
 +
 +    public Outcome<String> extractVolumeThroughJobQueue(final Long vmId, final long volumeId, final long zoneId) {
 +
 +        final CallContext context = CallContext.current();
 +        final User callingUser = context.getCallingUser();
 +        final Account callingAccount = context.getCallingAccount();
 +
 +        final VMInstanceVO vm = _vmInstanceDao.findById(vmId);
 +
 +        VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId());
 +
 +        workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER);
 +        workJob.setCmd(VmWorkExtractVolume.class.getName());
 +
 +        workJob.setAccountId(callingAccount.getId());
 +        workJob.setUserId(callingUser.getId());
 +        workJob.setStep(VmWorkJobVO.Step.Starting);
 +        workJob.setVmType(VirtualMachine.Type.Instance);
 +        workJob.setVmInstanceId(vm.getId());
... 611 lines suppressed ...