2424
2525import javax .inject .Inject ;
2626
27+ import com .cloud .storage .VMTemplateStorageResourceAssoc ;
28+ import com .cloud .storage .download .DownloadListener ;
29+ import com .cloud .utils .exception .CloudRuntimeException ;
2730import org .apache .cloudstack .engine .subsystem .api .storage .CopyCommandResult ;
2831import org .apache .cloudstack .engine .subsystem .api .storage .DataMotionService ;
2932import org .apache .cloudstack .engine .subsystem .api .storage .DataObject ;
@@ -118,26 +121,21 @@ public AsyncCallFuture<DataObjectResult> migrateData(DataObject srcDataObject, D
118121 }
119122 } else if (srcDataObject instanceof TemplateInfo && templateChain != null && templateChain .containsKey (srcDataObject )) {
120123 for (TemplateInfo templateInfo : templateChain .get (srcDataObject ).first ()) {
124+ if (templateIsOnDestination (templateInfo , destDatastore )) {
125+ res .setResult ("Template already exists on destination." );
126+ res .setSuccess (true );
127+ logger .debug ("Deleting template {} from source data store [{}]." , srcDataObject .getTO ().toString (),
128+ srcDataObject .getDataStore ().getTO ().toString ());
129+ srcDataObject .getDataStore ().delete (srcDataObject );
130+ future .complete (res );
131+ continue ;
132+ }
121133 destDataObject = destDatastore .create (templateInfo );
122134 templateInfo .processEvent (ObjectInDataStoreStateMachine .Event .MigrateDataRequested );
123135 destDataObject .processEvent (ObjectInDataStoreStateMachine .Event .MigrateDataRequested );
124136 migrateJob (future , templateInfo , destDataObject , destDatastore );
125137 }
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- }
138+ } else {
141139 destDataObject = destDatastore .create (srcDataObject );
142140 srcDataObject .processEvent (ObjectInDataStoreStateMachine .Event .MigrateDataRequested );
143141 destDataObject .processEvent (ObjectInDataStoreStateMachine .Event .MigrateDataRequested );
@@ -160,6 +158,69 @@ public AsyncCallFuture<DataObjectResult> migrateData(DataObject srcDataObject, D
160158 return future ;
161159 }
162160
161+ /**
162+ * Returns a boolean indicating whether a template is ready on the provided data store. If the template is being downloaded,
163+ * waits until the download finishes.
164+ * @param srcDataObject the template.
165+ * @param destDatastore the data store.
166+ */
167+ protected boolean templateIsOnDestination (DataObject srcDataObject , DataStore destDatastore ) {
168+ if (!(srcDataObject instanceof TemplateInfo )) {
169+ return false ;
170+ }
171+
172+ String templateAsString = srcDataObject .getTO ().toString ();
173+ String destDatastoreAsString = destDatastore .getTO ().toString ();
174+ TemplateDataStoreVO templateStoreVO ;
175+
176+ long timer = getTemplateDownloadTimeout ();
177+ long msToSleep = 10000L ;
178+ int previousDownloadPercentage = -1 ;
179+
180+ while (true ) {
181+ templateStoreVO = templateStoreDao .findByStoreTemplate (destDatastore .getId (), srcDataObject .getId ());
182+ if (templateStoreVO == null ) {
183+ logger .debug ("{} is not present at destination [{}]." , templateAsString , destDatastoreAsString );
184+ return false ;
185+ }
186+ VMTemplateStorageResourceAssoc .Status downloadState = templateStoreVO .getDownloadState ();
187+ if (downloadState == null || !VMTemplateStorageResourceAssoc .PENDING_DOWNLOAD_STATES .contains (downloadState )) {
188+ break ;
189+ }
190+ if (previousDownloadPercentage == templateStoreVO .getDownloadPercent ()) {
191+ timer -= msToSleep ;
192+ } else {
193+ timer = getTemplateDownloadTimeout ();
194+ }
195+ if (timer <= 0 ) {
196+ throw new CloudRuntimeException (String .format ("Timeout while waiting for %s to be downloaded to image store [%s]. " +
197+ "The download percentage has not changed for %d milliseconds." , templateAsString , destDatastoreAsString , getTemplateDownloadTimeout ()));
198+ }
199+ waitForTemplateDownload (msToSleep , templateAsString , destDatastoreAsString );
200+ }
201+
202+ if (templateStoreVO .getState () == ObjectInDataStoreStateMachine .State .Ready ) {
203+ logger .debug ("{} already exists on destination [{}]." , templateAsString , destDatastoreAsString );
204+ return true ;
205+ }
206+ return false ;
207+ }
208+
209+ protected long getTemplateDownloadTimeout () {
210+ return DownloadListener .DOWNLOAD_TIMEOUT ;
211+ }
212+
213+ protected void waitForTemplateDownload (long msToSleep , String templateAsString , String destDatastoreAsString ) {
214+ logger .debug ("{} is being downloaded to destination [{}]; we will verify in {} milliseconds if the download has finished." ,
215+ templateAsString , destDatastoreAsString , msToSleep );
216+ try {
217+ Thread .sleep (msToSleep );
218+ } catch (InterruptedException e ) {
219+ logger .warn ("[ignored] interrupted while waiting for template {} download to finish before trying to migrate it to data store [{}]." ,
220+ templateAsString , destDatastoreAsString );
221+ }
222+ }
223+
163224 protected void migrateJob (AsyncCallFuture <DataObjectResult > future , DataObject srcDataObject , DataObject destDataObject , DataStore destDatastore ) throws ExecutionException , InterruptedException {
164225 MigrateDataContext <DataObjectResult > context = new MigrateDataContext <DataObjectResult >(null , future , srcDataObject , destDataObject , destDatastore );
165226 AsyncCallbackDispatcher <SecondaryStorageServiceImpl , CopyCommandResult > caller = AsyncCallbackDispatcher .create (this );
0 commit comments