@Override
  public Flow marshalVmOperationFlow(
      String previousFlowName, String nextFlowName, FlowChain chain, VmInstanceSpec spec) {
    if (VmAllocatePrimaryStorageFlow.class.getName().equals(nextFlowName)) {
      if (spec.getCurrentVmOperation() == VmOperation.NewCreate) {
        if (getLocalStorageInCluster(spec.getDestHost().getClusterUuid()) != null) {
          return new LocalStorageAllocateCapacityFlow();
        }
      }
    } else if (spec.getCurrentVmOperation() == VmOperation.AttachVolume) {
      VolumeInventory volume = spec.getDestDataVolumes().get(0);
      if (VolumeStatus.NotInstantiated.toString().equals(volume.getStatus())
          && VmAllocatePrimaryStorageForAttachingDiskFlow.class.getName().equals(nextFlowName)) {
        if (isRootVolumeOnLocalStorage(spec.getVmInventory().getRootVolumeUuid())) {
          return new LocalStorageAllocateCapacityForAttachingVolumeFlow();
        }
      }
    } else if (spec.getCurrentVmOperation() == VmOperation.Migrate
        && isRootVolumeOnLocalStorage(spec.getVmInventory().getRootVolumeUuid())
        && VmMigrateOnHypervisorFlow.class.getName().equals(nextFlowName)) {
      if (KVMConstant.KVM_HYPERVISOR_TYPE.equals(spec.getVmInventory().getHypervisorType())) {
        return new LocalStorageKvmMigrateVmFlow();
      } else {
        throw new OperationFailureException(
            errf.stringToOperationError(
                String.format(
                    "local storage doesn't support live migration for hypervisor[%s]",
                    spec.getVmInventory().getHypervisorType())));
      }
    }

    return null;
  }
  @Override
  public void preRecoverDataVolume(VolumeInventory vol) {
    if (vol.getPrimaryStorageUuid() == null) {
      return;
    }

    SimpleQuery<PrimaryStorageVO> q = dbf.createQuery(PrimaryStorageVO.class);
    q.select(PrimaryStorageVO_.type);
    q.add(PrimaryStorageVO_.uuid, Op.EQ, vol.getPrimaryStorageUuid());
    String type = q.findValue();
    if (!LocalStorageConstants.LOCAL_STORAGE_TYPE.equals(type)) {
      return;
    }

    SimpleQuery<LocalStorageResourceRefVO> rq = dbf.createQuery(LocalStorageResourceRefVO.class);
    rq.add(LocalStorageResourceRefVO_.resourceUuid, Op.EQ, vol.getUuid());
    rq.add(LocalStorageResourceRefVO_.resourceType, Op.EQ, VolumeVO.class.getSimpleName());
    if (!rq.isExists()) {
      throw new OperationFailureException(
          errf.stringToOperationError(
              String.format(
                  "the data volume[name:%s, uuid:%s] is on the local storage[uuid:%s]; however,"
                      + "the host on which the data volume is has been deleted. Unable to recover this volume",
                  vol.getName(), vol.getUuid(), vol.getPrimaryStorageUuid())));
    }
  }
  @Override
  public void preAttachVolume(VmInstanceInventory vm, final VolumeInventory volume) {
    SimpleQuery<LocalStorageResourceRefVO> q = dbf.createQuery(LocalStorageResourceRefVO.class);
    q.add(
        LocalStorageResourceRefVO_.resourceUuid,
        Op.IN,
        list(vm.getRootVolumeUuid(), volume.getUuid()));
    q.groupBy(LocalStorageResourceRefVO_.hostUuid);
    long count = q.count();

    if (count < 2) {
      return;
    }

    q = dbf.createQuery(LocalStorageResourceRefVO.class);
    q.select(LocalStorageResourceRefVO_.hostUuid);
    q.add(LocalStorageResourceRefVO_.resourceUuid, Op.EQ, vm.getRootVolumeUuid());
    String rootHost = q.findValue();

    q = dbf.createQuery(LocalStorageResourceRefVO.class);
    q.select(LocalStorageResourceRefVO_.hostUuid);
    q.add(LocalStorageResourceRefVO_.resourceUuid, Op.EQ, volume.getUuid());
    String dataHost = q.findValue();

    if (!rootHost.equals(dataHost)) {
      throw new OperationFailureException(
          errf.stringToOperationError(
              String.format(
                  "cannot attach the data volume[uuid:%s] to the vm[uuid:%s]. Both vm's root volume and the data volume are"
                      + " on local primary storage, but they are on different hosts. The root volume[uuid:%s] is on the host[uuid:%s] but the data volume[uuid: %s]"
                      + " is on the host[uuid: %s]",
                  volume.getUuid(),
                  vm.getUuid(),
                  vm.getRootVolumeUuid(),
                  rootHost,
                  volume.getUuid(),
                  dataHost)));
    }
  }
  @Override
  @Transactional(readOnly = true)
  public List<VmInstanceVO> returnAttachableVms(
      VolumeInventory vol, List<VmInstanceVO> candidates) {
    String sql =
        "select ref.hostUuid from LocalStorageResourceRefVO ref where ref.resourceUuid = :uuid"
            + " and ref.resourceType = :rtype";
    TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
    q.setParameter("uuid", vol.getUuid());
    q.setParameter("rtype", VolumeVO.class.getSimpleName());
    List<String> ret = q.getResultList();
    if (ret.isEmpty()) {
      return candidates;
    }

    String hostUuid = ret.get(0);

    List<String> vmRootVolumeUuids =
        CollectionUtils.transformToList(
            candidates,
            new Function<String, VmInstanceVO>() {
              @Override
              public String call(VmInstanceVO arg) {
                return arg.getRootVolumeUuid();
              }
            });

    sql =
        "select ref.resourceUuid from LocalStorageResourceRefVO ref where ref.hostUuid = :huuid"
            + " and ref.resourceUuid in (:rootVolumeUuids) and ref.resourceType = :rtype";
    q = dbf.getEntityManager().createQuery(sql, String.class);
    q.setParameter("huuid", hostUuid);
    q.setParameter("rootVolumeUuids", vmRootVolumeUuids);
    q.setParameter("rtype", VolumeVO.class.getSimpleName());
    final List<String> toInclude = q.getResultList();

    candidates =
        CollectionUtils.transformToList(
            candidates,
            new Function<VmInstanceVO, VmInstanceVO>() {
              @Override
              public VmInstanceVO call(VmInstanceVO arg) {
                return toInclude.contains(arg.getRootVolumeUuid()) ? arg : null;
              }
            });

    return candidates;
  }