diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index f313f4b4d..53eb2d069 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -238,12 +238,14 @@ function VMClone(uuid, name){ }); dialogStyle(); } -function selectsnapshot(uuid, name ,snaps, opt, getlist,state){ +function selectsnapshot(uuid, name ,snaps, opt, getlist,state ,fstype){ var box = $("#dialogWindow"); box.html($("#templatesnapshot"+opt).html()); const capopt = opt.charAt(0).toUpperCase() + opt.slice(1); var optiontext = capopt + " _(Snapshot)_"; + //var fstype = "ZFS"; box.find('#VMName').html(name); + box.find('#fstype').html(fstype); box.find('#targetsnap').val(snaps); box.find('#targetsnapl').html(snaps); if (getlist) { @@ -282,9 +284,11 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist,state){ } if (opt == "create") { free = box.find('#targetsnapfspc').prop('checked') ? 'yes' : 'no'; + fstypeuse = box.find('#targetsnapfstype').prop('checked') ? 'yes' : 'no'; + if (fstypeuse == "no") fstype ="QEMU"; desc = box.find("#targetsnapdesc").prop('value'); } - ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid, snapshotname:target, remove:remove, free:free, removemeta:removemeta, keep:keep, desc:desc}, "loadlist"); + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid, snapshotname:target, remove:remove, free:free, removemeta:removemeta, keep:keep, desc:desc, fstype:fstype}, "loadlist"); box.dialog('close'); }, "_(Cancel)_": function(){ @@ -492,6 +496,7 @@ $(function() { _(VM Name)_: _(Snapshot Name)_:_(Check free space)_: _(Description )_: +_(FS Native Snapshot )_:_(Unchecked will use QEMU External Snapshot)_ diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php b/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php index 2577382bd..6319a2212 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php @@ -108,7 +108,9 @@ foreach ($vms as $vm) { } unset($dom); if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ; - $menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole,$vmpreview); + if ($diskcnt > 0) $fstype = $lv->get_disk_fstype($res); else $fstype="QEMU"; + #$fstype = "ZFS"; + $menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log),addslashes($fstype), $vmrcconsole,$vmpreview); $kvm[] = "kvm.push({id:'$uuid',state:'$state'});"; switch ($state) { case 'running': @@ -130,7 +132,7 @@ foreach ($vms as $vm) { } /* VM information */ - if ($snapshots != null) $snapshotstr = '('._('Snapshots').': '.count($snapshots).')'; else $snapshotstr = '('._('Snapshots').': '._('None').')'; + if ($snapshots != null) $snapshotstr = '('._('Snapshots').': '.count($snapshots)." / Type: $fstype)"; else $snapshotstr = '('._('Snapshots').': '._('None')." / Type: $fstype)"; $cdbus = $cdbus2 = $cdfile = $cdfile2 = ""; $cdromcount = 0; foreach ($cdroms as $arrCD) { @@ -280,7 +282,7 @@ foreach ($vms as $vm) { $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])); $snapshotparent = $snapshot["parent"] ? $snapshot["parent"] : "None"; $snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."
".my_time($snapshot["creationtime"],"H:i:s"); - $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"],$vmpreview); + $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"],$snapshot["method"],$vmpreview); echo "$tab|__   ",$snapshot["name"],"$snapshotdesc$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; $tab .="    "; } diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php index 178f5570f..655ad4c78 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/VMajax.php +++ b/emhttp/plugins/dynamix.vm.manager/include/VMajax.php @@ -338,7 +338,7 @@ case 'snap-create': case 'snap-create-external': requireLibvirt(); - $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free']) ; + $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free'],$_REQUEST['fstype']) ; break; case 'snap-images': @@ -396,7 +396,7 @@ case 'disk-create': $driver = $_REQUEST['driver']; $size = str_replace(["KB","MB","GB","TB","PB", " ", ","], ["K","M","G","T","P", "", ""], strtoupper($_REQUEST['size'])); $dir = dirname($disk); - if (!is_dir($dir)) mkdir($dir); + if (!is_dir($dir)) my_vmmkdir($dir); // determine the actual disk if user share is being used $dir = transpose_user_path($dir); #@exec("chattr +C -R ".escapeshellarg($dir)." >/dev/null"); diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php index 847077d9b..30f70af02 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php @@ -175,7 +175,7 @@ // create folder if needed if (!is_dir($strImgFolder)) { - mkdir($strImgFolder, 0777, true); + my_mkdir($strImgFolder); chown($strImgFolder, 'nobody'); chgrp($strImgFolder, 'users'); } @@ -191,7 +191,7 @@ // create parent folder if needed if (!is_dir($path_parts['dirname'])) { - mkdir($path_parts['dirname'], 0777, true); + my_mkdir($path_parts['dirname']); chown($path_parts['dirname'], 'nobody'); chgrp($path_parts['dirname'], 'users'); } @@ -216,7 +216,7 @@ // create folder if needed $strImgRawLocationParent = dirname($strImgRawLocationPath); if (!is_dir($strImgRawLocationParent)) { - mkdir($strImgRawLocationParent, 0777, true); + my_mkdir($strImgRawLocationParent); chown($strImgRawLocationParent, 'nobody'); chgrp($strImgRawLocationParent, 'users'); } @@ -1409,6 +1409,20 @@ return $ret; } + function get_disk_fstype($domain) { + $dom = $this->get_domain_object($domain); + $tmp = $this->get_disk_stats($dom); + $dirname = transpose_user_path($tmp[0]['file']); + $pathinfo = pathinfo($dirname); + $parent = $pathinfo["dirname"]; + $fstype = strtoupper(trim(shell_exec(" stat -f -c '%T' $parent"))); + if ($fstype != "ZFS") $fstype = "QEMU"; + #if ($fstype != "ZFS" && $fstype != "BTRFS") $fstype = "QEMU"; + unset($tmp); + + return $fstype; + } + function format_size($value, $decimals, $unit='?') { if ($value == '-') return 'unknown'; diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 21fb48052..b8322d33e 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1681,7 +1681,7 @@ private static $encoding = 'UTF-8'; if ($storage == "default") $clonedir = $domain_cfg['DOMAINDIR'].$clone ; else $clonedir = str_replace('/mnt/user/', "/mnt/$storage/", $domain_cfg['DOMAINDIR']).$clone; if (!is_dir($clonedir)) { - mkdir($clonedir,0777,true) ; + my_mkdir($clonedir) ; chown($clonedir, 'nobody'); chgrp($clonedir, 'users'); } @@ -1732,17 +1732,33 @@ private static $encoding = 'UTF-8'; return $snaps ; } - function write_snapshots_database($vm,$name,$method="QEMU") { + function write_snapshots_database($vm,$name,$state,$desc,$method="QEMU") { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; if (!is_dir($dbpath)) mkdir($dbpath) ; + $noxml = ""; $snaps_json = file_get_contents($dbpath."/snapshots.db") ; $snaps = json_decode($snaps_json,true) ; $snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$name) ; + if (!$snapshot_res) { + # Manual Snap no XML + if ($state == "shutoff" && ($method == "ZFS" || $method == "BTRFS")) { + # Create Snapshot info + $vmsnap = $name; + $snaps[$vmsnap]["name"]= $name; + $snaps[$vmsnap]["parent"]= "Base" ; + $snaps[$vmsnap]["state"]= "shutoff"; + $snaps[$vmsnap]["desc"]= $desc; + $snaps[$vmsnap]["memory"]= ['@attributes' => ['snapshot' => 'no']]; + $snaps[$vmsnap]["creationtime"]= date("U"); + $snaps[$vmsnap]["method"]= $method; + $noxml = "noxml"; + } + } else { $snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ; $a = simplexml_load_string($snapshot_xml) ; $a = json_encode($a) ; - $b= json_decode($a, TRUE); + $b = json_decode($a, TRUE); $vmsnap = $b["name"] ; $snaps[$vmsnap]["name"]= $b["name"]; $snaps[$vmsnap]["parent"]= $b["parent"] ; @@ -1751,6 +1767,7 @@ private static $encoding = 'UTF-8'; $snaps[$vmsnap]["memory"]= $b["memory"]; $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; $snaps[$vmsnap]["method"]= $method; + } $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { @@ -1773,11 +1790,12 @@ private static $encoding = 'UTF-8'; $snaps[$vmsnap]["parent"] = str_replace("qcow2",'',$snaps[$vmsnap]["parent"]) ; if (isset($parentfind[1]) && !isset($parentfind[2])) $snaps[$vmsnap]["parent"]="Base" ; - if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"]; + if (isset($b)) if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"]; $value = json_encode($snaps,JSON_PRETTY_PRINT) ; file_put_contents($dbpath."/snapshots.db",$value) ; + return $noxml; } function refresh_snapshots_database($vm) { @@ -1847,8 +1865,33 @@ private static $encoding = 'UTF-8'; return true ; } + function my_vmmkdir($dirname) { + $pathinfo = pathinfo($dirname); + $parent = $pathinfo["dirname"]; + $userPathFound = strpos($dirname,"/mnt/user"); + $realdir = $dirname; + if ($userPathFound !== false) { + $realLocation = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg("$parent"))); + $realdir = str_replace("/mnt/user","/mnt/$realLocation",$dirname); + $parent = dirname($realdir); + } + $fstype = trim(shell_exec(" stat -f -c '%T' $parent")); + switch ($fstype) { + case "zfs": + $zfsdataset = trim(shell_exec("zfs list -H -o name $parent")) ; + shell_exec("zfs create $zfsdataset/{$pathinfo['filename']}"); + break; + case "btrfs": + shell_exec("btrfs subvolume create $realdir"); + break; + default: + mkdir($realdir, 0777, true); + break; + } +} - function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $memorysnap = "yes") { + + function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $method = "QEMU", $memorysnap = "yes") { global $lv ; #Get State @@ -1876,7 +1919,14 @@ private static $encoding = 'UTF-8'; $dirpath= str_replace('/mnt/user/', "/mnt/$storagelocation/", $dirpath); } $filenew = $dirpath.'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; - $diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ; + switch ($method) { + case "QEMU" : + $diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ; + break; + case "ZFS": + case "BTRFS": + $diskspec .= " --diskspec '".$disk["device"]."',snapshot=manual " ; + } $capacity = $capacity + $disk["capacity"] ; } @@ -1884,7 +1934,7 @@ private static $encoding = 'UTF-8'; $mem = $lv->domain_get_memory_stats($vm) ; $memory = $mem[6] ; - if ($memorysnap = "yes") $memspec = ' --memspec "'.$dirpath.'/memory'.$name.'.mem",snapshot=external' ; else $memspec = "" ; + if ($memorysnap == "yes") $memspec = ' --memspec "'.$dirpath.'/memory'.$name.'.mem",snapshot=external' ; else $memspec = "" ; $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' $snapshotdesc --atomic" ; @@ -1911,16 +1961,36 @@ private static $encoding = 'UTF-8'; if ($state == "running") exec("virsh dumpxml '$vm' > ".escapeshellarg($xmlfile),$outxml,$rtnxml) ; $output= [] ; - $test = false ; - if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ; + #$test = false ; + #if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ; + + switch ($method) { + case "ZFS": + # Create ZFS Snapshot + #$zfsdataset = "vmpoolzfs/domains3/Arch3"; + #stat -f -c '%T' /mnt/vmpoolzfs/domains2/Arch3 + if ($state == "running") exec($cmdstr." 2>&1",$output,$return); + $zfsdataset = trim(shell_exec("zfs list -H -o name -r $dirpath")) ; + $fssnapcmd = " zfs snapshot $zfsdataset@$name"; + shell_exec($fssnapcmd); + # if running resume. + if ($state == "running") $lv->domain_resume($vm); + break; + case "BTRFS": + # Create BTRFS Snapshot + break; + default: + # No Action + exec($cmdstr." 2>&1",$output,$return); + } if (strpos(" ".$output[0],"error") ) { $arrResponse = ['error' => substr($output[0],6) ] ; } else { $arrResponse = ['success' => true] ; - write_snapshots_database("$vm","$name") ; + $ret = write_snapshots_database("$vm","$name",$state,$snapshotdesc,$method) ; #remove meta data - $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ; + if ($ret != "noxml") $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ; } return $arrResponse ; @@ -1980,12 +2050,14 @@ private static $encoding = 'UTF-8'; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; if (is_file($path) && $action == "yes") unlink("$path") ; + file_put_contents("/tmp/rmvsnaps",$path,FILE_APPEND); $item = array_search($path,$snapslist[$snap]['backing']["r".$diskname]) ; $item++ ; while($item > 0) { if (!isset($snapslist[$snap]['backing']["r".$diskname][$item])) break ; $newpath = $snapslist[$snap]['backing']["r".$diskname][$item] ; + file_put_contents("/tmp/rmvsnaps",$newpath,FILE_APPEND); if (is_file($newpath) && $action == "yes") unlink("$newpath") ; $item++ ; } diff --git a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js index 588cc6d11..e8c662aa9 100644 --- a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -62,7 +62,7 @@ function ajaxVMDispatchconsoleRV(params, spin){ } },'json'); } -function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, console="web", preview=false){ +function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, fstype="QEMU",console="web", preview=false){ var opts = []; var path = location.pathname; var x = path.indexOf("?"); @@ -110,7 +110,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) { e.preventDefault(); - selectsnapshot(uuid , name, "--generate" , "create",false,state) ; + selectsnapshot(uuid , name, "--generate" , "create",false,state,fstype) ; }}); } else if (state == "pmsuspended") { opts.push({text:_("Resume"), icon:"fa-play", action:function(e) { @@ -165,7 +165,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c opts.push({divider:true}); opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) { e.preventDefault(); - selectsnapshot(uuid , name, "--generate" , "create",false,state) ; + selectsnapshot(uuid , name, "--generate" , "create",false,state,fstype) ; }}); opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) { e.preventDefault(); @@ -200,7 +200,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c } context.attach('#vm-'+uuid, opts); } -function addVMSnapContext(name, uuid, template, state, snapshotname, preview=false){ +function addVMSnapContext(name, uuid, template, state, snapshotname, method, preview=false){ var opts = []; var path = location.pathname; var x = path.indexOf("?"); @@ -213,7 +213,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname, preview=fal e.preventDefault(); selectsnapshot(uuid, name, snapshotname, "revert",true) ; }}); - + if (method == "QEMU") { opts.push({text:_("Block Commit"), icon:"fa-hdd-o", action:function(e) { $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); e.preventDefault(); @@ -230,6 +230,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname, preview=fal e.preventDefault(); ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); }}); } + } } else { opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) { e.preventDefault(); diff --git a/emhttp/plugins/dynamix/include/Helpers.php b/emhttp/plugins/dynamix/include/Helpers.php index 0d2ecc3d5..2b9569c68 100644 --- a/emhttp/plugins/dynamix/include/Helpers.php +++ b/emhttp/plugins/dynamix/include/Helpers.php @@ -270,4 +270,25 @@ function my_preg_split($split, $text, $count=2) { function delete_file(...$file) { array_map('unlink',array_filter($file,'file_exists')); } +function my_mkdir($dirname,$permissions = 0777,$recursive = false) { + $dirname = transpose_user_path($dirname); + $pathinfo = pathinfo($dirname); + $parent = $pathinfo["dirname"]; + $fstype = trim(shell_exec(" stat -f -c '%T' $parent")); + $rtncode = false; + switch ($fstype) { + case "zfs": + $zfsdataset = trim(shell_exec("zfs list -H -o name $parent")) ; + $rtncode=exec("zfs create $zfsdataset/{$pathinfo['filename']}"); + if (!$rtncode) mkdir($dirname, $permissions, $recursive); + break; + case "btrfs": + $rtncode=exec("btrfs subvolume create $dirname"); + if (!$rtncode) mkdir($dirname, $permissions, $recursive); + break; + default: + mkdir($dirname, $permissions, $recursive); + break; + } +} ?>