From 8020bd3518614f1bd5f3d1a3aad5958fd90e37f6 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sat, 6 May 2023 00:00:36 +0100 Subject: [PATCH 01/18] Add snapshots to view --- .../dynamix.vm.manager/include/VMMachines.php | 23 ++++++++++++++++--- plugins/dynamix.vm.manager/include/VMajax.php | 5 ++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index 7e103c663..c36e56026 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -51,6 +51,7 @@ $icon = $lv->domain_get_icon_url($res); $image = substr($icon,-4)=='.png' ? "" : (substr($icon,0,5)=='icon-' ? "" : ""); $arrConfig = domain_to_config($uuid); + $snapshots = getvmsnapshots($vm) ; if ($state == 'running') { $mem = $dom['memory']/1024; } else { @@ -123,12 +124,13 @@ } /* VM information */ + if ($snapshots != null) $snapshotstr = _("(Snapshots :").count($snapshots).')' ; else $snapshotstr = _("(Snapshots :None)") ; echo ""; - echo "$image$vm
"._($status)."
"; + echo "$image$vm
"._($status)." $snapshotstcount
"; echo "$desc"; echo "$vcpu"; echo "$mem"; - echo "$disks"; + echo "$disks
$snapshotstr
"; echo "$graphics"; echo ""; @@ -218,7 +220,22 @@ if ($gastate == "disconnected") echo ""._('Guest agent not installed').""; else echo ""._('Guest not running').""; } - echo ""; + echo ""; + /* Display VM Snapshots */ + if ($snapshots != null) { + $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole); + echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; + echo ""; + foreach($snapshots as $snapshotname => $snapshot) { + $snapshotstate = $snapshot["state"] ; + $snapshotmemory = $snapshot["memory"]["@attributes"]["snapshot"] ; + $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "None"; + $snapshotdatetime = my_time($snapshot["creationtime"],"Y-m-d" )."
".my_time($snapshot["creationtime"],"H:i:s") ; + echo "         ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; + } + echo ""; +} + echo ""; echo ""; } echo "\0".implode($kvm); diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 4ee16c5b9..dd816b899 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -300,6 +300,11 @@ function embed(&$syslinux, $key, $value) { : ['error' => $lv->get_last_error()]; break; +case 'snap-create-external': + requireLibvirt(); + $arrResponse = vm_snapshot($domName) ; + break; + case 'snap-delete': requireLibvirt(); $arrResponse = $lv->domain_snapshot_delete($domName, $_REQUEST['snap']) From f104dfcf2ba3ac60529c7d010b0a244eab7cda0f Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 7 May 2023 19:39:34 +0100 Subject: [PATCH 02/18] Initial Snapshot and revert function --- .../dynamix.vm.manager/include/VMMachines.php | 7 +- plugins/dynamix.vm.manager/include/VMajax.php | 5 + .../dynamix.vm.manager/include/libvirt.php | 24 +- .../include/libvirt_helpers.php | 238 ++++++++++++++++++ .../javascript/vmmanager.js | 80 ++++++ 5 files changed, 348 insertions(+), 6 deletions(-) diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index c36e56026..a1df722ee 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -223,14 +223,15 @@ echo ""; /* Display VM Snapshots */ if ($snapshots != null) { - $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole); + echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; echo ""; foreach($snapshots as $snapshotname => $snapshot) { - $snapshotstate = $snapshot["state"] ; - $snapshotmemory = $snapshot["memory"]["@attributes"]["snapshot"] ; + $snapshotstate = _(ucfirst($snapshot["state"])) ; + $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "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')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"]); echo "         ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; } echo ""; diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index dd816b899..1ebb5b4f6 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -305,6 +305,11 @@ function embed(&$syslinux, $key, $value) { $arrResponse = vm_snapshot($domName) ; break; +case 'snap-revert-external': + requireLibvirt(); + $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove']) ; + break; + case 'snap-delete': requireLibvirt(); $arrResponse = $lv->domain_snapshot_delete($domName, $_REQUEST['snap']) diff --git a/plugins/dynamix.vm.manager/include/libvirt.php b/plugins/dynamix.vm.manager/include/libvirt.php index eaa144ef7..66a9c8e7f 100644 --- a/plugins/dynamix.vm.manager/include/libvirt.php +++ b/plugins/dynamix.vm.manager/include/libvirt.php @@ -1846,6 +1846,19 @@ function nvram_restore($uuid) { return false; } + function nvram_snapshot($uuid,$snapshotname) { + // snapshot backup OVMF VARS if this domain had them + if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) { + copy('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd'); + return true; + } + if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) { + copy('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd', '/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd'); + return true; + } + return false; + } + function is_dir_empty($dir) { if (!is_readable($dir)) return NULL; $handle = opendir($dir); @@ -2447,6 +2460,12 @@ function domain_snapshots_list($domain) { return ($tmp) ? $tmp : $this->_set_last_error(); } + //list all snapshots for domain + function domain_snapshot_get_xml($domain) { + $tmp = libvirt_domain_snapshot_get_xml($domain); + return ($tmp) ? $tmp : $this->_set_last_error(); + } + // create a snapshot and metadata node for description function domain_snapshot_create($domain) { $this->domain_set_metadata($domain); @@ -2456,10 +2475,9 @@ function domain_snapshot_create($domain) { } //delete snapshot and metadata - function domain_snapshot_delete($domain, $name) { - $this->snapshot_remove_metadata($domain, $name); + function domain_snapshot_delete($domain, $name, $flags=0) { $name = $this->domain_snapshot_lookup_by_name($domain, $name); - $tmp = libvirt_domain_snapshot_delete($name); + $tmp = libvirt_domain_snapshot_delete($name,$flags); return ($tmp) ? $tmp : $this->_set_last_error(); } diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 7bf82d5cd..9bbb72582 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -163,6 +163,7 @@ private static function isValidTagName($tag){ $docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp'; require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt.php"; + require_once "$docroot/webGui/include/Custom.php"; // Load emhttp variables if needed. if (!isset($var)){ @@ -1203,6 +1204,7 @@ function domain_to_config($uuid) { $arrDisks[] = [ 'new' => $strPath, 'size' => '', + 'driver' => $disk['type'], 'driver' => 'raw', 'dev' => $disk['device'], 'bus' => $disk['bus'], @@ -1454,4 +1456,240 @@ function getcopypaste($res) { if ($spicevmc || $qemuvdaagent) $copypaste = true ; else $copypaste = false ; return $copypaste ; } + + function compare_creationtime($a, $b) { + return strnatcmp($a['creationtime'], $b['creationtime']); + } + + function compare_creationtimelt($a, $b) { + return $a['creationtime'] < $b['creationtime']; + } + + function getvmsnapshots($vm) { + global $lv ; + $vmsnaps = $lv->domain_snapshots_list($lv->get_domain_object($vm)) ; + $snaps=array() ; + foreach($vmsnaps as $vmsnap) { + $snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$vmsnap) ; + $snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ; + $a = simplexml_load_string($snapshot_xml) ; + if($a == false) continue ; + $a = json_encode($a) ; + $b= json_decode($a, TRUE); + $vmsnap = $b["name"] ; + $snaps[$vmsnap]["name"]= $b["name"]; + if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; + $snaps[$vmsnap]["state"]= $b["state"]; + $snaps[$vmsnap]["memory"]= $b["memory"]; + $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; + $snaps[$vmsnap]["disks"]= $b["disks"]; + } + + if (is_array($snaps)) uasort($snaps,'compare_creationtime') ; + return $snaps ; + } + + function vm_snapshot($vm,$memorysnap = "yes") { + global $lv ; + + #Get State + $res = $lv->get_domain_by_name($vm); + $dom = $lv->domain_get_info($res); + $state = $lv->domain_state_translate($dom['state']); + + #Get disks for --diskspec + $disks =$lv->get_disk_stats($vm) ; + $diskspec = "" ; + $capacity = 0 ; + $name= "S" . date("YmdHis") ; + $cmdstr = "virsh snapshot-create-as $vm --name $name --atomic " ; + + foreach($disks as $disk) { + $file = $disk["file"] ; + $pathinfo = pathinfo($file) ; + $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; + $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; + $capacity = $capacity + $disk["capacity"] ; + } + + #get memory + $mem = $lv->domain_get_memory_stats($vm) ; + $memory = $mem[6] ; + + if ($memorysnap = "yes") $memspec = " --memspec ".$pathinfo["dirname"].'/memory'.$name.".mem,snapshot=external" ; else $memspec = "" ; + $cmdstr = "virsh snapshot-create-as $vm --name $name --atomic" ; + + + if ($state == "running") { + $cmdstr .= " --live ".$memspec.$diskspec ; + $capacity = $capacity + $memory ; + + } else { + $cmdstr .= " --disk-only ".$diskspec ; + } + + #Check free space. + $dirfree = disk_free_space($pathinfo["dirname"]) ; + + $capacity *= 1 ; + + #if ($dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse ;} + + #Copy nvram + if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_snapshot($lv->domain_get_uuid($vm),$name) ; + + $test = false ; + if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ; + + if (strpos(" ".$output[0],"error") ) { + $arrResponse = ['error' => substr($output[0],6) ] ; + } else { + # Remove nvram snapshot + $arrResponse = ['success' => true] ; + } + + return $arrResponse ; + + } + + function vm_revert($vm, $snap="--current",$action="no") { + global $lv ; + $snapslist= getvmsnapshots($vm) ; + + #echo "Revert Vm: $vm snap: $snap\n" ; + + $disks =$lv->get_disk_stats($vm) ; + + foreach($disks as $disk) { + $file = $disk["file"] ; + $output = "" ; + exec("qemu-img info --backing-chain -U $file | grep image:",$output) ; + foreach($output as $key => $line) { + $line=str_replace("image: ","",$line) ; + $output[$key] = $line ; + } + + $snaps[$vm][$disk["device"]] = $output ; + $rev = "r".$disk["device"] ; + $reversed = array_reverse($output) ; + $snaps[$vm][$rev] = $reversed ; + $pathinfo = pathinfo($file) ; + $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; + $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; + $capacity = $capacity + $disk["capacity"] ; + } + + switch ($snapslist[$snap]['state']) { + case "shutoff": + case "running": + #VM must be shutdown. + $res = $lv->get_domain_by_name($vm); + $dom = $lv->domain_get_info($res); + $state = $lv->domain_state_translate($dom['state']); + # if VM running shutdown. Record was running. + if ($state != 'shutdown') $arrResponse = $lv->domain_destroy($vm) ; + # Wait for shutdown? + # GetXML + $strXML= $lv->domain_get_xml($res) ; + $xmlobj = custom::createArray('domain',$strXML) ; + + # Process disks and update path. + $disks=($snapslist[$snap]['disks']["disk"]) ; + foreach ($disks as $disk) { + $diskname = $disk["@attributes"]["name"] ; + if ($diskname == "hda" || $diskname == "hdb") continue ; + $path = $disk["source"]["@attributes"]["file"] ; + $item = array_search($path,$snaps[$vm][$diskname]) ; + $newpath = $snaps[$vm][$diskname][$item + 1]; + $json_info = getDiskImageInfo($newpath) ; + #echo "Newpath: $newpath image type ".$json_info["format"]."\n" ; + foreach($xmlobj['devices']['disk'] as $ddk => $dd){ + if ($dd['target']["@attributes"]['dev'] == $diskname) { + $xmlobj['devices']['disk'][$ddk]['source']["@attributes"]['file'] = $newpath ; + $xmlobj['devices']['disk'][$ddk]['driver']["@attributes"]['type'] = $json_info["format"] ; + } + } + } + $xml = custom::createXML('domain',$xmlobj)->saveXML(); + $new = $lv->domain_define($xml); + if ($new) + $arrResponse = ['success' => true] ; else + $arrResponse = ['error' => $lv->get_last_error()] ; + + #echo "update xml \n" ; + + + # remove snapshot meta data and images for all snpahots. + + foreach ($disks as $disk) { + $diskname = $disk["@attributes"]["name"] ; + if ($diskname == "hda" || $diskname == "hdb") continue ; + $path = $disk["source"]["@attributes"]["file"] ; + #echo "rm $path\n" ; + if (is_file($path) && $action == "yes") unlink($path) ; + $item = array_search($path,$snaps[$vm]["r".$diskname]) ; + $item++ ; + while($item > 0) + { + if (!isset($snaps[$vm]["r".$diskname][$item])) break ; + $newpath = $snaps[$vm]["r".$diskname][$item] ; + # rm $newpath + if (is_file($path) && $action == "yes") unlink($newpath) ; + #echo "rm $newpath\n" ; + + $item++ ; + + } + } + + uasort($snapslist,'compare_creationtimelt') ; + foreach($snapslist as $s) { + #var_dump($s['name']) ; + $name = $s['name'] ; + #echo "delete snapshot --metadata ".$s["name"]."\n" ; + #Delete Metadata only. + if ($action == "yes") { + $ret = $lv->domain_snapshot_delete($vm, $name ,2) ; + #echo "Error: $ret\n" ; + } #else echo "Run domain_snapshot_delete($vm, $name ,2\n" ; + if ($s['name'] == $snap) break ; + } + #if VM was started restart. + if ($state == 'running') { + #echo "Restart VM\n" ; + $arrResponse = $lv->domain_start($vm) ; + } #else echo "VM Shutdown\n" ; + + + break ; + + + case "other": + break ; + #VM must be shutdown. + # if VM running shutdown. Record was running. + # Replace disk paths + # remove snapshot meta data and images for all snpahots. + # if VM was started restart. + + + #type running + #Non Live restores + #VM must be shutdown. + # if VM running shutdown. Record was running. + # Replace disk paths + # remove snapshot meta data and images for all snpahots. + # if VM was started restart. + + #Live restore(currently not supported.) + # Freeze VM + # Replace disk paths + # Replace Mem + # remove snapshot meta data and images for all snpahots. + # Unfreeze VM + } + $arrResponse = ['success' => true] ; + return($arrResponse) ; + } + ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index 5cea015df..dbd941570 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -143,6 +143,21 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c } }} opts.push({divider:true}); + opts.push({text:_("Snapshot"), icon:"fa-clone", action:function(e) { + e.preventDefault(); + swal({ + title:_("Are you sure?"), + text:_("Create snapshot for VM: ")+name, + type:"warning", + showCancelButton:true, + confirmButtonText:_('Proceed'), + cancelButtonText:_('Cancel') + },function(){ + $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-clone fa-spin'); + ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); + }); + }}); + opts.push({divider:true}); if (log !== "") { opts.push({text:_("Logs"), icon:"fa-navicon", action:function(e){e.preventDefault(); openTerminal('log',name,log);}}); } @@ -182,6 +197,71 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c } context.attach('#vm-'+uuid, opts); } +function addVMSnapContext(name, uuid, template, state, snapshotname){ + var opts = []; + var path = location.pathname; + var x = path.indexOf("?"); + if (x!=-1) path = path.substring(0,x); + + context.settings({right:false,above:false}); + if (state == "running") { +// opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { +// e.preventDefault(); +// ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); +// }}); +// opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { +// e.preventDefault(); +// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); +// }}); +// opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { +// e.preventDefault(); +// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); +// }}); + } else { + opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { + e.preventDefault(); + swal({ + title:_("Are you sure?"), + text:_("Revert Snapshot ") + snapshotname + _("\nfor VM: ") + name +"\n Note all snapshots taken after will be removed.", + type:"warning", + showCancelButton:true, + confirmButtonText:_('Proceed'), + cancelButtonText:_('Cancel') + },function(){ + $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); + ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); + }); + }}); +// opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { +// e.preventDefault(); +// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); +// }}); +// opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { +// e.preventDefault(); +// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); +// }}); +} + + if (state == "shutoff") { + opts.push({divider:true}); +// opts.push({text:_("Remove Snapshot"), icon:"fa-minus", action:function(e) { +// e.preventDefault(); +// snapname = "test" ; +// swal({ +// title:_("Are you sure?"), +// text:_("Remove Snapshot:") + snapname + _("\nfor VM: ") + name +"\n Note all snapshots taken after will be become invalid.", +// type:"warning", +// showCancelButton:true, +// confirmButtonText:_('Proceed'), +// cancelButtonText:_('Cancel') +// },function(){ +// $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); +// ajaxVMDispatch({action:"snap-remove-external", uuid:uuid,snapshotname:snapshotname}, "loadlist"); +// }); +// }}); + } + context.attach('#vmsnap-'+uuid, opts); +} function startAll() { $('input[type=button]').prop('disabled',true); for (var i=0,vm; vm=kvm[i]; i++) if (vm.state!='running') $('#vm-'+vm.id).parent().find('i').removeClass('fa-square').addClass('fa-refresh fa-spin'); From 7af43228c85f2a04ae200bc7b0d2166b2c1ac99d Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 7 May 2023 20:04:38 +0100 Subject: [PATCH 03/18] Change Snapshot option to show only for shutdown VMs. --- .../javascript/vmmanager.js | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index dbd941570..cb7d25560 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -143,27 +143,26 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c } }} opts.push({divider:true}); - opts.push({text:_("Snapshot"), icon:"fa-clone", action:function(e) { - e.preventDefault(); - swal({ - title:_("Are you sure?"), - text:_("Create snapshot for VM: ")+name, - type:"warning", - showCancelButton:true, - confirmButtonText:_('Proceed'), - cancelButtonText:_('Cancel') - },function(){ - $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-clone fa-spin'); - ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); - }); - }}); - opts.push({divider:true}); if (log !== "") { opts.push({text:_("Logs"), icon:"fa-navicon", action:function(e){e.preventDefault(); openTerminal('log',name,log);}}); } opts.push({text:_("Edit"), icon:"fa-pencil", href:path+'/UpdateVM?uuid='+uuid}); if (state == "shutoff") { opts.push({divider:true}); + opts.push({text:_("Snapshot"), icon:"fa-clone", action:function(e) { + e.preventDefault(); + swal({ + title:_("Are you sure?"), + text:_("Create snapshot for VM: ")+name, + type:"warning", + showCancelButton:true, + confirmButtonText:_('Proceed'), + cancelButtonText:_('Cancel') + },function(){ + $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-clone fa-spin'); + ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); + }); + }}); opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) { e.preventDefault(); swal({ From a953d903f4bff4ced22190e6b87182630943c7e7 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Mon, 8 May 2023 17:05:01 +0100 Subject: [PATCH 04/18] Add popups and delete function. Error with checkbox on revert page and real file delete is disabled --- plugins/dynamix.vm.manager/VMMachines.page | 100 +++++++++++++- plugins/dynamix.vm.manager/include/VMajax.php | 13 +- .../include/libvirt_helpers.php | 128 +++++++++++++++++- .../javascript/vmmanager.js | 34 ++--- 4 files changed, 246 insertions(+), 29 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 1981a9b8d..3460d1416 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -203,8 +203,68 @@ function getisoimage(uuid,dev,bus,file){ }); dialogStyle(); } + +function selectsnapshot(uuid, name ,snaps, opt, getlist){ + + var root = ; + var match= ".iso"; + var box = $("#dialogWindow2"); + box.html($("#templatesnapshot"+opt).html()); + var height = 200; + const Capopt = opt.charAt(0).toUpperCase() + opt.slice(1) ; + var optiontext = Capopt + " Snapshot" ; + box.find('#VMName').html(name) ; + box.find('#targetsnap').val(snaps) ; + box.find('#targetsnapl').html(snaps) ; + if (getlist) { + var only = 1 ; + if (opt == "remove") only = 0; + $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"snap-images", uuid:uuid , snapshotname:snaps, only:only}, function(data) { + if (data.html) { + box.find('#targetsnapimages').html(data.html) ; + } + },'json'); + } + + document.getElementById("targetsnaprmv").checked = true ; + + box.dialog({ + title: "_("+optiontext+ ")_", + resizable: false, + width: 600, + height: 500, + modal: true, + show: {effect:'fade', duration:250}, + hide: {effect:'fade', duration:250}, + + buttons: { + "_(Proceed)_": function(){ + var target = box.find('#targetsnap'); + if (target.length) { + target = target.val(); + if (!target ) {errorTarget(); return;} + } else target = ''; + box.find('#targetsnap').prop('disabled',true); + if (opt == "revert") { + const x = document.querySelector('#targetsnaprmv') ; + if (x.checked) remove = 'yes' ; else remove = 'no' ; + } + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:"yes" } , "loadlist"); + box.dialog('close'); + }, + "_(Cancel)_": function(){ + box.dialog('close'); + } + } + }); + dialogStyle(); +} + +// ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); + // ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); + function dialogStyle() { - $('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('').prop('title',"_(Close)_").prop('onclick',null).off('click').click(function(){box.dialog('close');}); + $('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('').prop('title'); $('.ui-dialog-title').css({'text-align':'center','width':'100%','font-size':'1.8rem'}); $('.ui-dialog-content').css({'padding-top':'15px','vertical-align':'bottom'}); $('.ui-button-text').css({'padding':'0px 5px'}); @@ -304,5 +364,43 @@ $(function() {
_(ISO Image)_: : +
+ +
+
+_(VM Name)_: + +
+_(Snapshot Name)_: + +
+ +
+_(VM Name)_: + +
+_(Snapshot Name)_: + +
+_(Remove Images)_: +
+ +
+ + +
+ +
+_(!! Warning removing Snapshots can break the chain !!)_

