1717package com .cloud .hypervisor .kvm .storage ;
1818
1919
20+ import com .cloud .agent .api .to .DiskTO ;
21+ import com .cloud .storage .Storage ;
22+ import com .cloud .storage .Storage .ImageFormat ;
23+ import com .cloud .storage .Storage .ProvisioningType ;
24+ import com .cloud .storage .Storage .StoragePoolType ;
25+ import com .cloud .utils .exception .CloudRuntimeException ;
26+ import com .cloud .utils .script .OutputInterpreter ;
27+ import com .cloud .utils .script .Script ;
28+ import com .google .gson .Gson ;
29+ import com .google .gson .JsonObject ;
30+ import com .google .gson .JsonParser ;
31+ import com .google .gson .JsonPrimitive ;
32+ import org .apache .cloudstack .storage .datastore .util .StorPoolUtil ;
33+ import org .apache .cloudstack .utils .qemu .QemuImg ;
34+ import org .apache .cloudstack .utils .qemu .QemuImg .PhysicalDiskFormat ;
35+ import org .apache .cloudstack .utils .qemu .QemuImgException ;
36+ import org .apache .cloudstack .utils .qemu .QemuImgFile ;
37+ import org .apache .commons .lang3 .StringUtils ;
38+ import org .apache .logging .log4j .LogManager ;
39+ import org .apache .logging .log4j .Logger ;
40+ import org .jetbrains .annotations .NotNull ;
41+ import org .libvirt .LibvirtException ;
42+
2043import java .io .BufferedWriter ;
2144import java .io .File ;
2245import java .io .FileWriter ;
2649import java .util .HashMap ;
2750import java .util .List ;
2851import java .util .Map ;
29-
30- import org .apache .cloudstack .utils .qemu .QemuImg .PhysicalDiskFormat ;
31- import org .apache .logging .log4j .Logger ;
32- import org .apache .logging .log4j .LogManager ;
33-
34- import com .cloud .agent .api .to .DiskTO ;
35- import com .cloud .storage .Storage ;
36- import com .cloud .storage .Storage .ImageFormat ;
37- import com .cloud .storage .Storage .ProvisioningType ;
38- import com .cloud .storage .Storage .StoragePoolType ;
39- import com .cloud .utils .exception .CloudRuntimeException ;
40- import com .cloud .utils .script .OutputInterpreter ;
41- import com .cloud .utils .script .Script ;
52+ import java .util .UUID ;
4253
4354public class StorPoolStorageAdaptor implements StorageAdaptor {
4455 public static void SP_LOG (String fmt , Object ... args ) {
@@ -149,6 +160,10 @@ public static String getVolumeNameFromPath(final String volumeUuid, boolean tild
149160 }
150161
151162 public static boolean attachOrDetachVolume (String command , String type , String volumeUuid ) {
163+ if (volumeUuid == null ) {
164+ LOGGER .debug ("Could not attach volume. The volume ID is null" );
165+ return false ;
166+ }
152167 final String name = getVolumeNameFromPath (volumeUuid , true );
153168 if (name == null ) {
154169 return false ;
@@ -345,11 +360,85 @@ public boolean createFolder(String uuid, String path) {
345360 throw new UnsupportedOperationException ("A folder cannot be created in this configuration." );
346361 }
347362
348- public KVMPhysicalDisk createTemplateFromDirectDownloadFile (String templateFilePath , String destTemplatePath ,
349- KVMStoragePool destPool , ImageFormat format , int timeout ) {
363+ @ Override
364+ public KVMPhysicalDisk createDiskFromTemplateBacking (KVMPhysicalDisk template , String name ,
365+ PhysicalDiskFormat format , long size , KVMStoragePool destPool , int timeout , byte [] passphrase ) {
350366 return null ;
351367 }
352368
369+ @ Override
370+ public KVMPhysicalDisk createTemplateFromDirectDownloadFile (String templateFilePath , String destTemplatePath ,
371+ KVMStoragePool destPool , ImageFormat format , int timeout ) {
372+ if (StringUtils .isEmpty (templateFilePath ) || destPool == null ) {
373+ throw new CloudRuntimeException (
374+ "Unable to create template from direct download template file due to insufficient data" );
375+ }
376+
377+ File sourceFile = new File (templateFilePath );
378+ if (!sourceFile .exists ()) {
379+ throw new CloudRuntimeException (
380+ "Direct download template file " + templateFilePath + " does not exist on this host" );
381+ }
382+
383+ if (!StoragePoolType .StorPool .equals (destPool .getType ())) {
384+ throw new CloudRuntimeException ("Unsupported storage pool type: " + destPool .getType ().toString ());
385+ }
386+
387+ if (!Storage .ImageFormat .QCOW2 .equals (format )) {
388+ throw new CloudRuntimeException ("Unsupported template format: " + format .toString ());
389+ }
390+
391+ String srcTemplateFilePath = templateFilePath ;
392+ KVMPhysicalDisk destDisk = null ;
393+ QemuImgFile srcFile = null ;
394+ QemuImgFile destFile = null ;
395+ String templateName = UUID .randomUUID ().toString ();
396+ String volume = null ;
397+ try {
398+
399+ srcTemplateFilePath = extractTemplate (templateFilePath , sourceFile , srcTemplateFilePath , templateName );
400+
401+ QemuImg .PhysicalDiskFormat srcFileFormat = QemuImg .PhysicalDiskFormat .QCOW2 ;
402+
403+ srcFile = new QemuImgFile (srcTemplateFilePath , srcFileFormat );
404+
405+ String spTemplate = destPool .getUuid ().split (";" )[0 ];
406+
407+ QemuImg qemu = new QemuImg (timeout );
408+ OutputInterpreter .AllLinesParser parser = createStorPoolVolume (destPool , srcFile , qemu , spTemplate );
409+
410+ String response = parser .getLines ();
411+
412+ LOGGER .debug (response );
413+ volume = StorPoolUtil .devPath (getNameFromResponse (response , false , false ));
414+ attachOrDetachVolume ("attach" , "volume" , volume );
415+ destDisk = destPool .getPhysicalDisk (volume );
416+ if (destDisk == null ) {
417+ throw new CloudRuntimeException (
418+ "Failed to find the disk: " + volume + " of the storage pool: " + destPool .getUuid ());
419+ }
420+
421+ destFile = new QemuImgFile (destDisk .getPath (), QemuImg .PhysicalDiskFormat .RAW );
422+
423+ qemu .convert (srcFile , destFile );
424+ parser = volumeSnapshot (StorPoolStorageAdaptor .getVolumeNameFromPath (volume , true ), spTemplate );
425+ response = parser .getLines ();
426+ LOGGER .debug (response );
427+ String newPath = StorPoolUtil .devPath (getNameFromResponse (response , false , true ));
428+ destDisk = destPool .getPhysicalDisk (newPath );
429+ } catch (QemuImgException | LibvirtException e ) {
430+ destDisk = null ;
431+ } finally {
432+ if (volume != null ) {
433+ attachOrDetachVolume ("detach" , "volume" , volume );
434+ volumeDelete (StorPoolStorageAdaptor .getVolumeNameFromPath (volume , true ));
435+ }
436+ Script .runSimpleBashScript ("rm -f " + srcTemplateFilePath );
437+ }
438+
439+ return destDisk ;
440+ }
441+
353442 @ Override
354443 public boolean createFolder (String uuid , String path , String localPath ) {
355444 return false ;
@@ -367,9 +456,104 @@ public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String n
367456 return null ;
368457 }
369458
370- @ Override
371- public KVMPhysicalDisk createDiskFromTemplateBacking (KVMPhysicalDisk template , String name ,
372- PhysicalDiskFormat format , long size , KVMStoragePool destPool , int timeout , byte [] passphrase ) {
373- return null ;
459+ private OutputInterpreter .AllLinesParser createStorPoolVolume (KVMStoragePool destPool , QemuImgFile srcFile ,
460+ QemuImg qemu , String templateUuid ) throws QemuImgException , LibvirtException {
461+ Map <String , String > info = qemu .info (srcFile );
462+ Map <String , Object > reqParams = new HashMap <>();
463+ reqParams .put ("template" , templateUuid );
464+ reqParams .put ("size" , info .get ("virtual_size" ));
465+ Map <String , String > tags = new HashMap <>();
466+ tags .put ("cs" , "template" );
467+ reqParams .put ("tags" , tags );
468+ Gson gson = new Gson ();
469+ String js = gson .toJson (reqParams );
470+
471+ Script sc = createStorPoolRequest (js , "VolumeCreate" , null ,true );
472+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
473+
474+ String res = sc .execute (parser );
475+ if (res != null ) {
476+ throw new CloudRuntimeException ("Could not create volume due to: " + res );
477+ }
478+ return parser ;
479+ }
480+
481+ private OutputInterpreter .AllLinesParser volumeSnapshot (String volumeName , String templateUuid ) {
482+ Map <String , String > reqParams = new HashMap <>();
483+ reqParams .put ("template" , templateUuid );
484+ Gson gson = new Gson ();
485+ String js = gson .toJson (reqParams );
486+
487+ Script sc = createStorPoolRequest (js , "VolumeSnapshot" , volumeName ,true );
488+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
489+
490+ String res = sc .execute (parser );
491+ if (res != null ) {
492+ throw new CloudRuntimeException ("Could not snapshot volume due to: " + res );
493+ }
494+ return parser ;
495+ }
496+
497+ private OutputInterpreter .AllLinesParser volumeDelete (String volumeName ) {
498+ Script sc = createStorPoolRequest (null , "VolumeDelete" , volumeName , false );
499+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
500+
501+ String res = sc .execute (parser );
502+ if (res != null ) {
503+ throw new CloudRuntimeException ("Could not delete volume due to: " + res );
504+ }
505+ return parser ;
506+ }
507+ @ NotNull
508+ private static Script createStorPoolRequest (String js , String apiCall , String param , boolean jsonRequired ) {
509+ Script sc = new Script ("storpool_req" , 0 , LOGGER );
510+ sc .add ("-P" );
511+ sc .add ("-M" );
512+ if (jsonRequired ) {
513+ sc .add ("--json" );
514+ sc .add (js );
515+ }
516+ sc .add (apiCall );
517+ if (param != null ) {
518+ sc .add (param );
519+ }
520+ return sc ;
521+ }
522+
523+ private String extractTemplate (String templateFilePath , File sourceFile , String srcTemplateFilePath ,
524+ String templateName ) {
525+ if (isTemplateExtractable (templateFilePath )) {
526+ srcTemplateFilePath = sourceFile .getParent () + "/" + templateName ;
527+ String extractCommand = getExtractCommandForDownloadedFile (templateFilePath , srcTemplateFilePath );
528+ Script .runSimpleBashScript (extractCommand );
529+ Script .runSimpleBashScript ("rm -f " + templateFilePath );
530+ }
531+ return srcTemplateFilePath ;
532+ }
533+
534+ private boolean isTemplateExtractable (String templatePath ) {
535+ String type = Script .runSimpleBashScript ("file " + templatePath + " | awk -F' ' '{print $2}'" );
536+ return type .equalsIgnoreCase ("bzip2" ) || type .equalsIgnoreCase ("gzip" ) || type .equalsIgnoreCase ("zip" );
537+ }
538+
539+ private String getExtractCommandForDownloadedFile (String downloadedTemplateFile , String templateFile ) {
540+ if (downloadedTemplateFile .endsWith (".zip" )) {
541+ return "unzip -p " + downloadedTemplateFile + " | cat > " + templateFile ;
542+ } else if (downloadedTemplateFile .endsWith (".bz2" )) {
543+ return "bunzip2 -c " + downloadedTemplateFile + " > " + templateFile ;
544+ } else if (downloadedTemplateFile .endsWith (".gz" )) {
545+ return "gunzip -c " + downloadedTemplateFile + " > " + templateFile ;
546+ } else {
547+ throw new CloudRuntimeException ("Unable to extract template " + downloadedTemplateFile );
548+ }
549+ }
550+
551+ private String getNameFromResponse (String resp , boolean tildeNeeded , boolean isSnapshot ) {
552+ JsonParser jsonParser = new JsonParser ();
553+ JsonObject respObj = (JsonObject ) jsonParser .parse (resp );
554+ JsonPrimitive data = isSnapshot ? respObj .getAsJsonPrimitive ("snapshotGlobalId" ) : respObj .getAsJsonPrimitive ("globalId" );
555+ String name = data !=null ? data .getAsString () : null ;
556+ name = name != null ? name .startsWith ("~" ) && !tildeNeeded ? name .split ("~" )[1 ] : name : name ;
557+ return name ;
374558 }
375559}
0 commit comments