2424
2525import javax .inject .Inject ;
2626
27+ import com .cloud .storage .VMTemplateStorageResourceAssoc ;
28+ import com .cloud .utils .exception .CloudRuntimeException ;
2729import org .apache .cloudstack .engine .subsystem .api .storage .CopyCommandResult ;
2830import org .apache .cloudstack .engine .subsystem .api .storage .DataMotionService ;
2931import org .apache .cloudstack .engine .subsystem .api .storage .DataObject ;
@@ -118,26 +120,21 @@ public AsyncCallFuture<DataObjectResult> migrateData(DataObject srcDataObject, D
118120 }
119121 } else if (srcDataObject instanceof TemplateInfo && templateChain != null && templateChain .containsKey (srcDataObject )) {
120122 for (TemplateInfo templateInfo : templateChain .get (srcDataObject ).first ()) {
123+ if (templateIsOnDestination (templateInfo , destDatastore )) {
124+ res .setResult ("Template already exists on destination." );
125+ res .setSuccess (true );
126+ logger .debug ("Deleting template {} from source data store [{}]." , srcDataObject .getTO ().toString (),
127+ srcDataObject .getDataStore ().getTO ().toString ());
128+ srcDataObject .getDataStore ().delete (srcDataObject );
129+ future .complete (res );
130+ continue ;
131+ }
121132 destDataObject = destDatastore .create (templateInfo );
122133 templateInfo .processEvent (ObjectInDataStoreStateMachine .Event .MigrateDataRequested );
123134 destDataObject .processEvent (ObjectInDataStoreStateMachine .Event .MigrateDataRequested );
124135 migrateJob (future , templateInfo , destDataObject , destDatastore );
125136 }
126- }
127- else {
128- // Check if template in destination store, if yes, do not proceed
129- if (srcDataObject instanceof TemplateInfo ) {
130- logger .debug ("Checking if template present at destination" );
131- TemplateDataStoreVO templateStoreVO = templateStoreDao .findByStoreTemplate (destDatastore .getId (), srcDataObject .getId ());
132- if (templateStoreVO != null ) {
133- String msg = "Template already exists in destination store" ;
134- logger .debug (msg );
135- res .setResult (msg );
136- res .setSuccess (true );
137- future .complete (res );
138- return future ;
139- }
140- }
137+ } else {
141138 destDataObject = destDatastore .create (srcDataObject );
142139 srcDataObject .processEvent (ObjectInDataStoreStateMachine .Event .MigrateDataRequested );
143140 destDataObject .processEvent (ObjectInDataStoreStateMachine .Event .MigrateDataRequested );
@@ -160,6 +157,69 @@ public AsyncCallFuture<DataObjectResult> migrateData(DataObject srcDataObject, D
160157 return future ;
161158 }
162159
160+ /**
161+ * Returns a boolean indicating whether a template is ready on the provided data store. If the template is being downloaded,
162+ * waits until the download finishes.
163+ * @param srcDataObject the template.
164+ * @param destDatastore the data store.
165+ */
166+ protected boolean templateIsOnDestination (DataObject srcDataObject , DataStore destDatastore ) {
167+ if (!(srcDataObject instanceof TemplateInfo )) {
168+ return false ;
169+ }
170+
171+ String templateAsString = srcDataObject .getTO ().toString ();
172+ String destDatastoreAsString = destDatastore .getTO ().toString ();
173+ TemplateDataStoreVO templateStoreVO ;
174+
175+ long timer = getTemplateDownloadTimeoutInSeconds ();
176+ int secondsToSleep = 10 ;
177+ int previousDownloadPercentage = -1 ;
178+
179+ while (true ) {
180+ templateStoreVO = templateStoreDao .findByStoreTemplate (destDatastore .getId (), srcDataObject .getId ());
181+ if (templateStoreVO == null ) {
182+ logger .debug ("{} is not present at destination [{}]." , templateAsString , destDatastoreAsString );
183+ return false ;
184+ }
185+ VMTemplateStorageResourceAssoc .Status downloadState = templateStoreVO .getDownloadState ();
186+ if (downloadState == null || !VMTemplateStorageResourceAssoc .PENDING_DOWNLOAD_STATES .contains (downloadState )) {
187+ break ;
188+ }
189+ if (previousDownloadPercentage == templateStoreVO .getDownloadPercent ()) {
190+ timer -= secondsToSleep ;
191+ } else {
192+ timer = getTemplateDownloadTimeoutInSeconds ();
193+ }
194+ if (timer <= 0 ) {
195+ throw new CloudRuntimeException (String .format ("Timeout while waiting for %s to be downloaded to image store [%s]. " +
196+ "The download percentage has not changed for %d seconds." , templateAsString , destDatastoreAsString , getTemplateDownloadTimeoutInSeconds ()));
197+ }
198+ waitForTemplateDownload (secondsToSleep , templateAsString , destDatastoreAsString );
199+ }
200+
201+ if (templateStoreVO .getState () == ObjectInDataStoreStateMachine .State .Ready ) {
202+ logger .debug ("{} already exists on destination [{}]." , templateAsString , destDatastoreAsString );
203+ return true ;
204+ }
205+ return false ;
206+ }
207+
208+ protected long getTemplateDownloadTimeoutInSeconds () {
209+ return 1800L ;
210+ }
211+
212+ protected void waitForTemplateDownload (int secondsToSleep , String templateAsString , String destDatastoreAsString ) {
213+ logger .debug ("{} is being downloaded to destination [{}]; we will verify in {} seconds if the download has finished." ,
214+ templateAsString , destDatastoreAsString , secondsToSleep );
215+ try {
216+ Thread .sleep (secondsToSleep * 1000L );
217+ } catch (InterruptedException e ) {
218+ logger .warn ("[ignored] interrupted while waiting for template {} download to finish before trying to migrate it to data store [{}]." ,
219+ templateAsString , destDatastoreAsString );
220+ }
221+ }
222+
163223 protected void migrateJob (AsyncCallFuture <DataObjectResult > future , DataObject srcDataObject , DataObject destDataObject , DataStore destDatastore ) throws ExecutionException , InterruptedException {
164224 MigrateDataContext <DataObjectResult > context = new MigrateDataContext <DataObjectResult >(null , future , srcDataObject , destDataObject , destDatastore );
165225 AsyncCallbackDispatcher <SecondaryStorageServiceImpl , CopyCommandResult > caller = AsyncCallbackDispatcher .create (this );
0 commit comments