+ +_(VM Name)_: + +
+_(Snapshot Name)_: + +
+ +
+
\ No newline at end of file diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 1ebb5b4f6..6c7a05d90 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -302,7 +302,13 @@ function embed(&$syslinux, $key, $value) { case 'snap-create-external': requireLibvirt(); - $arrResponse = vm_snapshot($domName) ; + $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname']) ; + break; + +case 'snap-images': + requireLibvirt(); + $html = vm_snapimages($domName,$_REQUEST['snapshotname'],$_REQUEST['only']) ; + $arrResponse = ['html' => $html , 'success' => true] ; break; case 'snap-revert-external': @@ -310,6 +316,11 @@ function embed(&$syslinux, $key, $value) { $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove']) ; break; +case 'snap-remove-external': + requireLibvirt(); + $arrResponse = vm_snapremove($domName,$_REQUEST['snapshotname']) ; + break; + case 'snap-delete': requireLibvirt(); $arrResponse = $lv->domain_snapshot_delete($domName, $_REQUEST['snap']) diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 9bbb72582..3a8c24a68 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1489,7 +1489,7 @@ function getvmsnapshots($vm) { return $snaps ; } - function vm_snapshot($vm,$memorysnap = "yes") { + function vm_snapshot($vm,$snapshotname,$memorysnap = "yes") { global $lv ; #Get State @@ -1501,7 +1501,7 @@ function vm_snapshot($vm,$memorysnap = "yes") { $disks =$lv->get_disk_stats($vm) ; $diskspec = "" ; $capacity = 0 ; - $name= "S" . date("YmdHis") ; + if ($snapshotname == "--generate") $name= "S" . date("YmdHis") ; else $name=$snapshotname ; $cmdstr = "virsh snapshot-create-as $vm --name $name --atomic " ; foreach($disks as $disk) { @@ -1691,5 +1691,127 @@ function vm_revert($vm, $snap="--current",$action="no") { $arrResponse = ['success' => true] ; return($arrResponse) ; } - + + function vm_snapimages($vm, $snap, $only) { + global $lv ; + $snapslist= getvmsnapshots($vm) ; + $data = "

Images and metadata to remove if tickbox checked.
" ; + + #echo "Revert Vm: $vm snap: $snap\n" ; + + $disks =$lv->get_disk_stats($vm) ; + foreach($disks as $disk) { + $file = $disk["file"] ; + $output = "" ; + exec("qemu-img info --backing-chain -U $file | grep image:",$output) ; + foreach($output as $key => $line) { + $line=str_replace("image: ","",$line) ; + $output[$key] = $line ; + } + + $snaps[$vm][$disk["device"]] = $output ; + $rev = "r".$disk["device"] ; + $reversed = array_reverse($output) ; + $snaps[$vm][$rev] = $reversed ; + $pathinfo = pathinfo($file) ; + $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; + $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; + $capacity = $capacity + $disk["capacity"] ; + } + #var_dump($snaps) ; + $disks=($snapslist[$snap]['disks']["disk"]) ; + foreach ($disks as $disk) { + $diskname = $disk["@attributes"]["name"] ; + if ($diskname == "hda" || $diskname == "hdb") continue ; + $path = $disk["source"]["@attributes"]["file"] ; + #echo "rm $path\n" ; + if (is_file($path)) $data .= "$path
" ; + $item = array_search($path,$snaps[$vm]["r".$diskname]) ; + $item++ ; + if ($only == 0) $item = 0 ; + while($item > 0) + { + if (!isset($snaps[$vm]["r".$diskname][$item])) break ; + $newpath = $snaps[$vm]["r".$diskname][$item] ; + # rm $newpath + if (is_file($path)) $data .= "$path
" ; + #echo "rm $newpath\n" ; + + $item++ ; + + } + } + $data .= "
Snapshots metadata to remove." ; + if ($only == 0) { + $data .= "
$snap"; + } else { + uasort($snapslist,'compare_creationtimelt') ; + foreach($snapslist as $s) { + $name = $s['name'] ; + $data .= "
$name"; + if ($s['name'] == $snap) break ; + } + } + return($data) ; + } + + + function vm_snapremove($vm, $snap) { + global $lv ; + $snapslist= getvmsnapshots($vm) ; + $res = $lv->get_domain_by_name($vm); + $dom = $lv->domain_get_info($res); + + $disks =$lv->get_disk_stats($vm) ; + foreach($disks as $disk) { + $file = $disk["file"] ; + $output = "" ; + exec("qemu-img info --backing-chain -U $file | grep image:",$output) ; + foreach($output as $key => $line) { + $line=str_replace("image: ","",$line) ; + $output[$key] = $line ; + } + + $snaps[$vm][$disk["device"]] = $output ; + $rev = "r".$disk["device"] ; + $reversed = array_reverse($output) ; + $snaps[$vm][$rev] = $reversed ; + $pathinfo = pathinfo($file) ; + $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; + $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; + $capacity = $capacity + $disk["capacity"] ; + } + + # GetXML + $strXML= $lv->domain_get_xml($res) ; + $xmlobj = custom::createArray('domain',$strXML) ; + + # Process disks and update path. + $disks=($snapslist[$snap]['disks']["disk"]) ; + foreach ($disks as $disk) { + $diskname = $disk["@attributes"]["name"] ; + if ($diskname == "hda" || $diskname == "hdb") continue ; + $path = $disk["source"]["@attributes"]["file"] ; + $item = array_search($path,$snaps[$vm][$diskname]) ; + if (!$item) { + #if currently attached to VM error. + $data = ["error" => "Image currently active for this domain."] ; + return ($data) ; + } + #echo "Newpath: $newpath image type ".$json_info["format"]."\n" ; + } + + $disks=($snapslist[$snap]['disks']["disk"]) ; + foreach ($disks as $disk) { + $diskname = $disk["@attributes"]["name"] ; + if ($diskname == "hda" || $diskname == "hdb") continue ; + $path = $disk["source"]["@attributes"]["file"] ; + #if (is_file($path)) unlink($path) ; + } + + #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + $data = ["success => 'true"] ; + return($data) ; + } + ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index cb7d25560..d7ef4d57f 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -149,19 +149,9 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c opts.push({text:_("Edit"), icon:"fa-pencil", href:path+'/UpdateVM?uuid='+uuid}); if (state == "shutoff") { opts.push({divider:true}); - opts.push({text:_("Snapshot"), icon:"fa-clone", action:function(e) { + opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) { e.preventDefault(); - swal({ - title:_("Are you sure?"), - text:_("Create snapshot for VM: ")+name, - type:"warning", - showCancelButton:true, - confirmButtonText:_('Proceed'), - cancelButtonText:_('Cancel') - },function(){ - $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-clone fa-spin'); - ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); - }); + selectsnapshot(uuid , name, "--generate" , "create",false) ; }}); opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) { e.preventDefault(); @@ -217,19 +207,15 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ // ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); // }}); } else { - opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { + opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) { e.preventDefault(); - swal({ - title:_("Are you sure?"), - text:_("Revert Snapshot ") + snapshotname + _("\nfor VM: ") + name +"\n Note all snapshots taken after will be removed.", - type:"warning", - showCancelButton:true, - confirmButtonText:_('Proceed'), - cancelButtonText:_('Cancel') - },function(){ - $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); - ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); - }); + $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); + selectsnapshot(uuid, name, snapshotname, "revert",true) ; + }}); + opts.push({text:_("Remove snapshot(Delete Disabled)"), icon:"fa-trash", action:function(e) { + e.preventDefault(); + $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); + selectsnapshot(uuid, name, snapshotname, "remove",true) ; }}); // opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { // e.preventDefault(); From 5e4bd81b4bcdba7850543c6cec2ef0f9fd8a166a Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 11 May 2023 17:25:17 +0100 Subject: [PATCH 05/18] Updates to snapshots. --- plugins/dynamix.vm.manager/VMMachines.page | 74 ++++++++++++++++-- plugins/dynamix.vm.manager/include/VMajax.php | 36 ++++++++- .../dynamix.vm.manager/include/libvirt.php | 17 ++++- .../include/libvirt_helpers.php | 76 +++++++------------ 4 files changed, 149 insertions(+), 54 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 3460d1416..b90156384 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -134,7 +134,7 @@ i.mover{margin-right:8px;display:none} .dropdown-menu{z-index:10001} - +
_(Name)__(Description)__(CPUs)__(Memory)__(vDisks)__(Graphics)__(Autostart)_
_(Name)__(Description)__(CPUs)__(Memory)__(vDisks/vCDs)__(Graphics)__(Autostart)_
@@ -163,7 +163,56 @@ function resetSorting() { function changemedia(uuid,dev,bus,file) { if (file === "--select") getisoimage(uuid,dev,bus,file); if (file === "--eject") ajaxVMDispatch({action:"change-media", uuid:uuid , cdrom:"" , dev:dev , bus:bus , file:file}, "loadlist"); +} +function getisoimageboth(uuid,dev,bus,file,dev2,bus2,file2){ + var root = ; + var match= ".iso"; + var box = $("#dialogWindow"); + box.html($("#templateISOboth").html()); + box.find('#target').attr('data-pickroot',root).attr('data-picktop',root).attr('data-pickmatch',match).attr('value', file).fileTreeAttach(null,null,function(path){ + var bits = path.substr(1).split('/'); + var auto = bits.length>3 ? '' : share; + box.find('#target').val(path+auto).change(); + }); + box.find('#target2').attr('data-pickroot',root).attr('data-picktop',root).attr('data-pickmatch',match).attr('value', file2).fileTreeAttach(null,null,function(path){ + var bits = path.substr(1).split('/'); + var auto = bits.length>3 ? '' : share; + box.find('#target2').val(path+auto).change(); + }); + var height = 100; + + box.dialog({ + title: "Select ISOs for CDROMs", + resizable: false, + width: 600, + height: 300, + modal: true, + show: {effect:'fade', duration:250}, + hide: {effect:'fade', duration:250}, + buttons: { + "_(Insert)_": function(){ + var target = box.find('#target'); + if (target.length) { + target = target.val(); + + } else target = ''; + var target2 = box.find('#target2'); + if (target2.length) { + target2 = target2.val(); + + } else target2 = ''; + box.find('#target').prop('disabled',true); + box.find('#target2').prop('disabled',true); + ajaxVMDispatch({action:"change-media-both", uuid:uuid , cdrom:"" , dev:dev , bus:bus , file:target, dev2:dev2 , bus2:bus2 , file2:target2}, "loadlist"); + box.dialog('close'); + }, + "_(Cancel)_": function(){ + box.dialog('close'); + } + } + }); + dialogStyle(); } function getisoimage(uuid,dev,bus,file){ var root = ; @@ -227,6 +276,7 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ } document.getElementById("targetsnaprmv").checked = true ; + document.getElementById("targetsnapfspc").checked = true ; box.dialog({ title: "_("+optiontext+ ")_", @@ -244,12 +294,18 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ target = target.val(); if (!target ) {errorTarget(); return;} } else target = ''; + var remove = 'yes' + var free = 'yes' box.find('#targetsnap').prop('disabled',true); if (opt == "revert") { - const x = document.querySelector('#targetsnaprmv') ; - if (x.checked) remove = 'yes' ; else remove = 'no' ; + const x = box.find('#targetsnaprmv').prop('checked') ; + if (x) remove = 'yes' ; else remove = 'no' ; } - ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:"yes" } , "loadlist"); + if (opt == "create") { + const x = box.find('#targetsnapfspc').prop('checked') ; + if (x) free = 'yes' ; else free = 'no' ; + } + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free } , "loadlist"); box.dialog('close'); }, "_(Cancel)_": function(){ @@ -365,6 +421,12 @@ $(function() { _(ISO Image)_: : +
+_(CD1 ISO Image)_: +:
+_(CD2 ISO Image)_: +: +
@@ -373,6 +435,8 @@ _(VM Name)_:
_(Snapshot Name)_: + _(Check free space)_: +
@@ -383,7 +447,7 @@ _(Snapshot Name)_:
_(Remove Images)_: -
+
diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 6c7a05d90..fe1b839bf 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -263,6 +263,40 @@ function embed(&$syslinux, $key, $value) { : ['error' => "Change Media Failed"]; break; +case 'change-media-both': + requireLibvirt(); + $res = $lv->get_domain_by_name($domName); + $cdroms = $lv->get_cdrom_stats($res) ; + $hda = $hdb = false ; + foreach ($lv->get_cdrom_stats($res) as $cd){ + if ($cd['device'] == 'hda') $hda = true ; + if ($cd['device'] == 'hdb') $hdb = true ; + } + $file= $_REQUEST['file']; + if ($file != "" && $hda == false) { + $cmdstr = "virsh attach-disk '$domName' '$file' hda --type cdrom --targetbus sata --config" ; + } else { + if ($file == "") $cmdstr = "virsh change-media '$domName' hda --eject --current"; + else $cmdstr = "virsh change-media '$domName' hda '$file'"; + } + $rtn=shell_exec($cmdstr) + ? ['success' => true] + : ['error' => "Change Media Failed"]; + + if (isset($rtn['error'])) return ; + + $file2 = $_REQUEST['file2']; + if ($file2 != "" && $hdb == false) { + $cmdstr = "virsh attach-disk '$domName' '$file2' hdb --type cdrom --targetbus sata --config" ; + } else { + if ($file2 == "") $cmdstr = "virsh change-media '$domName' hdb --eject --current"; + else $cmdstr = "virsh change-media '$domName' hdb '$file2' "; + } + $rtn=shell_exec($cmdstr) + ? ['success' => true] + : ['error' => "Change Media Failed"]; + break; + case 'memory-change': requireLibvirt(); $arrResponse = $lv->domain_set_memory($domName, $_REQUEST['memory']*1024) @@ -302,7 +336,7 @@ function embed(&$syslinux, $key, $value) { case 'snap-create-external': requireLibvirt(); - $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname']) ; + $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['free']) ; break; case 'snap-images': diff --git a/plugins/dynamix.vm.manager/include/libvirt.php b/plugins/dynamix.vm.manager/include/libvirt.php index 66a9c8e7f..bbf5bbac4 100644 --- a/plugins/dynamix.vm.manager/include/libvirt.php +++ b/plugins/dynamix.vm.manager/include/libvirt.php @@ -1846,7 +1846,7 @@ function nvram_restore($uuid) { return false; } - function nvram_snapshot($uuid,$snapshotname) { + function nvram_create_snapshot($uuid,$snapshotname) { // snapshot backup OVMF VARS if this domain had them if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) { copy('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd'); @@ -1859,6 +1859,21 @@ function nvram_snapshot($uuid,$snapshotname) { return false; } + function nvram_revert_snapshot($uuid,$snapshotname) { + // snapshot backup OVMF VARS if this domain had them + if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) { + copy('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd'); + unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd') ; + return true; + } + if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) { + copy('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd', '/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd'); + unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd') ; + return true; + } + return false; + } + function is_dir_empty($dir) { if (!is_readable($dir)) return NULL; $handle = opendir($dir); diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 3a8c24a68..d7a14e3fe 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1482,14 +1482,14 @@ function getvmsnapshots($vm) { $snaps[$vmsnap]["state"]= $b["state"]; $snaps[$vmsnap]["memory"]= $b["memory"]; $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; - $snaps[$vmsnap]["disks"]= $b["disks"]; + if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"]; } if (is_array($snaps)) uasort($snaps,'compare_creationtime') ; return $snaps ; } - function vm_snapshot($vm,$snapshotname,$memorysnap = "yes") { + function vm_snapshot($vm,$snapshotname,$free = "yes", $memorysnap = "yes") { global $lv ; #Get State @@ -1502,13 +1502,13 @@ function vm_snapshot($vm,$snapshotname,$memorysnap = "yes") { $diskspec = "" ; $capacity = 0 ; if ($snapshotname == "--generate") $name= "S" . date("YmdHis") ; else $name=$snapshotname ; - $cmdstr = "virsh snapshot-create-as $vm --name $name --atomic " ; + $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' --atomic " ; foreach($disks as $disk) { $file = $disk["file"] ; $pathinfo = pathinfo($file) ; $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; - $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; + $diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ; $capacity = $capacity + $disk["capacity"] ; } @@ -1516,8 +1516,8 @@ function vm_snapshot($vm,$snapshotname,$memorysnap = "yes") { $mem = $lv->domain_get_memory_stats($vm) ; $memory = $mem[6] ; - if ($memorysnap = "yes") $memspec = " --memspec ".$pathinfo["dirname"].'/memory'.$name.".mem,snapshot=external" ; else $memspec = "" ; - $cmdstr = "virsh snapshot-create-as $vm --name $name --atomic" ; + if ($memorysnap = "yes") $memspec = " --memspec ".$pathinfo["dirname"].'/memory"'.$name.'".mem,snapshot=external' ; else $memspec = "" ; + $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' --atomic" ; if ($state == "running") { @@ -1533,10 +1533,10 @@ function vm_snapshot($vm,$snapshotname,$memorysnap = "yes") { $capacity *= 1 ; - #if ($dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse ;} + if ($free == "yes" && $dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse ;} #Copy nvram - if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_snapshot($lv->domain_get_uuid($vm),$name) ; + if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ; $test = false ; if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ; @@ -1563,7 +1563,7 @@ function vm_revert($vm, $snap="--current",$action="no") { foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; - exec("qemu-img info --backing-chain -U $file | grep image:",$output) ; + exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; foreach($output as $key => $line) { $line=str_replace("image: ","",$line) ; $output[$key] = $line ; @@ -1581,7 +1581,6 @@ function vm_revert($vm, $snap="--current",$action="no") { switch ($snapslist[$snap]['state']) { case "shutoff": - case "running": #VM must be shutdown. $res = $lv->get_domain_by_name($vm); $dom = $lv->domain_get_info($res); @@ -1594,7 +1593,7 @@ function vm_revert($vm, $snap="--current",$action="no") { $xmlobj = custom::createArray('domain',$strXML) ; # Process disks and update path. - $disks=($snapslist[$snap]['disks']["disk"]) ; + $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; @@ -1602,10 +1601,9 @@ function vm_revert($vm, $snap="--current",$action="no") { $item = array_search($path,$snaps[$vm][$diskname]) ; $newpath = $snaps[$vm][$diskname][$item + 1]; $json_info = getDiskImageInfo($newpath) ; - #echo "Newpath: $newpath image type ".$json_info["format"]."\n" ; foreach($xmlobj['devices']['disk'] as $ddk => $dd){ if ($dd['target']["@attributes"]['dev'] == $diskname) { - $xmlobj['devices']['disk'][$ddk]['source']["@attributes"]['file'] = $newpath ; + $xmlobj['devices']['disk'][$ddk]['source']["@attributes"]['file'] = "$newpath" ; $xmlobj['devices']['disk'][$ddk]['driver']["@attributes"]['type'] = $json_info["format"] ; } } @@ -1615,27 +1613,21 @@ function vm_revert($vm, $snap="--current",$action="no") { if ($new) $arrResponse = ['success' => true] ; else $arrResponse = ['error' => $lv->get_last_error()] ; - - #echo "update xml \n" ; - - + # remove snapshot meta data and images for all snpahots. foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; - #echo "rm $path\n" ; - if (is_file($path) && $action == "yes") unlink($path) ; + if (is_file($path) && $action == "yes") unlink("$path") ; $item = array_search($path,$snaps[$vm]["r".$diskname]) ; $item++ ; while($item > 0) { if (!isset($snaps[$vm]["r".$diskname][$item])) break ; $newpath = $snaps[$vm]["r".$diskname][$item] ; - # rm $newpath - if (is_file($path) && $action == "yes") unlink($newpath) ; - #echo "rm $newpath\n" ; + if (is_file($path) && $action == "yes") unlink("$newpath") ; $item++ ; @@ -1644,23 +1636,20 @@ function vm_revert($vm, $snap="--current",$action="no") { uasort($snapslist,'compare_creationtimelt') ; foreach($snapslist as $s) { - #var_dump($s['name']) ; $name = $s['name'] ; - #echo "delete snapshot --metadata ".$s["name"]."\n" ; #Delete Metadata only. if ($action == "yes") { - $ret = $lv->domain_snapshot_delete($vm, $name ,2) ; - #echo "Error: $ret\n" ; - } #else echo "Run domain_snapshot_delete($vm, $name ,2\n" ; + $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ; + } if ($s['name'] == $snap) break ; } #if VM was started restart. if ($state == 'running') { #echo "Restart VM\n" ; $arrResponse = $lv->domain_start($vm) ; - } #else echo "VM Shutdown\n" ; - + } + if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; break ; @@ -1708,8 +1697,7 @@ function vm_snapimages($vm, $snap, $only) { $line=str_replace("image: ","",$line) ; $output[$key] = $line ; } - - $snaps[$vm][$disk["device"]] = $output ; + $snaps[$vm][$disk["device"]] = $output ; $rev = "r".$disk["device"] ; $reversed = array_reverse($output) ; $snaps[$vm][$rev] = $reversed ; @@ -1718,13 +1706,12 @@ function vm_snapimages($vm, $snap, $only) { $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; $capacity = $capacity + $disk["capacity"] ; } - #var_dump($snaps) ; - $disks=($snapslist[$snap]['disks']["disk"]) ; - foreach ($disks as $disk) { - $diskname = $disk["@attributes"]["name"] ; + + $snapdisks= $snapslist[$snap]['disks'] ; + foreach ($snapdisks as $diskkey => $snapdisk) { + $diskname = $snapdisk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; - $path = $disk["source"]["@attributes"]["file"] ; - #echo "rm $path\n" ; + $path = $snapdisk["source"]["@attributes"]["file"] ; if (is_file($path)) $data .= "$path
" ; $item = array_search($path,$snaps[$vm]["r".$diskname]) ; $item++ ; @@ -1733,10 +1720,7 @@ function vm_snapimages($vm, $snap, $only) { { if (!isset($snaps[$vm]["r".$diskname][$item])) break ; $newpath = $snaps[$vm]["r".$diskname][$item] ; - # rm $newpath - if (is_file($path)) $data .= "$path
" ; - #echo "rm $newpath\n" ; - + if (is_file($path)) $data .= "$newpath
" ; $item++ ; } @@ -1787,26 +1771,24 @@ function vm_snapremove($vm, $snap) { $xmlobj = custom::createArray('domain',$strXML) ; # Process disks and update path. - $disks=($snapslist[$snap]['disks']["disk"]) ; + $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; $item = array_search($path,$snaps[$vm][$diskname]) ; if (!$item) { - #if currently attached to VM error. $data = ["error" => "Image currently active for this domain."] ; return ($data) ; - } - #echo "Newpath: $newpath image type ".$json_info["format"]."\n" ; + } } - $disks=($snapslist[$snap]['disks']["disk"]) ; + $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; - #if (is_file($path)) unlink($path) ; + #if (is_file($path)) unlink("$path") ; } #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; From 3907be975848077f722cc136db94dddcd2eb47b3 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 11 May 2023 17:25:53 +0100 Subject: [PATCH 06/18] Add CD view to main VM detail --- .../dynamix.vm.manager/include/VMMachines.php | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index a1df722ee..a19303379 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -52,6 +52,7 @@ $image = substr($icon,-4)=='.png' ? "" : (substr($icon,0,5)=='icon-' ? "" : ""); $arrConfig = domain_to_config($uuid); $snapshots = getvmsnapshots($vm) ; + $cdroms = $lv->get_cdrom_stats($res) ; if ($state == 'running') { $mem = $dom['memory']/1024; } else { @@ -124,13 +125,33 @@ } /* VM information */ + if ($snapshots != null) $snapshotstr = _("(Snapshots :").count($snapshots).')' ; else $snapshotstr = _("(Snapshots :None)") ; + $cdbus = $cdbus2 = $cdfile = $cdfile2 = "" ; + $cdromcount = 0 ; + foreach ($cdroms as $arrCD) { + $disk = $arrCD['file'] ?? $arrCD['partition']; + $dev = $arrCD['device']; + $bus = $arrValidDiskBuses[$arrCD['bus']] ?? 'VirtIO'; + if ($dev == "hda") { + $cdbus = $arrValidDiskBuses[$arrCD['bus']] ?? 'VirtIO'; + $cdfile = $arrCD['file'] ?? $arrCD['partition']; + if ($cdfile != "") $cdromcount++ ; + } + if ($dev == "hdb") { + $cdbus2 = $arrValidDiskBuses[$arrCD['bus']] ?? 'VirtIO'; + $cdfile2 = $arrCD['file'] ?? $arrCD['partition']; + if ($cdfile2 != "") $cdromcount++ ; + } + } + $changemedia = "getisoimageboth(\"{$uuid}\",\"hda\",\"{$cdbus}\",\"{$cdfile}\",\"hdb\",\"{$cdbus2}\",\"{$cdfile2}\")"; + $cdstr = $cdromcount." / 2 "; echo ""; echo "$image$vm
"._($status)." $snapshotstcount
"; echo "$desc"; echo "$vcpu"; echo "$mem"; - echo "$disks
$snapshotstr
"; + echo "$disks    $cdstr
$snapshotstr
"; echo "$graphics"; echo ""; @@ -170,7 +191,7 @@ } /* Display VM cdroms */ - foreach ($lv->get_cdrom_stats($res) as $arrCD) { + foreach ($cdroms as $arrCD) { $capacity = $lv->format_size($arrCD['capacity'], 0); $allocation = $lv->format_size($arrCD['allocation'], 0); $disk = $arrCD['file'] ?? $arrCD['partition']; @@ -226,13 +247,15 @@ echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; echo ""; + $tab = "    " ; foreach($snapshots as $snapshotname => $snapshot) { $snapshotstate = _(ucfirst($snapshot["state"])) ; $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "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')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"]); - echo "         ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; + echo "$tab|__   ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; + $tab .="    " ; } echo ""; } From 8011ce4533d1e0e4842f5268295a6c076411cc07 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 11 May 2023 18:57:43 +0100 Subject: [PATCH 07/18] Add snpshot option to Dashboard --- plugins/dynamix.vm.manager/VMMachines.page | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index b90156384..fa0de4111 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -121,6 +121,7 @@ div.four label:nth-child(4n+4){float:none;clear:both} div.four label.cpu1{width:32%} div.four label.cpu2{width:26%} div.template,div#dialogWindow,input#upload{display:none} +div.template,div#dialogWindow2,input#upload{display:none} table.domdisk thead tr th:nth-child(1){width:56%!important} table.domdisk thead tr th:nth-child(n+2){width:8%!important} table.domdisk thead tr th:nth-child(1){padding-left:72px} @@ -200,7 +201,6 @@ function getisoimageboth(uuid,dev,bus,file,dev2,bus2,file2){ var target2 = box.find('#target2'); if (target2.length) { target2 = target2.val(); - } else target2 = ''; box.find('#target').prop('disabled',true); box.find('#target2').prop('disabled',true); @@ -301,7 +301,7 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ const x = box.find('#targetsnaprmv').prop('checked') ; if (x) remove = 'yes' ; else remove = 'no' ; } - if (opt == "create") { + if (opt == "create") { const x = box.find('#targetsnapfspc').prop('checked') ; if (x) free = 'yes' ; else free = 'no' ; } From 4c835ba2faf9c431f81bf991d9b96b05a64713a9 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Thu, 11 May 2023 18:58:04 +0100 Subject: [PATCH 08/18] Add snapshot option to Dashboard --- plugins/dynamix/DashStats.page | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/plugins/dynamix/DashStats.page b/plugins/dynamix/DashStats.page index 05274201c..85d5167e3 100644 --- a/plugins/dynamix/DashStats.page +++ b/plugins/dynamix/DashStats.page @@ -181,6 +181,7 @@ div.last{padding-bottom:12px} div.leftside{float:left;width:66%} div.rightside{float:right;margin:0;text-align:center} div[id$=chart]{margin:-12px 8px -24px -18px} +div.template,div#dialogWindow,input#upload{display:none} span.ctrl{float:right;margin-top:0;margin-right:10px} span.ctrl span{font-size:2rem!important} span.outer{float:left} @@ -723,6 +724,69 @@ var netchart = new ApexCharts(document.querySelector('#netchart'), options_net); if ($.cookie('port_select')!=null && !ports.includes($.cookie('port_select'))) $.removeCookie('port_select'); var port_select = $.cookie('port_select')||ports[0]; +function selectsnapshot(uuid, name ,snaps, opt, getlist){ + + var root = ; + var match= ".iso"; + var box = $("#dialogWindow2"); + box.html($("#templatesnapshot"+opt).html()); + var height = 200; + const Capopt = opt.charAt(0).toUpperCase() + opt.slice(1) ; + var optiontext = Capopt + " Snapshot" ; + box.find('#VMName').html(name) ; + box.find('#targetsnap').val(snaps) ; + box.find('#targetsnapl').html(snaps) ; + if (getlist) { + var only = 1 ; + if (opt == "remove") only = 0; + $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"snap-images", uuid:uuid , snapshotname:snaps, only:only}, function(data) { + if (data.html) { + box.find('#targetsnapimages').html(data.html) ; + } + },'json'); + } + + + document.getElementById("targetsnapfspc").checked = true ; + + box.dialog({ + title: "_("+optiontext+ ")_", + resizable: false, + width: 600, + height: 500, + modal: true, + show: {effect:'fade', duration:250}, + hide: {effect:'fade', duration:250}, + + buttons: { + "_(Proceed)_": function(){ + var target = box.find('#targetsnap'); + if (target.length) { + target = target.val(); + if (!target ) {errorTarget(); return;} + } else target = ''; + var remove = 'yes' + var free = 'yes' + box.find('#targetsnap').prop('disabled',true); + if (opt == "revert") { + const x = box.find('#targetsnaprmv').prop('checked') ; + if (x) remove = 'yes' ; else remove = 'no' ; + } + if (opt == "create") { + const x = box.find('#targetsnapfspc').prop('checked') ; + if (x) free = 'yes' ; else free = 'no' ; + } + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free } , "loadlist"); + box.dialog('close'); + }, + "_(Cancel)_": function(){ + box.dialog('close'); + } + } + }); + dialogStyle(); +} + function initCharts(clear) { $.post('/webGui/include/InitCharts.php',{cmd:'get'},function(data) { data = JSON.parse(data); @@ -1341,4 +1405,22 @@ $(function() { $.post('/webGui/include/InitCharts.php',{cmd:'set',data:JSON.stringify(data)}); }); }); + function dialogStyle() { + $('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('').prop('title'); + $('.ui-dialog-title').css({'text-align':'center','width':'100%','font-size':'1.8rem'}); + $('.ui-dialog-content').css({'padding-top':'15px','vertical-align':'bottom'}); + $('.ui-button-text').css({'padding':'0px 5px'}); +} + + +
+
+_(VM Name)_: + +
+_(Snapshot Name)_: + + _(Check free space)_: +
+
From 0ad1be3c23996abaf510b9e9a72c5296a827a63d Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sat, 13 May 2023 12:46:35 +0100 Subject: [PATCH 09/18] Updates to snapshots --- plugins/dynamix.vm.manager/VMMachines.page | 45 ++++-- .../dynamix.vm.manager/include/VMMachines.php | 3 +- plugins/dynamix.vm.manager/include/VMajax.php | 4 +- .../include/libvirt_helpers.php | 135 +++++++++++++++--- .../javascript/vmmanager.js | 2 +- plugins/dynamix/DashStats.page | 16 ++- 6 files changed, 167 insertions(+), 38 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index fa0de4111..5438d131a 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -135,7 +135,7 @@ i.mover{margin-right:8px;display:none} .dropdown-menu{z-index:10001} - +
_(Name)__(Description)__(CPUs)__(Memory)__(vDisks/vCDs)__(Graphics)__(Autostart)_
_(Name)__(Description)__(CPUs)__(Memory)__(vDisks / vCDs)__(Graphics)__(Autostart)_
@@ -192,7 +192,7 @@ function getisoimageboth(uuid,dev,bus,file,dev2,bus2,file2){ show: {effect:'fade', duration:250}, hide: {effect:'fade', duration:250}, buttons: { - "_(Insert)_": function(){ + "_(Update)_": function(){ var target = box.find('#target'); if (target.length) { target = target.val(); @@ -253,7 +253,7 @@ function getisoimage(uuid,dev,bus,file){ dialogStyle(); } -function selectsnapshot(uuid, name ,snaps, opt, getlist){ +function selectsnapshot(uuid, name ,snaps, opt, getlist,state){ var root = ; var match= ".iso"; @@ -276,6 +276,8 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ } document.getElementById("targetsnaprmv").checked = true ; + document.getElementById("targetsnaprmvmeta").checked = true ; + document.getElementById("targetsnapkeep").checked = true ; document.getElementById("targetsnapfspc").checked = true ; box.dialog({ @@ -295,17 +297,25 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ if (!target ) {errorTarget(); return;} } else target = ''; var remove = 'yes' + var keep = 'yes' + var removemeta = 'yes' var free = 'yes' + var desc = '' box.find('#targetsnap').prop('disabled',true); if (opt == "revert") { - const x = box.find('#targetsnaprmv').prop('checked') ; + var x = box.find('#targetsnaprmv').prop('checked') ; if (x) remove = 'yes' ; else remove = 'no' ; + x = box.find('#targetsnaprmvmeta').prop('checked') ; + if (x) removemeta = 'yes' ; else removemeta = 'no' ; + x = box.find('#targetsnapkeep').prop('checked') ; + if (x) keep = 'yes' ; else keep = 'no' ; } if (opt == "create") { - const x = box.find('#targetsnapfspc').prop('checked') ; + var x = box.find('#targetsnapfspc').prop('checked') ; if (x) free = 'yes' ; else free = 'no' ; + var desc = box.find("#targetsnapdesc").prop('value') ; } - ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free } , "loadlist"); + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} , "loadlist"); box.dialog('close'); }, "_(Cancel)_": function(){ @@ -430,16 +440,21 @@ _(CD2 ISO Image)_:
-_(VM Name)_: - -
-_(Snapshot Name)_: + +

+ + + + +
_(VM Name)_: +
_(Snapshot Name)_: _(Check free space)_: -
+
_(Description )_: +
-
+
_(VM Name)_:
@@ -447,7 +462,11 @@ _(Snapshot Name)_:
_(Remove Images)_: - + +_(Remove Meta)_: + + +

diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index a19303379..d269f27a8 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -250,11 +250,12 @@ $tab = "    " ; foreach($snapshots as $snapshotname => $snapshot) { $snapshotstate = _(ucfirst($snapshot["state"])) ; + $snapshotdesc = $snapshot["desc"] ; $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "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')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"]); - echo "$tab|__   ".$snapshot["name"]."$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; + echo "$tab|__   ".$snapshot["name"]."$snapshotdesc$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; $tab .="    " ; } echo ""; diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index fe1b839bf..7f3906913 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -336,7 +336,7 @@ function embed(&$syslinux, $key, $value) { case 'snap-create-external': requireLibvirt(); - $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['free']) ; + $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free']) ; break; case 'snap-images': @@ -347,7 +347,7 @@ function embed(&$syslinux, $key, $value) { case 'snap-revert-external': requireLibvirt(); - $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove']) ; + $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove'], $_REQUEST['removemeta']) ; break; case 'snap-remove-external': diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index d7a14e3fe..302b66e53 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1480,6 +1480,7 @@ function getvmsnapshots($vm) { $snaps[$vmsnap]["name"]= $b["name"]; if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; $snaps[$vmsnap]["state"]= $b["state"]; + $snaps[$vmsnap]["desc"]= $b["description"]; $snaps[$vmsnap]["memory"]= $b["memory"]; $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"]; @@ -1489,7 +1490,7 @@ function getvmsnapshots($vm) { return $snaps ; } - function vm_snapshot($vm,$snapshotname,$free = "yes", $memorysnap = "yes") { + function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $memorysnap = "yes") { global $lv ; #Get State @@ -1502,8 +1503,8 @@ function vm_snapshot($vm,$snapshotname,$free = "yes", $memorysnap = "yes") { $diskspec = "" ; $capacity = 0 ; if ($snapshotname == "--generate") $name= "S" . date("YmdHis") ; else $name=$snapshotname ; - $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' --atomic " ; - + if ($snapshotdesc != "") $snapshotdesc = " --description '$snapshotdesc'" ; + foreach($disks as $disk) { $file = $disk["file"] ; $pathinfo = pathinfo($file) ; @@ -1517,7 +1518,7 @@ function vm_snapshot($vm,$snapshotname,$free = "yes", $memorysnap = "yes") { $memory = $mem[6] ; if ($memorysnap = "yes") $memspec = " --memspec ".$pathinfo["dirname"].'/memory"'.$name.'".mem,snapshot=external' ; else $memspec = "" ; - $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' --atomic" ; + $cmdstr = "virsh snapshot-create-as '$vm' --name '$name' $snapshotdesc --atomic" ; if ($state == "running") { @@ -1552,12 +1553,9 @@ function vm_snapshot($vm,$snapshotname,$free = "yes", $memorysnap = "yes") { } - function vm_revert($vm, $snap="--current",$action="no") { + function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { global $lv ; $snapslist= getvmsnapshots($vm) ; - - #echo "Revert Vm: $vm snap: $snap\n" ; - $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { @@ -1627,7 +1625,7 @@ function vm_revert($vm, $snap="--current",$action="no") { { if (!isset($snaps[$vm]["r".$diskname][$item])) break ; $newpath = $snaps[$vm]["r".$diskname][$item] ; - if (is_file($path) && $action == "yes") unlink("$newpath") ; + if (is_file($newpath) && $action == "yes") unlink("$newpath") ; $item++ ; @@ -1638,14 +1636,13 @@ function vm_revert($vm, $snap="--current",$action="no") { foreach($snapslist as $s) { $name = $s['name'] ; #Delete Metadata only. - if ($action == "yes") { + if ($actionmeta == "yes") { $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ; } if ($s['name'] == $snap) break ; } #if VM was started restart. if ($state == 'running') { - #echo "Restart VM\n" ; $arrResponse = $lv->domain_start($vm) ; } @@ -1686,8 +1683,6 @@ function vm_snapimages($vm, $snap, $only) { $snapslist= getvmsnapshots($vm) ; $data = "

Images and metadata to remove if tickbox checked.
" ; - #echo "Revert Vm: $vm snap: $snap\n" ; - $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; @@ -1761,16 +1756,13 @@ function vm_snapremove($vm, $snap) { $reversed = array_reverse($output) ; $snaps[$vm][$rev] = $reversed ; $pathinfo = pathinfo($file) ; - $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; - $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; - $capacity = $capacity + $disk["capacity"] ; } # GetXML $strXML= $lv->domain_get_xml($res) ; $xmlobj = custom::createArray('domain',$strXML) ; - # Process disks and update path. + # Process disks. $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; @@ -1788,12 +1780,115 @@ function vm_snapremove($vm, $snap) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; - #if (is_file($path)) unlink("$path") ; + if (is_file($path)) { + if(!unlink("$path")) { + $data = ["error" => "Unable to remove image file $path"] ; + return ($data) ; + } + } } - #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; - $data = ["success => 'true"] ; + $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + if($ret) + $data = ["error" => "Unable to remove snap metadata $snap"] ; + else + $data = ["success => 'true"] ; + return($data) ; } + +function vm_blockcommit($vm,$path,$base,$top,$pivot,$action) { + /* + NAME + blockcommit - Start a block commit operation. + + SYNOPSIS + blockcommit [--bandwidth ] [--base ] [--shallow] [--top ] [--active] [--delete] [--wait] [--verbose] [--timeout ] [--pivot] [--keep-overlay] [--async] [--keep-relative] [--bytes] + + DESCRIPTION + Commit changes from a snapshot down to its backing image. + + OPTIONS + [--domain] domain name, id or uuid + [--path] fully-qualified path of disk + --bandwidth bandwidth limit in MiB/s + --base path of base file to commit into (default bottom of chain) + --shallow use backing file of top as base + --top path of top file to commit from (default top of chain) + --active trigger two-stage active commit of top file + --delete delete files that were successfully committed + --wait wait for job to complete (with --active, wait for job to sync) + --verbose with --wait, display the progress + --timeout implies --wait, abort if copy exceeds timeout (in seconds) + --pivot implies --active --wait, pivot when commit is synced + --keep-overlay implies --active --wait, quit when commit is synced + --async with --wait, don't wait for cancel to finish + --keep-relative keep the backing chain relatively referenced + --bytes the bandwidth limit is in bytes/s rather than MiB/s + */ +} + +function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) { + /* + NAME + blockcopy - Start a block copy operation. + + SYNOPSIS + blockcopy [--dest ] [--bandwidth ] [--shallow] [--reuse-external] [--blockdev] [--wait] [--verbose] [--timeout ] [--pivot] [--finish] [--async] [--xml ] [--format ] [--granularity ] [--buf-size ] [--bytes] [--transient-job] [--synchronous-writes] [--print-xml] + + DESCRIPTION + Copy a disk backing image chain to dest. + + OPTIONS + [--domain] domain name, id or uuid + [--path] fully-qualified path of source disk + --dest path of the copy to create + --bandwidth bandwidth limit in MiB/s + --shallow make the copy share a backing chain + --reuse-external reuse existing destination + --blockdev copy destination is block device instead of regular file + --wait wait for job to reach mirroring phase + --verbose with --wait, display the progress + --timeout implies --wait, abort if copy exceeds timeout (in seconds) + --pivot implies --wait, pivot when mirroring starts + --finish implies --wait, quit when mirroring starts + --async with --wait, don't wait for cancel to finish + --xml filename containing XML description of the copy destination + --format format of the destination file + --granularity power-of-two granularity to use during the copy + --buf-size maximum amount of in-flight data during the copy + --bytes the bandwidth limit is in bytes/s rather than MiB/s + --transient-job the copy job is not persisted if VM is turned off + --synchronous-writes the copy job forces guest writes to be synchronously written to the destination + --print-xml print the XML used to start the copy job instead of starting the job + */ +} + +function vm_blockpull($vm,$path,$base,$top,$pivot,$action) { + /* + NAME + blockpull - Populate a disk from its backing image. + + SYNOPSIS + blockpull [--bandwidth ] [--base ] [--wait] [--verbose] [--timeout ] [--async] [--keep-relative] [--bytes] + + DESCRIPTION + Populate a disk from its backing image. + + OPTIONS + [--domain] domain name, id or uuid + [--path] fully-qualified path of disk + --bandwidth bandwidth limit in MiB/s + --base path of backing file in chain for a partial pull + --wait wait for job to finish + --verbose with --wait, display the progress + --timeout with --wait, abort if pull exceeds timeout (in seconds) + --async with --wait, don't wait for cancel to finish + --keep-relative keep the backing chain relatively referenced + --bytes the bandwidth limit is in bytes/s rather than MiB/s + + + */ +} ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index d7ef4d57f..0654c87ac 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -151,7 +151,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) ; + selectsnapshot(uuid , name, "--generate" , "create",false,state) ; }}); opts.push({text:_("Remove VM"), icon:"fa-minus", action:function(e) { e.preventDefault(); diff --git a/plugins/dynamix/DashStats.page b/plugins/dynamix/DashStats.page index 85d5167e3..f556fe7a3 100644 --- a/plugins/dynamix/DashStats.page +++ b/plugins/dynamix/DashStats.page @@ -766,17 +766,25 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist){ if (!target ) {errorTarget(); return;} } else target = ''; var remove = 'yes' + var keep = 'yes' + var removemeta = 'yes' var free = 'yes' + var desc = '' box.find('#targetsnap').prop('disabled',true); if (opt == "revert") { const x = box.find('#targetsnaprmv').prop('checked') ; if (x) remove = 'yes' ; else remove = 'no' ; + x = box.find('#targetsnaprmvmeta').prop('checked') ; + if (x) removemeta = 'yes' ; else removemeta = 'no' ; + x = box.find('#targetsnapkeep').prop('checked') ; + if (x) keep = 'yes' ; else keep = 'no' ; } if (opt == "create") { const x = box.find('#targetsnapfspc').prop('checked') ; if (x) free = 'yes' ; else free = 'no' ; + var desc = box.find("#targetsnapdesc").prop('value') ; } - ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free } , "loadlist"); + ajaxVMDispatch({action:"snap-" + opt +'-external', uuid:uuid , snapshotname:target , remove:remove, free:free, desc:desc } , "loadlist"); box.dialog('close'); }, "_(Cancel)_": function(){ @@ -1416,6 +1424,7 @@ $(function() {
+ _(VM Name)_:
@@ -1423,4 +1432,9 @@ _(Snapshot Name)_: _(Check free space)_:
+_(Description )_: + +
+ + From 1911da7fc38e28c8310d273f33e1e48364b9fcee Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 21 May 2023 19:04:35 +0100 Subject: [PATCH 10/18] Add VMAjaxCall for Block commands --- .../dynamix.vm.manager/scripts/VMAjaxCall.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 plugins/dynamix.vm.manager/scripts/VMAjaxCall.php diff --git a/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php new file mode 100644 index 000000000..9e40bf79e --- /dev/null +++ b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php @@ -0,0 +1,76 @@ +#!/usr/bin/php -q + + 'http://localhost/pub/plugins?buffer_length=1', + CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket', + CURLOPT_POST => 1, + CURLOPT_RETURNTRANSFER => true + ]); + foreach ($messages as $message) { + curl_setopt($com, CURLOPT_POSTFIELDS, $message); + curl_exec($com); + } + curl_close($com); +} +function execCommand_nchan($command) { + $waitID = mt_rand(); + [$cmd,$args] = explode(' ',$command,2); + write("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","show_Wait\0$waitID"); + $proc = popen("$command 2>&1",'r'); + while ($out = fgets($proc)) { + $out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out); + write("addLog\0".htmlspecialchars($out)); + } + $retval = pclose($proc); + $out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!'; + write("stop_Wait\0$waitID","addLog\0
$out"); + return $retval===0; + } + +#{action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} +$url = rawurldecode($argv[1]??''); +$waitID = "bob" ; +$style = [""; +$path = "--current" ; $pivot = "yes" ; $action = " " ; +write(implode($style)."

"); +write("

","addLog\0
"._('Block Commit').": ".htmlspecialchars($path)."

"._('Please wait')."
","show_Wait\0$waitID"); + +foreach (explode('&', $url) as $chunk) { + $param = explode("=", $chunk); + if ($param) { +# write("addLog\0Parm" . sprintf("Value for parameter \"%s\" is \"%s\"
\n", urldecode($param[0]), urldecode($param[1]))); + ${urldecode($param[0])} = urldecode($param[1]) ; + } +} + +write("VMName $name "); +write("SNAP $snapshotname "); +write("Base $targetbase "); +write("Top $targettop "); +$path = "--current" ; $pivot = "yes" ; $action = " " ; +vm_blockcommit($name,$snapshotname,$path,$targetbase,$targettop,$pivot,$action) ; +#execCommand_nchan("ls /root") ; +write('_DONE_',''); + +?> \ No newline at end of file From 34c3de339e40af42d91715feedac2d83dd1caa2a Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 21 May 2023 19:24:13 +0100 Subject: [PATCH 11/18] Updates for Blockcommit --- plugins/dynamix.vm.manager/VMMachines.page | 108 ++++++++++++++++-- plugins/dynamix.vm.manager/include/VMajax.php | 19 +++ .../include/libvirt_helpers.php | 97 ++++++++++++++-- .../javascript/vmmanager.js | 19 ++- plugins/dynamix/DashStats.page | 23 ++-- 5 files changed, 231 insertions(+), 35 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 5438d131a..0290b97fc 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -18,6 +18,7 @@ Markdown="false" */ ?> ; + var match= ".iso"; + var box = $("#dialogWindow2"); + box.html($("#templateblock").html()); + var height = 200; + const Capopt = opt.charAt(0).toUpperCase() + opt.slice(1) ; + var optiontext = Capopt + " Block Devices" ; + box.find('#VMName').html(name) ; + box.find('#targetsnap').val(snaps) ; + box.find('#targetsnapl').html(snaps) ; + + getlist = true ; + if (getlist) { + var only = 1 ; + if (opt == "remove") only = 0; + $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"snap-list", uuid:uuid }, function(data) { + if (data.html) { + var targetbase = document.getElementById("targetblockbase") ; + htmlstrbase = "" + htmlstrtop = "" + $("select.targetblockbase").replaceWith(htmlstrbase) ; + $("select.targetblocktop").replaceWith(htmlstrtop) ; + } + },'json'); + } + + document.getElementById("targetsnaprmv").checked = true ; + document.getElementById("targetsnaprmvmeta").checked = true ; + document.getElementById("targetsnapkeep").checked = true ; + document.getElementById("targetsnapfspc").checked = true ; + + box.dialog({ + title: "_("+optiontext+ ")_", + resizable: false, + width: 600, + height: 500, + modal: true, + show: {effect:'fade', duration:250}, + hide: {effect:'fade', duration:250}, + buttons: { + _("Action")_: function(){ + var target = box.find('#targetsnap'); + if (target.length) { + target = target.val(); + if (!target ) {errorTarget(); return;} + } else target = ''; + var remove = 'yes' + var keep = 'yes' + var removemeta = 'yes' + var free = 'yes' + var desc = '' + box.find('#targetsnap').prop('disabled',true); + if (opt == "create") { + var x = box.find('#targetsnapfspc').prop('checked') ; + if (x) free = 'yes' ; else free = 'no' ; + var desc = box.find("#targetsnapdesc").prop('value') ; + } + var targetbase = $('#targetblockbase').val() + var targettop = $('#targetblocktop').val() + //ajaxVMDispatch({action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} , "loadlist"); + Ajaxurl = "VMAjaxCall.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMajax.php&" + $.param({action:"snap-commit", name:name ,targetbase:targetbase, targettop:targettop , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep})) ; + openPlugin((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ; + box.dialog('close'); + }, + "_(Cancel)_": function(){ + box.dialog('close'); + } + } + }); + dialogStyle(); +} + // ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); // ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); @@ -442,13 +517,12 @@ _(CD2 ISO Image)_:


- - + + +_(Check free space)_: +
_(VM Name)_: -
_(VM Name)_: +
_(Snapshot Name)_: - - _(Check free space)_: -
_(Description )_:
@@ -485,5 +559,25 @@ _(Snapshot Name)_:
+
+_(VM Name)_: + +
+_(Snapshot Name)_: + +
+



+ + +
_(Base Image)_: +
_(Top Image )_: + +
+ +
+
+
\ No newline at end of file diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 7f3906913..5c1fa042a 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -339,12 +339,31 @@ function embed(&$syslinux, $key, $value) { $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free']) ; break; +case 'snap-blockcommit': + requireLibvirt(); + echo "Hello" ; + #$arrResponse = vm_snapshot($domName,'',$_REQUEST['base'],$_REQUEST['top'],"","") ; + break; + case 'snap-images': requireLibvirt(); $html = vm_snapimages($domName,$_REQUEST['snapshotname'],$_REQUEST['only']) ; $arrResponse = ['html' => $html , 'success' => true] ; break; +case 'snap-list': + requireLibvirt(); + $arrResponse = ($data = getvmsnapshots($domName)) + ? ['success' => true] + : ['error' => $lv->get_last_error()]; + $datartn = ""; + foreach($data as $snap=>$snapdetail) { + $snapshotdatetime = date("Y-m-d H:i:s",$snapdetail["creationtime"]) ; + $datartn .= "" ; + } + $arrResponse['html'] = $datartn ; + break; + case 'snap-revert-external': requireLibvirt(); $arrResponse = vm_revert($domName,$_REQUEST['snapshotname'],$_REQUEST['remove'], $_REQUEST['removemeta']) ; diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 302b66e53..b19b97fe3 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1538,6 +1538,9 @@ function vm_snapshot($vm,$snapshotname, $snapshotdesc, $free = "yes", $memorysn #Copy nvram if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ; + + $xmlfile = $pathinfo["dirname"].'/"'.$name.'".running' ; + if ($state == "running") exec("virsh dumpxml '$vm' > $xmlfile",$outxml,$rtnxml) ; $test = false ; if ($test) exec($cmdstr." --print-xml 2>&1",$output,$return) ; else exec($cmdstr." 2>&1",$output,$return) ; @@ -1577,8 +1580,9 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { $capacity = $capacity + $disk["capacity"] ; } - switch ($snapslist[$snap]['state']) { - case "shutoff": + switch ($snapslist[$snap]['state']) { + case "shutoff": + case "running": #VM must be shutdown. $res = $lv->get_domain_by_name($vm); $dom = $lv->domain_get_info($res); @@ -1596,6 +1600,10 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; + if ($diskname == "hdc") { + $primarypathinfo = pathinfo($path) ; + $primarypath = $primarypathinfo['dirname'] ; + } $item = array_search($path,$snaps[$vm][$diskname]) ; $newpath = $snaps[$vm][$diskname][$item + 1]; $json_info = getDiskImageInfo($newpath) ; @@ -1638,7 +1646,13 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { #Delete Metadata only. if ($actionmeta == "yes") { $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ; - } + } + # Remove running XML and memory. + $xmlfile = $primarypath."/$name.running" ; + $memoryfile = $primarypath."/memory$name.mem" ; + #var_dump(is_file($xmlfile), is_file($memoryfile)) ; + if (is_file($memoryfile) && $action == "yes") unlink($memoryfile) ; + if (is_file($xmlfile) && $action == "yes") unlink($xmlfile) ; if ($s['name'] == $snap) break ; } #if VM was started restart. @@ -1687,7 +1701,7 @@ function vm_snapimages($vm, $snap, $only) { foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; - exec("qemu-img info --backing-chain -U $file | grep image:",$output) ; + exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; foreach($output as $key => $line) { $line=str_replace("image: ","",$line) ; $output[$key] = $line ; @@ -1703,6 +1717,7 @@ function vm_snapimages($vm, $snap, $only) { } $snapdisks= $snapslist[$snap]['disks'] ; + #var_dump($snaps) ; foreach ($snapdisks as $diskkey => $snapdisk) { $diskname = $snapdisk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; @@ -1763,7 +1778,8 @@ function vm_snapremove($vm, $snap) { $xmlobj = custom::createArray('domain',$strXML) ; # Process disks. - $disks=($snapslist[$snap]['disks']) ; + $disks=($snapslist[$snap]['disks']) ; + var_dump($disks,$snaps) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; @@ -1781,14 +1797,14 @@ function vm_snapremove($vm, $snap) { if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; if (is_file($path)) { - if(!unlink("$path")) { - $data = ["error" => "Unable to remove image file $path"] ; - return ($data) ; - } + #if(!unlink("$path")) { + # $data = ["error" => "Unable to remove image file $path"] ; + # return ($data) ; + #} } } - $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else @@ -1797,7 +1813,8 @@ function vm_snapremove($vm, $snap) { return($data) ; } -function vm_blockcommit($vm,$path,$base,$top,$pivot,$action) { +function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { + global $lv ; /* NAME blockcommit - Start a block commit operation. @@ -1825,7 +1842,65 @@ function vm_blockcommit($vm,$path,$base,$top,$pivot,$action) { --async with --wait, don't wait for cancel to finish --keep-relative keep the backing chain relatively referenced --bytes the bandwidth limit is in bytes/s rather than MiB/s + + blockcommit Debian --path /mnt/user/domains/Debian/vdisk1.S20230513120410qcow2 --verbose --pivot --delete */ + # Error if VM Not running. + + $snapslist= getvmsnapshots($vm) ; + $disks =$lv->get_disk_stats($vm) ; + #var_dump($disks) ; + foreach($disks as $disk) { + $path = $disk['file'] ; + $cmdstr = "virsh blockcommit '$vm' --path '$path' --verbose --pivot --delete" ; + # Process disks and update path. + $snapdisks=($snapslist[$snap]['disks']) ; + if ($base != "--base" && $base != "") { + #get file name from snapshot. + $snapdisks=($snapslist[$base]['disks']) ; + $basepath = "" ; + foreach ($snapdisks as $snapdisk) { + $diskname = $snapdisk["@attributes"]["name"] ; + if ($diskname != $disk['device']) continue ; + $basepath = $snapdisk["source"]["@attributes"]["file"] ; + } + if ($basepath != "") $cmdstr .= " --base '$basepath' "; + } + if ($top != "--top" && $top !="") { + #get file name from snapshot. + $snapdisks=($snapslist[$top]['disks']) ; + $toppath = "" ; + foreach ($snapdisks as $snapdisk) { + $diskname = $snapdisk["@attributes"]["name"] ; + if ($diskname != $disk['device']) continue ; + $toppath = $snapdisk["source"]["@attributes"]["file"] ; + } + if ($toppath != "") $cmdstr .= " --top '$toppath' "; + } + if ($action) $cmdstr .= " $action "; + + + $test = false ; + if ($test) { $cmdstr .= " --print-xml " ; execCommand_nchan($cmdstr) ; } else execCommand_nchan($cmdstr) ; + #var_dump($cmdstr,$output) ; + if (strpos(" ".$output[0],"error") ) { + $arrResponse = ['error' => substr($output[0],6) ] ; + #return($arrResponse) ; + } else { + # Remove nvram snapshot + $arrResponse = ['success' => true] ; + } + #Error Check + } + #If complete ok remove meta data for snapshots. + if (!$test) $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + if($ret) + $data = ["error" => "Unable to remove snap metadata $snap"] ; + else + $data = ["success => 'true"] ; + + return $data ; + } function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) { diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index 0654c87ac..940afe673 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -106,6 +106,11 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c e.preventDefault(); ajaxVMDispatch( {action:"domain-destroy", uuid:uuid}, "loadlist"); }}); + opts.push({divider:true}); + opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) { + e.preventDefault(); + selectsnapshot(uuid , name, "--generate" , "create",false,state) ; + }}); } else if (state == "pmsuspended") { opts.push({text:_("Resume"), icon:"fa-play", action:function(e) { e.preventDefault(); @@ -141,7 +146,8 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c ajaxVMDispatchconsoleRV({action:"domain-start-consoleRV", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ; }}); } - }} + } + } opts.push({divider:true}); if (log !== "") { opts.push({text:_("Logs"), icon:"fa-navicon", action:function(e){e.preventDefault(); openTerminal('log',name,log);}}); @@ -198,10 +204,11 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ // e.preventDefault(); // ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); // }}); -// opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); -// }}); + 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(); + selectblock(uuid, name, snapshotname, "commit",true) ; + }}); // opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { // e.preventDefault(); // ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); @@ -212,7 +219,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); selectsnapshot(uuid, name, snapshotname, "revert",true) ; }}); - opts.push({text:_("Remove snapshot(Delete Disabled)"), icon:"fa-trash", action:function(e) { + opts.push({text:_("Remove snapshot"), icon:"fa-trash", action:function(e) { e.preventDefault(); $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); selectsnapshot(uuid, name, snapshotname, "remove",true) ; diff --git a/plugins/dynamix/DashStats.page b/plugins/dynamix/DashStats.page index f556fe7a3..996ba3281 100644 --- a/plugins/dynamix/DashStats.page +++ b/plugins/dynamix/DashStats.page @@ -1124,8 +1124,8 @@ function sortTables() { }); } function addProperties() { - $('tbody.system').addClass('sortable').attr('sort','_system_information_'); - $('tbody').not('.system').each(function(){ + $('div.frame').find('tbody.system').addClass('sortable').attr('sort','_system_information_'); + $('div.frame').find('tbody').not('.system').each(function(){ $(this).addClass('sortable').attr('sort',$(this).attr('title').marker()); $(this).find('td:first').prepend(""); }); @@ -1425,16 +1425,17 @@ $(function() {
-_(VM Name)_: - -
-_(Snapshot Name)_: - - _(Check free space)_: -
-_(Description )_: - +

+ + +
_(VM Name)_: +
_(Snapshot Name)_: + +_(Check free space)_: +
_(Description )_: +
+ From c7b88354db031f1c896f6f633f7936098a337db6 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Wed, 24 May 2023 07:27:12 +0100 Subject: [PATCH 12/18] Make Script executable --- .../dynamix.vm.manager/scripts/VMAjaxCall.php | 77 ++++++++++++------- 1 file changed, 51 insertions(+), 26 deletions(-) mode change 100644 => 100755 plugins/dynamix.vm.manager/scripts/VMAjaxCall.php diff --git a/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php old mode 100644 new mode 100755 index 9e40bf79e..7fc64bf76 --- a/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php +++ b/plugins/dynamix.vm.manager/scripts/VMAjaxCall.php @@ -12,31 +12,39 @@ ?> 'http://localhost/pub/plugins?buffer_length=1', - CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket', - CURLOPT_POST => 1, - CURLOPT_RETURNTRANSFER => true - ]); - foreach ($messages as $message) { - curl_setopt($com, CURLOPT_POSTFIELDS, $message); - curl_exec($com); + $com = curl_init(); + curl_setopt_array($com,[ + CURLOPT_URL => 'http://localhost/pub/vmaction?buffer_length=1', + CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket', + CURLOPT_POST => 1, + CURLOPT_RETURNTRANSFER => true + ]); + foreach ($messages as $message) { + curl_setopt($com, CURLOPT_POSTFIELDS, $message); + curl_exec($com); + } + curl_close($com); } - curl_close($com); -} function execCommand_nchan($command) { $waitID = mt_rand(); [$cmd,$args] = explode(' ',$command,2); write("

","addLog\0
"._('Command execution')."".basename($cmd).' '.str_replace(" -","
  -",htmlspecialchars($args))."
"._('Please wait')."

","show_Wait\0$waitID"); + write("addToID\0 99\0 $action") ; $proc = popen("$command 2>&1",'r'); while ($out = fgets($proc)) { $out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out); - write("addLog\0".htmlspecialchars($out)); + write("addToID\0 99\0".htmlspecialchars($out)); + sleep(5) ; } $retval = pclose($proc); $out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!'; @@ -46,31 +54,48 @@ function execCommand_nchan($command) { #{action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} $url = rawurldecode($argv[1]??''); -$waitID = "bob" ; +$waitID = mt_rand(); $style = [""; -$path = "--current" ; $pivot = "yes" ; $action = " " ; -write(implode($style)."

"); -write("

","addLog\0
"._('Block Commit').": ".htmlspecialchars($path)."

"._('Please wait')."
","show_Wait\0$waitID"); - +$path = "--current" ; $pivot = "yes" ; foreach (explode('&', $url) as $chunk) { $param = explode("=", $chunk); if ($param) { -# write("addLog\0Parm" . sprintf("Value for parameter \"%s\" is \"%s\"
\n", urldecode($param[0]), urldecode($param[1]))); ${urldecode($param[0])} = urldecode($param[1]) ; } } +$id = 1 ; +write(implode($style)."

"); +write("addLog\0".htmlspecialchars("VMName $name ")); +write("addLog\0".htmlspecialchars("SNAP $snapshotname ")); +write("addLog\0".htmlspecialchars("Base $targetbase ")); +write("addLog\0".htmlspecialchars("Top $targettop ")); + sleep(3) ; + write("stop_Wait\0$waitID") ; +write("

","addLog\0
"._("Block $action").": ".htmlspecialchars($path)."

"._('Please wait')."
","show_Wait\0$waitID"); + + + sleep(3) ; + write("stop_Wait\0$waitID") ; +$path = "--current" ; $pivot = "yes" ; +write("addToID\0 99\0 $action") ; +switch ($action) { + case "commit": + # vm_blockcommit($name,$snapshotname,$path,$targetbase,$targettop,$pivot,' ') ; + break ; + case "copy": + # vm_blockcopy($name,$snapshotname,$path,$targetbase,$targettop,$pivot,' ') ; + break; + case "pull": + vm_blockpull($name,$snapshotname,$path,$targetbase,$targettop,$pivot,' ') ; + break ; -write("VMName $name "); -write("SNAP $snapshotname "); -write("Base $targetbase "); -write("Top $targettop "); -$path = "--current" ; $pivot = "yes" ; $action = " " ; -vm_blockcommit($name,$snapshotname,$path,$targetbase,$targettop,$pivot,$action) ; -#execCommand_nchan("ls /root") ; + } +#execCommand_nchan("ls /") ; +write("stop_Wait\0$waitID") ; write('_DONE_',''); ?> \ No newline at end of file From 0eec9f2ad72482dd478392654f1d6bf36da36c6f Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Wed, 24 May 2023 07:44:29 +0100 Subject: [PATCH 13/18] WIP Update --- plugins/dynamix.vm.manager/VMMachines.page | 4 +- .../include/libvirt_helpers.php | 60 +++++++++++++++- .../javascript/vmmanager.js | 5 ++ plugins/dynamix/include/DefaultPageLayout.php | 72 ++++++++++++++++++- 4 files changed, 137 insertions(+), 4 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 0290b97fc..9b06fe820 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -389,8 +389,8 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ var targetbase = $('#targetblockbase').val() var targettop = $('#targetblocktop').val() //ajaxVMDispatch({action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} , "loadlist"); - Ajaxurl = "VMAjaxCall.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMajax.php&" + $.param({action:"snap-commit", name:name ,targetbase:targetbase, targettop:targettop , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep})) ; - openPlugin((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ; + Ajaxurl = "VMAjaxCall.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMajax.php&" + $.param({action:opt , name:name ,targetbase:targetbase, targettop:targettop , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep})) ; + openVMAction((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ; box.dialog('close'); }, "_(Cancel)_": function(){ diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index b19b97fe3..d9f08e203 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1939,7 +1939,8 @@ function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) { */ } -function vm_blockpull($vm,$path,$base,$top,$pivot,$action) { +function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { + global $lv ; /* NAME blockpull - Populate a disk from its backing image. @@ -1964,6 +1965,63 @@ function vm_blockpull($vm,$path,$base,$top,$pivot,$action) { */ + $snapslist= getvmsnapshots($vm) ; + $disks =$lv->get_disk_stats($vm) ; + #var_dump($disks) ; + foreach($disks as $disk) { + $path = $disk['file'] ; + $cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --pivot --delete" ; + $cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --wait" ; + # Process disks and update path. + $snapdisks=($snapslist[$snap]['disks']) ; + if ($base != "--base" && $base != "") { + #get file name from snapshot. + $snapdisks=($snapslist[$base]['disks']) ; + $basepath = "" ; + foreach ($snapdisks as $snapdisk) { + $diskname = $snapdisk["@attributes"]["name"] ; + if ($diskname != $disk['device']) continue ; + $basepath = $snapdisk["source"]["@attributes"]["file"] ; + } + if ($basepath != "") $cmdstr .= " --base '$basepath' "; + } + if ($top != "--top" && $top !="") { + #get file name from snapshot. + $snapdisks=($snapslist[$top]['disks']) ; + $toppath = "" ; + foreach ($snapdisks as $snapdisk) { + $diskname = $snapdisk["@attributes"]["name"] ; + if ($diskname != $disk['device']) continue ; + $toppath = $snapdisk["source"]["@attributes"]["file"] ; + } + if ($toppath != "") $cmdstr .= " --top '$toppath' "; + } + if ($action) $cmdstr .= " $action "; + + + $test = false ; + if ($test) { $cmdstr .= " --print-xml " ; execCommand_nchan($cmdstr) ; } else execCommand_nchan($cmdstr) ; + #var_dump($cmdstr,$output) ; + if (strpos(" ".$output[0],"error") ) { + $arrResponse = ['error' => substr($output[0],6) ] ; + #return($arrResponse) ; + } else { + # Remove nvram snapshot + $arrResponse = ['success' => true] ; + } + #Error Check + } + #If complete ok remove meta data for snapshots. + #if (!$test) $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + if($ret) + $data = ["error" => "Unable to remove snap metadata $snap"] ; + else + $data = ["success => 'true"] ; + + return $data ; + } + + ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index 940afe673..133dcbf35 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -209,6 +209,11 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ e.preventDefault(); selectblock(uuid, name, snapshotname, "commit",true) ; }}); + opts.push({text:_("Block Pull"), 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(); + selectblock(uuid, name, snapshotname, "pull",true) ; + }}); // opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { // e.preventDefault(); // ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); diff --git a/plugins/dynamix/include/DefaultPageLayout.php b/plugins/dynamix/include/DefaultPageLayout.php index 9d236569c..80aedd800 100644 --- a/plugins/dynamix/include/DefaultPageLayout.php +++ b/plugins/dynamix/include/DefaultPageLayout.php @@ -342,6 +342,29 @@ function openDocker(cmd,title,plg,func,start=0,button=0) { $('button.confirm').prop('disabled',button==0); }); } +function openVMAction(cmd,title,plg,func,start=0,button=0) { + // start = 0 : run command only when not already running (default) + // start = 1 : run command unconditionally + // button = 0 : hide CLOSE button (default) + // button = 1 : show CLOSE button + nchan_vmaction.start(); + $.post('/webGui/include/StartCommand.php',{cmd:cmd,start:start},function(pid) { + if (pid==0) { + nchan_docker.stop(); + $('div.spinner.fixed').hide(); + $(".upgrade_notice").addClass('alert'); + return; + } + swal({title:title,text:"

",html:true,animation:'none',showConfirmButton:button!=0,confirmButtonText:""},function(close){ + nchan_vmaction.stop(); + $('div.spinner.fixed').hide(); + $('.sweet-alert').hide('fast').removeClass('nchan'); + setTimeout(function(){bannerAlert(" ["+pid.toString().padStart(8,'0')+"]\" onclick='abortOperation("+pid+")'>",cmd,plg,func,start);}); + }); + $('.sweet-alert').addClass('nchan'); + $('button.confirm').prop('disabled',button==0); + }); +} function abortOperation(pid) { swal({title:"",text:"",html:true,animation:'none',type:'warning',showCancelButton:true,confirmButtonText:"",cancelButtonText:""},function(){ $.post('/webGui/include/StartCommand.php',{kill:pid},function() { @@ -915,7 +938,54 @@ function parseINI(data){ } box.scrollTop(box[0].scrollHeight); }); - +var nchan_vmaction = new NchanSubscriber('/sub/vmaction',{subscriber:'websocket'}); +nchan_vmaction.on('message', function(data) { + if (!data || openDone(data)) return; + var box = $('pre#swaltext'); + data = data.split('\0'); + switch (data[0]) { + case 'addLog': + var rows = document.getElementsByClassName('logLine'); + if (rows.length) { + var row = rows[rows.length-1]; + row.innerHTML += data[1]+'
'; + } + break; + case 'progress': + var rows = document.getElementsByClassName('progress-'+data[1]); + if (rows.length) { + rows[rows.length-1].textContent = data[2]; + } + break; + case 'addToID': + var rows = document.getElementById(data[1]); + if (rows === null) { + rows = document.getElementsByClassName('logLine'); + if (rows.length) { + var row = rows[rows.length-1]; + row.innerHTML += 'VM ID ['+data[1]+']: '+data[2]+'.
'; + } + } else { + var rows_content = rows.getElementsByClassName('content'); + if (!rows_content.length || rows_content[rows_content.length-1].textContent != data[2]) { + rows.innerHTML += ''+data[2]+'.'; + } + } + break; + case 'show_Wait': + progress_span[data[1]] = document.getElementById('wait-'+data[1]); + progress_dots[data[1]] = setInterval(function(){if (((progress_span[data[1]].innerHTML += '.').match(/\./g)||[]).length > 9) progress_span[data[1]].innerHTML = progress_span[data[1]].innerHTML.replace(/\.+$/,'');},500); + break; + case 'stop_Wait': + clearInterval(progress_dots[data[1]]); + progress_span[data[1]].innerHTML = ''; + break; + default: + box.html(box.html()+data[0]); + break; + } + box.scrollTop(box[0].scrollHeight); +}); var backtotopoffset = 250; var backtotopduration = 500; $(window).scroll(function() { From ea5cc331356ef75e813990dca3b442903167a432 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 28 May 2023 18:46:51 +0100 Subject: [PATCH 14/18] WIP Update --- plugins/dynamix.vm.manager/VMMachines.page | 30 ++-- .../dynamix.vm.manager/include/VMMachines.php | 5 +- plugins/dynamix.vm.manager/include/VMajax.php | 6 - .../include/libvirt_helpers.php | 146 +++++++++--------- .../javascript/vmmanager.js | 54 ++----- plugins/dynamix/include/DefaultPageLayout.php | 4 +- 6 files changed, 118 insertions(+), 127 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 9b06fe820..6a76fbd76 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -196,8 +196,7 @@ function getisoimageboth(uuid,dev,bus,file,dev2,bus2,file2){ "_(Update)_": function(){ var target = box.find('#target'); if (target.length) { - target = target.val(); - + target = target.val(); } else target = ''; var target2 = box.find('#target2'); if (target2.length) { @@ -359,6 +358,15 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ document.getElementById("targetsnaprmvmeta").checked = true ; document.getElementById("targetsnapkeep").checked = true ; document.getElementById("targetsnapfspc").checked = true ; + if (opt== "pull") { + $('.toprow').hide(); + $('.targetpivotrow').hide(); + $('.targetdeleterow').hide(); + } else { + $('.toprow').show(); + $('.targetpivotrow').show(); + $('.targetdeleterow').show(); + } box.dialog({ title: "_("+optiontext+ ")_", @@ -379,6 +387,8 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ var keep = 'yes' var removemeta = 'yes' var free = 'yes' + var delete_file = 'yes' + var pivot = 'yes' var desc = '' box.find('#targetsnap').prop('disabled',true); if (opt == "create") { @@ -388,7 +398,10 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ } var targetbase = $('#targetblockbase').val() var targettop = $('#targetblocktop').val() - //ajaxVMDispatch({action:"snap-", uuid:uuid , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep, desc:desc} , "loadlist"); + x = box.find('#targetpivot').prop('checked') ; + if (x) pivot = 'yes' ; else pivot = 'no' ; + x = box.find('#targetdelete').prop('checked') ; + if (x) delete_file = 'yes' ; else delete_file = 'no' ; Ajaxurl = "VMAjaxCall.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMajax.php&" + $.param({action:opt , name:name ,targetbase:targetbase, targettop:targettop , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep})) ; openVMAction((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ; box.dialog('close'); @@ -401,9 +414,6 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ dialogStyle(); } -// ajaxVMDispatch({action:"snap-create-external", uuid:uuid}, "loadlist"); - // ajaxVMDispatch({action:"snap-revert-external", uuid:uuid , snapshotname:snapshotname , remove:'yes'}, "loadlist"); - function dialogStyle() { $('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('').prop('title'); $('.ui-dialog-title').css({'text-align':'center','width':'100%','font-size':'1.8rem'}); @@ -571,13 +581,15 @@ _(Snapshot Name)_: _(Base Image)_: -_(Top Image )_: +_(Top Image )_: +_(Pivot)_: + +_(Delete)_: + -

-
\ No newline at end of file diff --git a/plugins/dynamix.vm.manager/include/VMMachines.php b/plugins/dynamix.vm.manager/include/VMMachines.php index d269f27a8..68c075827 100644 --- a/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/plugins/dynamix.vm.manager/include/VMMachines.php @@ -21,6 +21,7 @@ require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php"; $user_prefs = '/boot/config/plugins/dynamix.vm.manager/userprefs.cfg'; +if (file_exists('/boot/config/plugins/dynamix.vm.manager/vmpreview')) $vmpreview = true ; else $vmpreview = false ; $vms = $lv->get_domains(); if (empty($vms)) { echo ''._('No Virtual Machines installed').''; @@ -103,7 +104,7 @@ } 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')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole); + $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); $kvm[] = "kvm.push({id:'$uuid',state:'$state'});"; switch ($state) { case 'running': @@ -254,7 +255,7 @@ $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "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')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"]); + $snapmenu = sprintf("onclick=\"addVMSnapContext('%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,$snapshot["name"],$vmpreview); echo "$tab|__   ".$snapshot["name"]."$snapshotdesc$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory"; $tab .="    " ; } diff --git a/plugins/dynamix.vm.manager/include/VMajax.php b/plugins/dynamix.vm.manager/include/VMajax.php index 5c1fa042a..b57025ace 100644 --- a/plugins/dynamix.vm.manager/include/VMajax.php +++ b/plugins/dynamix.vm.manager/include/VMajax.php @@ -339,12 +339,6 @@ function embed(&$syslinux, $key, $value) { $arrResponse = vm_snapshot($domName,$_REQUEST['snapshotname'],$_REQUEST['desc'],$_REQUEST['free']) ; break; -case 'snap-blockcommit': - requireLibvirt(); - echo "Hello" ; - #$arrResponse = vm_snapshot($domName,'',$_REQUEST['base'],$_REQUEST['top'],"","") ; - break; - case 'snap-images': requireLibvirt(); $html = vm_snapimages($domName,$_REQUEST['snapshotname'],$_REQUEST['only']) ; diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index d9f08e203..4d326c0a1 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1580,9 +1580,9 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { $capacity = $capacity + $disk["capacity"] ; } - switch ($snapslist[$snap]['state']) { + switch ($snapslist[$snap]['state']) { case "shutoff": - case "running": + case "running": #VM must be shutdown. $res = $lv->get_domain_by_name($vm); $dom = $lv->domain_get_info($res); @@ -1650,7 +1650,7 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { # Remove running XML and memory. $xmlfile = $primarypath."/$name.running" ; $memoryfile = $primarypath."/memory$name.mem" ; - #var_dump(is_file($xmlfile), is_file($memoryfile)) ; + if (is_file($memoryfile) && $action == "yes") unlink($memoryfile) ; if (is_file($xmlfile) && $action == "yes") unlink($xmlfile) ; if ($s['name'] == $snap) break ; @@ -1778,14 +1778,13 @@ function vm_snapremove($vm, $snap) { $xmlobj = custom::createArray('domain',$strXML) ; # Process disks. - $disks=($snapslist[$snap]['disks']) ; - var_dump($disks,$snaps) ; + $disks=($snapslist[$snap]['disks']) ; foreach ($disks as $disk) { $diskname = $disk["@attributes"]["name"] ; if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; $item = array_search($path,$snaps[$vm][$diskname]) ; - if (!$item) { + if ($item!==false) { $data = ["error" => "Image currently active for this domain."] ; return ($data) ; } @@ -1797,15 +1796,16 @@ function vm_snapremove($vm, $snap) { if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; if (is_file($path)) { - #if(!unlink("$path")) { - # $data = ["error" => "Unable to remove image file $path"] ; - # return ($data) ; - #} + if(!unlink("$path")) { + $data = ["error" => "Unable to remove image file $path"] ; + return ($data) ; + } } } - #$ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; - if($ret) + $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + #var_dump($ret) ; + if(!$ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else $data = ["success => 'true"] ; @@ -1879,13 +1879,14 @@ function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { } if ($action) $cmdstr .= " $action "; - $test = false ; - if ($test) { $cmdstr .= " --print-xml " ; execCommand_nchan($cmdstr) ; } else execCommand_nchan($cmdstr) ; + if ($test) $cmdstr .= " --print-xml " ; + + $error = execCommand_nchan($cmdstr,$path) ; #var_dump($cmdstr,$output) ; - if (strpos(" ".$output[0],"error") ) { + if (!$error) { $arrResponse = ['error' => substr($output[0],6) ] ; - #return($arrResponse) ; + return($arrResponse) ; } else { # Remove nvram snapshot $arrResponse = ['success' => true] ; @@ -1903,42 +1904,6 @@ function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { } -function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) { - /* - NAME - blockcopy - Start a block copy operation. - - SYNOPSIS - blockcopy [--dest ] [--bandwidth ] [--shallow] [--reuse-external] [--blockdev] [--wait] [--verbose] [--timeout ] [--pivot] [--finish] [--async] [--xml ] [--format ] [--granularity ] [--buf-size ] [--bytes] [--transient-job] [--synchronous-writes] [--print-xml] - - DESCRIPTION - Copy a disk backing image chain to dest. - - OPTIONS - [--domain] domain name, id or uuid - [--path] fully-qualified path of source disk - --dest path of the copy to create - --bandwidth bandwidth limit in MiB/s - --shallow make the copy share a backing chain - --reuse-external reuse existing destination - --blockdev copy destination is block device instead of regular file - --wait wait for job to reach mirroring phase - --verbose with --wait, display the progress - --timeout implies --wait, abort if copy exceeds timeout (in seconds) - --pivot implies --wait, pivot when mirroring starts - --finish implies --wait, quit when mirroring starts - --async with --wait, don't wait for cancel to finish - --xml filename containing XML description of the copy destination - --format format of the destination file - --granularity power-of-two granularity to use during the copy - --buf-size maximum amount of in-flight data during the copy - --bytes the bandwidth limit is in bytes/s rather than MiB/s - --transient-job the copy job is not persisted if VM is turned off - --synchronous-writes the copy job forces guest writes to be synchronously written to the destination - --print-xml print the XML used to start the copy job instead of starting the job - */ -} - function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { global $lv ; /* @@ -1967,11 +1932,26 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { */ $snapslist= getvmsnapshots($vm) ; $disks =$lv->get_disk_stats($vm) ; + foreach($disks as $disk) { + $file = $disk["file"] ; + $output = "" ; + exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; + foreach($output as $key => $line) { + $line=str_replace("image: ","",$line) ; + $output[$key] = $line ; + } + $snaps[$vm][$disk["device"]] = $output ; + $rev = "r".$disk["device"] ; + $reversed = array_reverse($output) ; + $snaps[$vm][$rev] = $reversed ; + } + $snaps_json=json_encode($snaps) ; + file_put_contents("/tmp/snaps",$snaps_json) ; #var_dump($disks) ; foreach($disks as $disk) { $path = $disk['file'] ; $cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --pivot --delete" ; - $cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --wait" ; + $cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --wait " ; # Process disks and update path. $snapdisks=($snapslist[$snap]['disks']) ; if ($base != "--base" && $base != "") { @@ -1985,26 +1965,17 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { } if ($basepath != "") $cmdstr .= " --base '$basepath' "; } - if ($top != "--top" && $top !="") { - #get file name from snapshot. - $snapdisks=($snapslist[$top]['disks']) ; - $toppath = "" ; - foreach ($snapdisks as $snapdisk) { - $diskname = $snapdisk["@attributes"]["name"] ; - if ($diskname != $disk['device']) continue ; - $toppath = $snapdisk["source"]["@attributes"]["file"] ; - } - if ($toppath != "") $cmdstr .= " --top '$toppath' "; - } - if ($action) $cmdstr .= " $action "; + if ($action) $cmdstr .= " $action "; $test = false ; - if ($test) { $cmdstr .= " --print-xml " ; execCommand_nchan($cmdstr) ; } else execCommand_nchan($cmdstr) ; - #var_dump($cmdstr,$output) ; - if (strpos(" ".$output[0],"error") ) { + if ($test) $cmdstr .= " --print-xml " ; + + $error = execCommand_nchan($cmdstr,$path) ; + + if (!$error) { $arrResponse = ['error' => substr($output[0],6) ] ; - #return($arrResponse) ; + return($arrResponse) ; } else { # Remove nvram snapshot $arrResponse = ['success' => true] ; @@ -2012,7 +1983,7 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { #Error Check } #If complete ok remove meta data for snapshots. - #if (!$test) $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; + $ret = $lv->domain_snapshot_delete($vm, $snap ,2) ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else @@ -2022,6 +1993,41 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { } +function vm_blockcopy($vm,$path,$base,$top,$pivot,$action) { + /* + NAME + blockcopy - Start a block copy operation. + + SYNOPSIS + blockcopy [--dest ] [--bandwidth ] [--shallow] [--reuse-external] [--blockdev] [--wait] [--verbose] [--timeout ] [--pivot] [--finish] [--async] [--xml ] [--format ] [--granularity ] [--buf-size ] [--bytes] [--transient-job] [--synchronous-writes] [--print-xml] + + DESCRIPTION + Copy a disk backing image chain to dest. + + OPTIONS + [--domain] domain name, id or uuid + [--path] fully-qualified path of source disk + --dest path of the copy to create + --bandwidth bandwidth limit in MiB/s + --shallow make the copy share a backing chain + --reuse-external reuse existing destination + --blockdev copy destination is block device instead of regular file + --wait wait for job to reach mirroring phase + --verbose with --wait, display the progress + --timeout implies --wait, abort if copy exceeds timeout (in seconds) + --pivot implies --wait, pivot when mirroring starts + --finish implies --wait, quit when mirroring starts + --async with --wait, don't wait for cancel to finish + --xml filename containing XML description of the copy destination + --format format of the destination file + --granularity power-of-two granularity to use during the copy + --buf-size maximum amount of in-flight data during the copy + --bytes the bandwidth limit is in bytes/s rather than MiB/s + --transient-job the copy job is not persisted if VM is turned off + --synchronous-writes the copy job forces guest writes to be synchronously written to the destination + --print-xml print the XML used to start the copy job instead of starting the job + */ +} ?> diff --git a/plugins/dynamix.vm.manager/javascript/vmmanager.js b/plugins/dynamix.vm.manager/javascript/vmmanager.js index 133dcbf35..41fe241bb 100644 --- a/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/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"){ +function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, console="web", preview=false){ var opts = []; var path = location.pathname; var x = path.indexOf("?"); @@ -107,10 +107,11 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c ajaxVMDispatch( {action:"domain-destroy", uuid:uuid}, "loadlist"); }}); opts.push({divider:true}); + if (preview) { opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) { e.preventDefault(); selectsnapshot(uuid , name, "--generate" , "create",false,state) ; - }}); + }}); } } else if (state == "pmsuspended") { opts.push({text:_("Resume"), icon:"fa-play", action:function(e) { e.preventDefault(); @@ -192,7 +193,7 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c } context.attach('#vm-'+uuid, opts); } -function addVMSnapContext(name, uuid, template, state, snapshotname){ +function addVMSnapContext(name, uuid, template, state, snapshotname, preview=false){ var opts = []; var path = location.pathname; var x = path.indexOf("?"); @@ -200,24 +201,28 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ context.settings({right:false,above:false}); if (state == "running") { -// opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); -// }}); + if (preview) { + opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { + e.preventDefault(); + ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); + }}); + } 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(); selectblock(uuid, name, snapshotname, "commit",true) ; }}); + if (preview) { opts.push({text:_("Block Pull"), 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(); selectblock(uuid, name, snapshotname, "pull",true) ; }}); -// opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); -// }}); + + opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { + e.preventDefault(); + ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); + }}); } } else { opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) { e.preventDefault(); @@ -229,34 +234,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname){ $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); selectsnapshot(uuid, name, snapshotname, "remove",true) ; }}); -// opts.push({text:_("Block Commit"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); -// }}); -// opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { -// e.preventDefault(); -// ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist"); -// }}); } - - if (state == "shutoff") { - opts.push({divider:true}); -// opts.push({text:_("Remove Snapshot"), icon:"fa-minus", action:function(e) { -// e.preventDefault(); -// snapname = "test" ; -// swal({ -// title:_("Are you sure?"), -// text:_("Remove Snapshot:") + snapname + _("\nfor VM: ") + name +"\n Note all snapshots taken after will be become invalid.", -// type:"warning", -// showCancelButton:true, -// confirmButtonText:_('Proceed'), -// cancelButtonText:_('Cancel') -// },function(){ -// $('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin'); -// ajaxVMDispatch({action:"snap-remove-external", uuid:uuid,snapshotname:snapshotname}, "loadlist"); -// }); -// }}); - } context.attach('#vmsnap-'+uuid, opts); } function startAll() { diff --git a/plugins/dynamix/include/DefaultPageLayout.php b/plugins/dynamix/include/DefaultPageLayout.php index 80aedd800..53aaf4205 100644 --- a/plugins/dynamix/include/DefaultPageLayout.php +++ b/plugins/dynamix/include/DefaultPageLayout.php @@ -350,7 +350,7 @@ function openVMAction(cmd,title,plg,func,start=0,button=0) { nchan_vmaction.start(); $.post('/webGui/include/StartCommand.php',{cmd:cmd,start:start},function(pid) { if (pid==0) { - nchan_docker.stop(); + nchan_vmaction.stop(); $('div.spinner.fixed').hide(); $(".upgrade_notice").addClass('alert'); return; @@ -963,7 +963,7 @@ function parseINI(data){ rows = document.getElementsByClassName('logLine'); if (rows.length) { var row = rows[rows.length-1]; - row.innerHTML += 'VM ID ['+data[1]+']: '+data[2]+'.
'; + row.innerHTML += ''+data[1]+': '+data[2]+'.
'; } } else { var rows_content = rows.getElementsByClassName('content'); From 67f67430601af0ca1b9e243804760334feb59251 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Mon, 29 May 2023 17:07:04 +0100 Subject: [PATCH 15/18] WIP Update Remove using snapshot metadata and create own record. --- plugins/dynamix.vm.manager/VMMachines.page | 9 +- plugins/dynamix.vm.manager/include/VMajax.php | 4 +- .../include/libvirt_helpers.php | 161 ++++++++++++------ .../javascript/vmmanager.js | 12 +- .../dynamix.vm.manager/scripts/VMAjaxCall.php | 35 ++-- 5 files changed, 137 insertions(+), 84 deletions(-) diff --git a/plugins/dynamix.vm.manager/VMMachines.page b/plugins/dynamix.vm.manager/VMMachines.page index 6a76fbd76..6d32b5643 100644 --- a/plugins/dynamix.vm.manager/VMMachines.page +++ b/plugins/dynamix.vm.manager/VMMachines.page @@ -396,13 +396,14 @@ function selectblock(uuid, name ,snaps, opt, getlist,state){ if (x) free = 'yes' ; else free = 'no' ; var desc = box.find("#targetsnapdesc").prop('value') ; } - var targetbase = $('#targetblockbase').val() - var targettop = $('#targetblocktop').val() + var targetbase = box.find("#targetblockbase").prop('value') ; + var targettop = box.find("#targetblocktop").prop('value') ; + x = box.find('#targetpivot').prop('checked') ; if (x) pivot = 'yes' ; else pivot = 'no' ; x = box.find('#targetdelete').prop('checked') ; if (x) delete_file = 'yes' ; else delete_file = 'no' ; - Ajaxurl = "VMAjaxCall.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMajax.php&" + $.param({action:opt , name:name ,targetbase:targetbase, targettop:targettop , snapshotname:target , remove:remove, free:free ,removemeta:removemeta ,keep:keep})) ; + Ajaxurl = "VMAjaxCall.php " + encodeURIComponent("/usr/local/emhttp/plugins/dynamix.vm.manager/include/VMajax.php&" + $.param({action:opt , name:name ,targetbase:targetbase, targettop:targettop , snapshotname:target , remove:remove, targetpivot:pivot ,removemeta:removemeta ,targetdelete:delete_file})) ; openVMAction((Ajaxurl),"Block Commit", "dynamix.vm.manager", "loadlist") ; box.dialog('close'); }, @@ -579,7 +580,7 @@ _(Snapshot Name)_:



"; /* Display VM Snapshots */ if ($snapshots != null) { - + $i=0 ; + foreach($snapshots as $snap) { + if ($snap['parent'] == "" || $snap['parent'] == "Base") $i++; + $steps[$i] .= $snap['name'].';' ; + } echo ""; echo ""; - $tab = "    " ; - foreach($snapshots as $snapshotname => $snapshot) { - $snapshotstate = _(ucfirst($snapshot["state"])) ; - $snapshotdesc = $snapshot["desc"] ; - $snapshotmemory = _(ucfirst($snapshot["memory"]["@attributes"]["snapshot"])) ; - $snapshotparent = $snapshot["parent"]["name"] ? $snapshot["parent"]["name"] : "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); - echo ""; - $tab .="    " ; + foreach($steps as $stepsline) + { + $snapshotlist = explode(";",$stepsline) ; + $tab = "    " ; + foreach($snapshotlist as $snapshotitem) { + if ($snapshotitem == "") continue ; + $snapshot = $snapshots[$snapshotitem] ; + $snapshotstate = _(ucfirst($snapshot["state"])) ; + $snapshotdesc = $snapshot["desc"] ; + $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); + echo ""; + $tab .="    " ; + } + echo ""; + } } - echo ""; -} echo "
_(Base Image)_: -
_(Top Image )_:
"._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory')."
$tab|__   ".$snapshot["name"]."$snapshotdesc$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory
$tab|__   ".$snapshot["name"]."$snapshotdesc$snapshotdatetime$snapshotstate$snapshotparent$snapshotmemory
"; - echo ""; } + echo "\0".implode($kvm); ?> diff --git a/plugins/dynamix.vm.manager/include/libvirt.php b/plugins/dynamix.vm.manager/include/libvirt.php index bbf5bbac4..b476349d1 100644 --- a/plugins/dynamix.vm.manager/include/libvirt.php +++ b/plugins/dynamix.vm.manager/include/libvirt.php @@ -770,7 +770,7 @@ function config_to_xml($config) { $vmrc = " - + $vmrcmousemode diff --git a/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/plugins/dynamix.vm.manager/include/libvirt_helpers.php index 721f4fbc2..bbbe272ba 100644 --- a/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1515,11 +1515,34 @@ function write_snapshots_database($vm,$name) { $b= json_decode($a, TRUE); $vmsnap = $b["name"] ; $snaps[$vmsnap]["name"]= $b["name"]; - if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; + #if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; + $snaps[$vmsnap]["parent"]= $b["parent"] ; $snaps[$vmsnap]["state"]= $b["state"]; $snaps[$vmsnap]["desc"]= $b["description"]; $snaps[$vmsnap]["memory"]= $b["memory"]; $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; + + $disks =$lv->get_disk_stats($vm) ; + foreach($disks as $disk) { + $file = $disk["file"] ; + $output = "" ; + exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; + foreach($output as $key => $line) { + $line=str_replace("image: ","",$line) ; + $output[$key] = $line ; + } + + $snaps[$vmsnap]['backing'][$disk["device"]] = $output ; + $rev = "r".$disk["device"] ; + $reversed = array_reverse($output) ; + $snaps[$vmsnap]['backing'][$rev] = $reversed ; + } + $parentfind = $snaps[$vmsnap]['backing'][$disk["device"]] ; + $parendfileinfo = pathinfo($parentfind[1]) ; + $snaps[$vmsnap]["parent"]= $parendfileinfo["extension"]; + $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"]; $value = json_encode($snaps,JSON_PRETTY_PRINT) ; @@ -1527,6 +1550,45 @@ function write_snapshots_database($vm,$name) { file_put_contents($dbpath."/snapshots.db",$value) ; } + function refresh_snapshots_database($vm) { + global $lv ; + $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; + #var_dump($dbpath) ; + if (!is_dir($dbpath)) mkdir($dbpath) ; + $snaps_json = file_get_contents($dbpath."/snapshots.db") ; + $snaps = json_decode($snaps_json,true) ; + foreach($snaps as $vmsnap=>$snap) + #var_dump($snaps_json) ; + + + $disks =$lv->get_disk_stats($vm) ; + foreach($disks as $disk) { + $file = $disk["file"] ; + $output = "" ; + exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; + foreach($output as $key => $line) { + $line=str_replace("image: ","",$line) ; + $output[$key] = $line ; + } + + $snaps[$vmsnap]['backing'][$disk["device"]] = $output ; + $rev = "r".$disk["device"] ; + $reversed = array_reverse($output) ; + $snaps[$vmsnap]['backing'][$rev] = $reversed ; + } + $parentfind = $snaps[$vmsnap]['backing'][$disk["device"]] ; + $parendfileinfo = pathinfo($parentfind[1]) ; + $snaps[$vmsnap]["parent"]= $parendfileinfo["extension"]; + $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"]; + var_dump($snaps) ; + $value = json_encode($snaps,JSON_PRETTY_PRINT) ; + + file_put_contents($dbpath."/snapshots.db",$value) ; + } + function delete_snapshots_database($vm,$name) { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; @@ -1944,6 +2006,7 @@ function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { #Remove NVRAMs #if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; #If complete ok remove meta data for snapshots. + refresh_snapshots_database($vm) ; $ret = $ret = delete_snapshots_database("$vm","$snap") ; ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; @@ -2033,6 +2096,7 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { } #If complete ok remove meta data for snapshots. #$ret = delete_snapshots_database("$vm","$snap") ; + refresh_snapshots_database($vm) ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; else From 8f69ed880d02321e3d26a354d615c53c65be9af0 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sat, 3 Jun 2023 07:47:48 +0100 Subject: [PATCH 17/18] Add snapshot directory setup to rc.libvirt --- etc/rc.d/rc.libvirt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/rc.d/rc.libvirt b/etc/rc.d/rc.libvirt index e19f7790f..b2e4e11f0 100755 --- a/etc/rc.d/rc.libvirt +++ b/etc/rc.d/rc.libvirt @@ -159,6 +159,10 @@ start_libvirtd() { cp -n /etc/libvirt-/*.conf /etc/libvirt &> /dev/null # ensure tpm-states path exists mkdir -p /etc/libvirt/qemu/swtpm/tpm-states + # Setup Snapshot persistance. + mkdir -p /etc/libvirt/qemu/snapshot/ + rm -r /var/lib/libvirt/qemu/snapshot/ + ln -fs /etc/libvirt/qemu/snapshot/ /var/lib/libvirt/qemu/ echo "Starting libvirtd..." mkdir -p $(dirname $LIBVIRTD_PIDFILE) check_processor From c74236612eec7c7b1124e17536551e7770929716 Mon Sep 17 00:00:00 2001 From: SimonFair <39065407+SimonFair@users.noreply.github.com> Date: Sun, 4 Jun 2023 12:09:36 +0100 Subject: [PATCH 18/18] WIP Update --- .../dynamix.vm.manager/VMMachines.page | 4 +- .../dynamix.vm.manager/include/VMMachines.php | 11 +- .../dynamix.vm.manager/include/libvirt.php | 16 +- .../include/libvirt_helpers.php | 185 +++++++----------- .../javascript/vmmanager.js | 16 +- 5 files changed, 102 insertions(+), 130 deletions(-) diff --git a/emhttp/plugins/dynamix.vm.manager/VMMachines.page b/emhttp/plugins/dynamix.vm.manager/VMMachines.page index 0fdab5663..1391c5438 100644 --- a/emhttp/plugins/dynamix.vm.manager/VMMachines.page +++ b/emhttp/plugins/dynamix.vm.manager/VMMachines.page @@ -278,7 +278,7 @@ function selectsnapshot(uuid, name ,snaps, opt, getlist,state){ document.getElementById("targetsnaprmv").checked = true ; document.getElementById("targetsnaprmvmeta").checked = true ; document.getElementById("targetsnapkeep").checked = true ; - document.getElementById("targetsnapfspc").checked = false ; + document.getElementById("targetsnapfspc").checked = true ; box.dialog({ title: "_("+optiontext+ ")_", @@ -533,7 +533,7 @@ _(CD2 ISO Image)_: _(Snapshot Name)_: _(Check free space)_: - + _(Description )_: diff --git a/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php b/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php index e88ce6c8f..c79cbdba8 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php +++ b/emhttp/plugins/dynamix.vm.manager/include/VMMachines.php @@ -245,13 +245,14 @@ echo ""; /* Display VM Snapshots */ if ($snapshots != null) { - $i=0 ; + $j=0 ; + $steps = array() ; foreach($snapshots as $snap) { - if ($snap['parent'] == "" || $snap['parent'] == "Base") $i++; - $steps[$i] .= $snap['name'].';' ; + if ($snap['parent'] == "" || $snap['parent'] == "Base") $j++; + $steps[$j] .= $snap['name'].';' ; } - echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; - echo ""; + echo " "._('Snapshots').""._('Date/Time').""._('Type').""._('Parent').""._('Memory').""; + echo ""; foreach($steps as $stepsline) { $snapshotlist = explode(";",$stepsline) ; diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php index b476349d1..05b272cb5 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt.php @@ -1446,7 +1446,7 @@ function domain_get_icon_url($domain) { function domain_change_xml($domain, $xml) { $dom = $this->get_domain_object($domain); - if (!($old_xml = domain_get_xml($dom))) + if (!($old_xml = $this->domain_get_xml($dom))) return $this->_set_last_error(); if (!libvirt_domain_undefine($dom)) return $this->_set_last_error(); @@ -1874,6 +1874,20 @@ function nvram_revert_snapshot($uuid,$snapshotname) { return false; } + function nvram_delete_snapshot($uuid,$snapshotname) { + // snapshot backup OVMF VARS if this domain had them + if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd')) { + unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi.fd') ; + return true; + } + if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd')) { + unlink('/etc/libvirt/qemu/nvram/'.$uuid.$snapshotname.'_VARS-pure-efi-tpm.fd') ; + return true; + } + return false; + } + + function is_dir_empty($dir) { if (!is_readable($dir)) return NULL; $handle = opendir($dir); diff --git a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php index bbbe272ba..4e5f13f4f 100644 --- a/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php +++ b/emhttp/plugins/dynamix.vm.manager/include/libvirt_helpers.php @@ -1463,66 +1463,37 @@ function compare_creationtime($a, $b) { function compare_creationtimelt($a, $b) { return $a['creationtime'] < $b['creationtime']; - } + } function getvmsnapshots($vm) { $snaps=array() ; - $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; $snaps_json = file_get_contents($dbpath."/snapshots.db") ; $snaps = json_decode($snaps_json,true) ; if (is_array($snaps)) uasort($snaps,'compare_creationtime') ; return $snaps ; } - function getvmsnapshots2($vm) { - global $lv ; - $vmsnaps = $lv->domain_snapshots_list($lv->get_domain_object($vm)) ; - $snaps=array() ; - foreach($vmsnaps as $vmsnap) { - $snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$vmsnap) ; - $snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ; - $a = simplexml_load_string($snapshot_xml) ; - if($a == false) continue ; - $a = json_encode($a) ; - $b= json_decode($a, TRUE); - $vmsnap = $b["name"] ; - $snaps[$vmsnap]["name"]= $b["name"]; - if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; - $snaps[$vmsnap]["state"]= $b["state"]; - $snaps[$vmsnap]["desc"]= $b["description"]; - $snaps[$vmsnap]["memory"]= $b["memory"]; - $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; - if (array_key_exists(0 , $b["disks"]["disk"])) $snaps[$vmsnap]["disks"]= $b["disks"]["disk"]; else $snaps[$vmsnap]["disks"][0]= $b["disks"]["disk"]; - } - - if (is_array($snaps)) uasort($snaps,'compare_creationtime') ; - return $snaps ; - } + function write_snapshots_database($vm,$name) { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; - #var_dump($dbpath) ; if (!is_dir($dbpath)) mkdir($dbpath) ; $snaps_json = file_get_contents($dbpath."/snapshots.db") ; $snaps = json_decode($snaps_json,true) ; - #var_dump($snaps_json) ; - $snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$name) ; - $snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ; - $a = simplexml_load_string($snapshot_xml) ; - #if($a == false) continue ; - $a = json_encode($a) ; - - $b= json_decode($a, TRUE); - $vmsnap = $b["name"] ; - $snaps[$vmsnap]["name"]= $b["name"]; - #if(isset($b["parent"])) $snaps[$vmsnap]["parent"]= $b["parent"]; else $snaps[$vmsnap]["parent"]["name"] = "Base" ; - $snaps[$vmsnap]["parent"]= $b["parent"] ; - $snaps[$vmsnap]["state"]= $b["state"]; - $snaps[$vmsnap]["desc"]= $b["description"]; - $snaps[$vmsnap]["memory"]= $b["memory"]; - $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; + $snapshot_res=$lv->domain_snapshot_lookup_by_name($vm,$name) ; + $snapshot_xml=$lv->domain_snapshot_get_xml($snapshot_res) ; + $a = simplexml_load_string($snapshot_xml) ; + $a = json_encode($a) ; + $b= json_decode($a, TRUE); + $vmsnap = $b["name"] ; + $snaps[$vmsnap]["name"]= $b["name"]; + $snaps[$vmsnap]["parent"]= $b["parent"] ; + $snaps[$vmsnap]["state"]= $b["state"]; + $snaps[$vmsnap]["desc"]= $b["description"]; + $snaps[$vmsnap]["memory"]= $b["memory"]; + $snaps[$vmsnap]["creationtime"]= $b["creationTime"]; - $disks =$lv->get_disk_stats($vm) ; + $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; $output = "" ; @@ -1544,23 +1515,20 @@ function write_snapshots_database($vm,$name) { 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"]; - + + $value = json_encode($snaps,JSON_PRETTY_PRINT) ; - #var_dump($value) ; file_put_contents($dbpath."/snapshots.db",$value) ; } function refresh_snapshots_database($vm) { global $lv ; $dbpath = "/etc/libvirt/qemu/snapshot/$vm" ; - #var_dump($dbpath) ; if (!is_dir($dbpath)) mkdir($dbpath) ; $snaps_json = file_get_contents($dbpath."/snapshots.db") ; $snaps = json_decode($snaps_json,true) ; foreach($snaps as $vmsnap=>$snap) - #var_dump($snaps_json) ; - - + $disks =$lv->get_disk_stats($vm) ; foreach($disks as $disk) { $file = $disk["file"] ; @@ -1582,9 +1550,29 @@ function refresh_snapshots_database($vm) { $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"]; - var_dump($snaps) ; $value = json_encode($snaps,JSON_PRETTY_PRINT) ; + $res = $lv->get_domain_by_name($vm); + if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ; + + + #Remove any NVRAMs that are no longer valid. + # Get uuid + $vmuuid = $lv->domain_get_uuid($vm) ; + #Get list of files + #$filepath = "/etc/libvirt/qemu/nvram/'.$uuid*" ; #$snapshotname" + $filepath = "/etc/libvirt/qemu/nvram/$vmuuid*" ; #$snapshotname" + $nvram_files=glob($filepath) ; + foreach($nvram_files as $key => $nvram_file) { + if ($nvram_file == "/etc/libvirt/qemu/nvram/$vmuuid"."_VARS-pure-efi.fd" || $nvram_file == "/etc/libvirt/qemu/nvram/$vmuuid"."_VARS-pure-efi-tpm.fd" ) unset($nvram_files[$key]) ; + foreach ($snaps as $snapshotname => $snap) { + $tpmfilename = "/etc/libvirt/qemu/nvram/".$vmuuid.$snapshotname."_VARS-pure-efi-tpm.fd" ; + $nontpmfilename = "/etc/libvirt/qemu/nvram/".$vmuuid.$snapshotname."_VARS-pure-efi.fd" ; + if ($nvram_file == $tpmfilename || $nvram_file == $nontpmfilename ) { + unset($nvram_files[$key]) ;} + } + } + foreach ($nvram_files as $nvram_file) unlink($nvram_file) ; + file_put_contents($dbpath."/snapshots.db",$value) ; } @@ -1596,7 +1584,6 @@ function delete_snapshots_database($vm,$name) { $snaps = json_decode($snaps_json,true) ; unset($snaps[$name]) ; $value = json_encode($snaps,JSON_PRETTY_PRINT) ; - #var_dump($value) ; file_put_contents($dbpath."/snapshots.db",$value) ; return true ; } @@ -1675,25 +1662,6 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { $snapslist= getvmsnapshots($vm) ; $disks =$lv->get_disk_stats($vm) ; - foreach($disks as $disk) { - $file = $disk["file"] ; - $output = "" ; - exec("qemu-img info --backing-chain -U '$file' | grep image:",$output) ; - foreach($output as $key => $line) { - $line=str_replace("image: ","",$line) ; - $output[$key] = $line ; - } - - $snaps[$vm][$disk["device"]] = $output ; - $rev = "r".$disk["device"] ; - $reversed = array_reverse($output) ; - $snaps[$vm][$rev] = $reversed ; - $pathinfo = pathinfo($file) ; - $filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ; - $diskspec .= " --diskspec ".$disk["device"].",snapshot=external,file=".$filenew ; - $capacity = $capacity + $disk["capacity"] ; - } - switch ($snapslist[$snap]['state']) { case "shutoff": case "running": @@ -1718,8 +1686,8 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { $primarypathinfo = pathinfo($path) ; $primarypath = $primarypathinfo['dirname'] ; } - $item = array_search($path,$snaps[$vm][$diskname]) ; - $newpath = $snaps[$vm][$diskname][$item + 1]; + $item = array_search($path,$snapslist[$snap]['backing'][$diskname]) ; + $newpath = $snapslist[$snap]['backing'][$diskname][$item + 1]; $json_info = getDiskImageInfo($newpath) ; foreach($xmlobj['devices']['disk'] as $ddk => $dd){ if ($dd['target']["@attributes"]['dev'] == $diskname) { @@ -1741,66 +1709,53 @@ function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes') { if ($diskname == "hda" || $diskname == "hdb") continue ; $path = $disk["source"]["@attributes"]["file"] ; if (is_file($path) && $action == "yes") unlink("$path") ; - $item = array_search($path,$snaps[$vm]["r".$diskname]) ; + $item = array_search($path,$snapslist[$snap]['backing']["r".$diskname]) ; $item++ ; while($item > 0) { - if (!isset($snaps[$vm]["r".$diskname][$item])) break ; - $newpath = $snaps[$vm]["r".$diskname][$item] ; + if (!isset($snapslist[$snap]['backing']["r".$diskname][$item])) break ; + $newpath = $snapslist[$snap]['backing']["r".$diskname][$item] ; if (is_file($newpath) && $action == "yes") unlink("$newpath") ; - $item++ ; - } } uasort($snapslist,'compare_creationtimelt') ; foreach($snapslist as $s) { $name = $s['name'] ; + + $xmlfile = $primarypath."/$name.running" ; + $memoryfile = $primarypath."/memory$name.mem" ; + + if ($snapslist[$snap]['state'] == "running") { + # Set XML to saved XML + $xml = file_get_contents($xmlfile) ; + $xmlobj = custom::createArray('domain',$xml) ; + $xml = custom::createXML('domain',$xmlobj)->saveXML(); + $rtn = $lv->domain_define($xml) ; + + # Resotre Memeory. + + $makerun = true ; + if ($makerun == true) exec("virsh restore $memoryfile") ; + #exec("virsh restore $memoryfile") ; + } #Delete Metadata only. if ($actionmeta == "yes") { $ret = delete_snapshots_database("$vm","$name") ; } - # Remove running XML and memory. - $xmlfile = $primarypath."/$name.running" ; - $memoryfile = $primarypath."/memory$name.mem" ; - if (is_file($memoryfile) && $action == "yes") unlink($memoryfile) ; if (is_file($xmlfile) && $action == "yes") unlink($xmlfile) ; if ($s['name'] == $snap) break ; } #if VM was started restart. - if ($state == 'running') { + if ($state == 'running' && $snapslist[$snap]['state'] != "running") { $arrResponse = $lv->domain_start($vm) ; } if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; break ; - - case "other": - break ; - #VM must be shutdown. - # if VM running shutdown. Record was running. - # Replace disk paths - # remove snapshot meta data and images for all snpahots. - # if VM was started restart. - - - #type running - #Non Live restores - #VM must be shutdown. - # if VM running shutdown. Record was running. - # Replace disk paths - # remove snapshot meta data and images for all snpahots. - # if VM was started restart. - - #Live restore(currently not supported.) - # Freeze VM - # Replace disk paths - # Replace Mem - # remove snapshot meta data and images for all snpahots. - # Unfreeze VM } $arrResponse = ['success' => true] ; return($arrResponse) ; @@ -1917,6 +1872,9 @@ function vm_snapremove($vm, $snap) { } } + # Delete NVRAM + if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap) ; + $ret = delete_snapshots_database("$vm","$snap") ; if(!$ret) @@ -2003,9 +1961,9 @@ function vm_blockcommit($vm, $snap ,$path,$base,$top,$pivot,$action) { } } - #Remove NVRAMs - #if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; - #If complete ok remove meta data for snapshots. + # Delete NVRAM + #if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap) ; + refresh_snapshots_database($vm) ; $ret = $ret = delete_snapshots_database("$vm","$snap") ; ; if($ret) @@ -2092,10 +2050,9 @@ function vm_blockpull($vm, $snap ,$path,$base,$top,$pivot,$action) { # Remove nvram snapshot $arrResponse = ['success' => true] ; } - #Error Check + } - #If complete ok remove meta data for snapshots. - #$ret = delete_snapshots_database("$vm","$snap") ; + refresh_snapshots_database($vm) ; if($ret) $data = ["error" => "Unable to remove snap metadata $snap"] ; diff --git a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js index 2a5929f43..a3907ffe0 100644 --- a/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js +++ b/emhttp/plugins/dynamix.vm.manager/javascript/vmmanager.js @@ -107,11 +107,11 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, c ajaxVMDispatch( {action:"domain-destroy", uuid:uuid}, "loadlist"); }}); opts.push({divider:true}); - if (preview) { + opts.push({text:_("Create Snapshot"), icon:"fa-clone", action:function(e) { e.preventDefault(); selectsnapshot(uuid , name, "--generate" , "create",false,state) ; - }}); } + }}); } else if (state == "pmsuspended") { opts.push({text:_("Resume"), icon:"fa-play", action:function(e) { e.preventDefault(); @@ -201,24 +201,24 @@ function addVMSnapContext(name, uuid, template, state, snapshotname, preview=fal context.settings({right:false,above:false}); if (state == "running") { - if (preview) { - opts.push({text:_("Revert snapshot"), icon:"fa-stop", action:function(e) { + + opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) { e.preventDefault(); - ajaxVMDispatch({action:"snapshot-revert-externa", uuid:uuid, snapshotname:snapshotname}, "loadlist"); + selectsnapshot(uuid, name, snapshotname, "revert",true) ; }}); - } + 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(); selectblock(uuid, name, snapshotname, "commit",true) ; }}); - if (preview) { + opts.push({text:_("Block Pull"), 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(); selectblock(uuid, name, snapshotname, "pull",true) ; }}); - + if (preview) { opts.push({text:_("Block Copy"), icon:"fa-stop", action:function(e) { e.preventDefault(); ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist");