diff --git a/api/src/org/apache/cloudstack/api/command/user/template/CopyTemplateCmd.java b/api/src/org/apache/cloudstack/api/command/user/template/CopyTemplateCmd.java index a25bd65f9c2..10c62643fe7 100644 --- a/api/src/org/apache/cloudstack/api/command/user/template/CopyTemplateCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/template/CopyTemplateCmd.java @@ -18,6 +18,8 @@ package org.apache.cloudstack.api.command.user.template; import java.util.List; +import org.apache.log4j.Logger; + import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandJobType; import org.apache.cloudstack.api.ApiConstants; @@ -26,12 +28,9 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.TemplateResponse; -import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; -import org.apache.log4j.Logger; - import com.cloud.event.EventTypes; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.StorageUnavailableException; @@ -56,7 +55,7 @@ public class CopyTemplateCmd extends BaseAsyncCmd { private Long id; @Parameter(name=ApiConstants.SOURCE_ZONE_ID, type=CommandType.UUID, entityType = ZoneResponse.class, - required=true, description="ID of the zone the template is currently hosted on.") + description = "ID of the zone the template is currently hosted on. If not specified and template is cross-zone, then we will sync this template to region wide image store") private Long sourceZoneId; @@ -110,10 +109,12 @@ public class CopyTemplateCmd extends BaseAsyncCmd { return "copying template: " + getId() + " from zone: " + getSourceZoneId() + " to zone: " + getDestinationZoneId(); } + @Override public ApiCommandJobType getInstanceType() { return ApiCommandJobType.Template; } + @Override public Long getInstanceId() { return getId(); } @@ -132,7 +133,7 @@ public class CopyTemplateCmd extends BaseAsyncCmd { } response.setResponseName(getCommandName()); - this.setResponseObject(response); + setResponseObject(response); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to copy template"); } diff --git a/engine/components-api/src/com/cloud/template/TemplateManager.java b/engine/components-api/src/com/cloud/template/TemplateManager.java index 1372a1433aa..3adce4f1504 100755 --- a/engine/components-api/src/com/cloud/template/TemplateManager.java +++ b/engine/components-api/src/com/cloud/template/TemplateManager.java @@ -103,6 +103,8 @@ public interface TemplateManager { DataStore getImageStore(long zoneId, long tmpltId); + DataStore getImageStore(long tmpltId); + Long getTemplateSize(long templateId, long zoneId); DataStore getImageStore(String storeUuid, Long zoneId); diff --git a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreDao.java b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreDao.java index 7a9a15a56df..39347c5cf3a 100644 --- a/engine/schema/src/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreDao.java +++ b/engine/schema/src/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreDao.java @@ -62,6 +62,8 @@ StateDao listByTemplate(long templateId); diff --git a/engine/storage/src/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java b/engine/storage/src/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java index 69a52c0a430..5104fb29aa5 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java +++ b/engine/storage/src/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java @@ -343,15 +343,20 @@ public class TemplateDataStoreDaoImpl extends GenericDaoBase sc = templateRoleSearch.create(); sc.setParameters("template_id", templateId); - sc.setParameters("store_role", DataStoreRole.ImageCache); + sc.setParameters("store_role", role); sc.setParameters("destroyed", false); sc.setParameters("state", ObjectInDataStoreStateMachine.State.Ready); return findOneIncludingRemovedBy(sc); } + @Override + public TemplateDataStoreVO findReadyOnCache(long templateId) { + return findReadyByTemplate(templateId, DataStoreRole.ImageCache); + } + @Override public List listOnCache(long templateId) { SearchCriteria sc = templateRoleSearch.create(); diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java index de309484545..8dc8196c4b6 100755 --- a/server/src/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/com/cloud/template/TemplateManagerImpl.java @@ -694,38 +694,47 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, Account caller = CallContext.current().getCallingAccount(); // Verify parameters - if (sourceZoneId.equals(destZoneId)) { - throw new InvalidParameterValueException("Please specify different source and destination zones."); - } - - DataCenterVO sourceZone = _dcDao.findById(sourceZoneId); - if (sourceZone == null) { - throw new InvalidParameterValueException("Please specify a valid source zone."); - } - - DataCenterVO dstZone = _dcDao.findById(destZoneId); - if (dstZone == null) { - throw new InvalidParameterValueException("Please specify a valid destination zone."); - } - VMTemplateVO template = _tmpltDao.findById(templateId); if (template == null || template.getRemoved() != null) { throw new InvalidParameterValueException("Unable to find template with id"); } - DataStore srcSecStore = getImageStore(sourceZoneId, templateId); - if (srcSecStore == null) { - throw new InvalidParameterValueException("There is no template " + templateId + " in zone " + sourceZoneId); + DataStore srcSecStore = null; + if (sourceZoneId != null) { + // template is on zone-wide secondary storage + srcSecStore = getImageStore(sourceZoneId, templateId); + } else { + // template is on region store + srcSecStore = getImageStore(templateId); } - if (template.isCrossZones()){ - //TODO: we may need UI still enable CopyTemplate in case of cross zone template to trigger sync to region store. + if (srcSecStore == null) { + throw new InvalidParameterValueException("There is no template " + templateId + " ready on image store."); + } + + if (template.isCrossZones()) { // sync template from cache store to region store if it is not there, for cases where we are going to migrate existing NFS to S3. _tmpltSvr.syncTemplateToRegionStore(templateId, srcSecStore); s_logger.debug("Template " + templateId + " is cross-zone, don't need to copy"); return template; } + if (sourceZoneId != null) { + if (sourceZoneId.equals(destZoneId)) { + throw new InvalidParameterValueException("Please specify different source and destination zones."); + } + + DataCenterVO sourceZone = _dcDao.findById(sourceZoneId); + if (sourceZone == null) { + throw new InvalidParameterValueException("Please specify a valid source zone."); + } + } + + DataCenterVO dstZone = _dcDao.findById(destZoneId); + if (dstZone == null) { + throw new InvalidParameterValueException("Please specify a valid destination zone."); + } + DataStore dstSecStore = getImageStore(destZoneId, templateId); if (dstSecStore != null) { s_logger.debug("There is template " + templateId + " in secondary storage " + dstSecStore.getName() + " in zone " + destZoneId @@ -1706,6 +1715,18 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, return null; } + // get the region wide image store where a template is READY on, + // just pick one is enough. + @Override + public DataStore getImageStore(long tmpltId) { + TemplateDataStoreVO tmpltStore = _tmplStoreDao.findReadyByTemplate(tmpltId, DataStoreRole.Image); + if (tmpltStore != null) { + return _dataStoreMgr.getDataStore(tmpltStore.getDataStoreId(), DataStoreRole.Image); + } + + return null; + } + @Override public Long getTemplateSize(long templateId, long zoneId) { TemplateDataStoreVO templateStoreRef = _tmplStoreDao.findByTemplateZoneDownloadStatus(templateId, zoneId,