@Override
  public AsyncCallFuture<TemplateApiResult> copyTemplate(
      TemplateInfo srcTemplate, DataStore destStore) {
    // generate a URL from source template ssvm to download to destination data store
    String url = generateCopyUrl(srcTemplate);
    if (url == null) {
      s_logger.warn(
          "Unable to start/resume copy of template "
              + srcTemplate.getUniqueName()
              + " to "
              + destStore.getName()
              + ", no secondary storage vm in running state in source zone");
      throw new CloudRuntimeException("No secondary VM in running state in source template zone ");
    }

    TemplateObject tmplForCopy =
        (TemplateObject) _templateFactory.getTemplate(srcTemplate, destStore);
    if (s_logger.isDebugEnabled()) {
      s_logger.debug("Setting source template url to " + url);
    }
    tmplForCopy.setUrl(url);

    if (s_logger.isDebugEnabled()) {
      s_logger.debug("Mark template_store_ref entry as Creating");
    }
    AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<TemplateApiResult>();
    DataObject templateOnStore = destStore.create(tmplForCopy);
    templateOnStore.processEvent(Event.CreateOnlyRequested);

    if (s_logger.isDebugEnabled()) {
      s_logger.debug("Invoke datastore driver createAsync to create template on destination store");
    }
    try {
      TemplateOpContext<TemplateApiResult> context =
          new TemplateOpContext<TemplateApiResult>(null, (TemplateObject) templateOnStore, future);
      AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> caller =
          AsyncCallbackDispatcher.create(this);
      caller
          .setCallback(caller.getTarget().copyTemplateCrossZoneCallBack(null, null))
          .setContext(context);
      destStore.getDriver().createAsync(destStore, templateOnStore, caller);
    } catch (CloudRuntimeException ex) {
      // clean up already persisted template_store_ref entry in case of createTemplateCallback is
      // never called
      TemplateDataStoreVO templateStoreVO =
          _vmTemplateStoreDao.findByStoreTemplate(destStore.getId(), srcTemplate.getId());
      if (templateStoreVO != null) {
        TemplateInfo tmplObj = _templateFactory.getTemplate(srcTemplate, destStore);
        tmplObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
      }
      TemplateApiResult res = new TemplateApiResult((TemplateObject) templateOnStore);
      res.setResult(ex.getMessage());
      future.complete(res);
    }
    return future;
  }
  @Override
  public void createTemplateAsync(
      TemplateInfo template, DataStore store, AsyncCompletionCallback<TemplateApiResult> callback) {
    // persist template_store_ref entry
    TemplateObject templateOnStore = (TemplateObject) store.create(template);
    // update template_store_ref and template state
    try {
      templateOnStore.processEvent(ObjectInDataStoreStateMachine.Event.CreateOnlyRequested);
    } catch (Exception e) {
      TemplateApiResult result = new TemplateApiResult(templateOnStore);
      result.setResult(e.toString());
      result.setSuccess(false);
      if (callback != null) {
        callback.complete(result);
      }
      return;
    }

    try {
      TemplateOpContext<TemplateApiResult> context =
          new TemplateOpContext<TemplateApiResult>(callback, templateOnStore, null);

      AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> caller =
          AsyncCallbackDispatcher.create(this);
      caller.setCallback(caller.getTarget().createTemplateCallback(null, null)).setContext(context);
      store.getDriver().createAsync(store, templateOnStore, caller);
    } catch (CloudRuntimeException ex) {
      // clean up already persisted template_store_ref entry in case of createTemplateCallback is
      // never called
      TemplateDataStoreVO templateStoreVO =
          _vmTemplateStoreDao.findByStoreTemplate(store.getId(), template.getId());
      if (templateStoreVO != null) {
        TemplateInfo tmplObj = _templateFactory.getTemplate(template, store);
        tmplObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
      }
      TemplateApiResult result = new TemplateApiResult(template);
      result.setResult(ex.getMessage());
      if (callback != null) {
        callback.complete(result);
      }
    }
  }
  @Override
  public AsyncCallFuture<TemplateApiResult> copyTemplate(
      TemplateInfo srcTemplate, DataStore destStore) {
    // for vmware template, we need to check if ova packing is needed, since template created from
    // snapshot does not have .ova file
    // we invoke createEntityExtractURL to trigger ova packing. Ideally, we can directly use
    // extractURL to pass to following createTemplate.
    // Need to understand what is the background to use two different urls for copy and extract.
    if (srcTemplate.getFormat() == ImageFormat.OVA) {
      ImageStoreEntity tmpltStore = (ImageStoreEntity) srcTemplate.getDataStore();
      tmpltStore.createEntityExtractUrl(
          srcTemplate.getInstallPath(), srcTemplate.getFormat(), srcTemplate);
    }
    // generate a URL from source template ssvm to download to destination data store
    String url = generateCopyUrl(srcTemplate);
    if (url == null) {
      s_logger.warn(
          "Unable to start/resume copy of template "
              + srcTemplate.getUniqueName()
              + " to "
              + destStore.getName()
              + ", no secondary storage vm in running state in source zone");
      throw new CloudRuntimeException("No secondary VM in running state in source template zone ");
    }

    TemplateObject tmplForCopy =
        (TemplateObject) _templateFactory.getTemplate(srcTemplate, destStore);
    if (s_logger.isDebugEnabled()) {
      s_logger.debug("Setting source template url to " + url);
    }
    tmplForCopy.setUrl(url);

    if (s_logger.isDebugEnabled()) {
      s_logger.debug("Mark template_store_ref entry as Creating");
    }
    AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<TemplateApiResult>();
    DataObject templateOnStore = destStore.create(tmplForCopy);
    templateOnStore.processEvent(Event.CreateOnlyRequested);

    if (s_logger.isDebugEnabled()) {
      s_logger.debug("Invoke datastore driver createAsync to create template on destination store");
    }
    try {
      TemplateOpContext<TemplateApiResult> context =
          new TemplateOpContext<TemplateApiResult>(null, (TemplateObject) templateOnStore, future);
      AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> caller =
          AsyncCallbackDispatcher.create(this);
      caller
          .setCallback(caller.getTarget().copyTemplateCrossZoneCallBack(null, null))
          .setContext(context);
      destStore.getDriver().createAsync(destStore, templateOnStore, caller);
    } catch (CloudRuntimeException ex) {
      // clean up already persisted template_store_ref entry in case of createTemplateCallback is
      // never called
      TemplateDataStoreVO templateStoreVO =
          _vmTemplateStoreDao.findByStoreTemplate(destStore.getId(), srcTemplate.getId());
      if (templateStoreVO != null) {
        TemplateInfo tmplObj = _templateFactory.getTemplate(srcTemplate, destStore);
        tmplObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed);
      }
      TemplateApiResult res = new TemplateApiResult((TemplateObject) templateOnStore);
      res.setResult(ex.getMessage());
      future.complete(res);
    }
    return future;
  }