Merge pull request #1521 from SimonFair/Multi-disk-locations

Add storage override option
This commit is contained in:
tom mortensen
2023-11-27 23:44:21 -08:00
committed by GitHub
6 changed files with 127 additions and 26 deletions

View File

@@ -200,7 +200,7 @@ foreach ($vms as $vm) {
echo "<tr child-id='$i' id='name-$i".(in_array('name-'.$i++,$show) ? "'>" : "' style='display:none'>");
echo "<td colspan='8' style='margin:0;padding:0'>";
echo "<table class='tablesorter domdisk'>";
echo "<thead class='child'><tr><th><i class='fa fa-hdd-o'></i> <b>",_('Disk devices'),"</b></th><th>",_('Serial'),"</b></th><th>",_('Bus'),"</th><th>",_('Capacity'),"</th><th>",_('Allocation'),"</th><th>Boot Order</th</tr></thead>";
echo "<thead class='child'><tr><th><i class='fa fa-hdd-o'></i> <b>",_('Disk devices/Volume'),"</b></th><th>",_('Serial'),"</b></th><th>",_('Bus'),"</th><th>",_('Capacity'),"</th><th>",_('Allocation'),"</th><th>Boot Order</th</tr></thead>";
echo "<tbody class='child'>";
/* Display VM disks */
@@ -213,7 +213,9 @@ foreach ($vms as $vm) {
$boot= $arrDisk["boot order"];
$serial = $arrDisk["serial"];
if ($boot < 1) $boot = _('Not set');
echo "<tr><td>$disk</td><td>$serial</td><td>$bus</td>";
$reallocation = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($disk)." 2>/dev/null"));
if (!empty($reallocation)) $reallocationstr = "($reallocation)"; else $reallocationstr = "";
echo "<tr><td>$disk $reallocationstr</td><td>$serial</td><td>$bus</td>";
if ($state == 'shutoff') {
echo "<td title='Click to increase Disk Size'>";
echo "<form method='get' action=''>";

View File

@@ -399,7 +399,7 @@ case 'disk-create':
if (!is_dir($dir)) mkdir($dir);
// determine the actual disk if user share is being used
$dir = transpose_user_path($dir);
@exec("chattr +C -R ".escapeshellarg($dir)." >/dev/null");
#@exec("chattr +C -R ".escapeshellarg($dir)." >/dev/null");
$strLastLine = exec("qemu-img create -q -f ".escapeshellarg($driver)." ".escapeshellarg($disk)." ".escapeshellarg($size)." 2>&1", $out, $status);
$arrResponse = empty($status)
? ['success' => true]

View File

@@ -79,7 +79,7 @@
$folder = transpose_user_path($folder);
@shell_exec("chattr +C -R " . escapeshellarg($folder) . " &>/dev/null");
#@shell_exec("chattr +C -R " . escapeshellarg($folder) . " &>/dev/null");
return true;
}
@@ -208,6 +208,7 @@
$return_value = 0;
} else {
$strImgRawLocationPath = $strImgPath;
if (!empty($disk['storage']) && !empty($disk['select']) && $disk['select'] == 'auto' && $disk['storage'] != "default") $disk['select'] = $disk['storage'];
if (!empty($disk['select']) && (!in_array($disk['select'], ['auto', 'manual'])) && (is_dir('/mnt/'.$disk['select']))) {
// Force qemu disk creation to happen directly on either cache/disk1/disk2 ect based on dropdown selection
$strImgRawLocationPath = str_replace('/mnt/user/', '/mnt/'.$disk['select'].'/', $strImgPath);
@@ -1043,6 +1044,8 @@
function domain_new($config) {
# Set storage for disks.
foreach ($config['disk'] as $i => $disk) { $config['disk'][$i]['storage'] = $config['template']['storage'];}
// attempt to create all disk images if needed
$diskcount = 0;
if (!empty($config['disk'])) {
@@ -1963,6 +1966,20 @@
return false;
}
function nvram_rename($uuid,$newuuid) {
// rename backup OVMF VARS if this domain had them
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd')) {
rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi.fd_backup', '/etc/libvirt/qemu/nvram/'.$newuuid.'_VARS-pure-efi.fd');
return true;
}
if (is_file('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd')) {
rename('/etc/libvirt/qemu/nvram/'.$uuid.'_VARS-pure-efi-tpm.fd_backup', '/etc/libvirt/qemu/nvram/'.$newuuid.'_VARS-pure-efi-tpm.fd');
return true;
}
return false;
}
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')) {

View File

@@ -1610,7 +1610,9 @@ private static $encoding = 'UTF-8';
$dom = $lv->domain_get_info($res);
$state = $lv->domain_state_translate($dom['state']);
$vmxml = $lv->domain_get_xml($res) ;
file_put_contents("/tmp/cloningxml" ,$vmxml) ;
file_put_contents("/tmp/cloningxml" ,$vmxml) ; ## Remove before stable.
$storage = $lv->_get_single_xpath_result($vm, '//domain/metadata/*[local-name()=\'vmtemplate\']/@storage');
if (empty($storage)) $storage = "default" ;
# if VM running shutdown. Record was running.
if ($state != 'shutoff') {write("addLog\0".htmlspecialchars(_("Shuting down $vm current $state"))); $arrResponse = $lv->domain_destroy($vm) ; }
# Wait for shutdown?
@@ -1666,7 +1668,7 @@ private static $encoding = 'UTF-8';
$file_clone[$diskid]["target"] = $config["disk"][$diskid]["new"] ;
}
$clonedir = $domain_cfg['DOMAINDIR'].$clone ;
if ($storage == "default") $clonedir = $domain_cfg['DOMAINDIR'].$clone ; else $clonedir = str_replace('/mnt/user/', "/mnt/$storage/", $domain_cfg['DOMAINDIR']).$clone;
if (!is_dir($clonedir)) {
mkdir($clonedir,0777,true) ;
chown($clonedir, 'nobody');
@@ -1680,13 +1682,13 @@ private static $encoding = 'UTF-8';
$target = $disk['target'] ;
$source = $disk['source'] ;
if ($target == $source) { write("addLog\0".htmlspecialchars(_("New image file is same as old"))); return( false) ; }
$sourcerealdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($source)." 2>/dev/null"));
$reptgt = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $target);
$repsrc = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $source);
if ($storage == "default") $sourcerealdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($source)." 2>/dev/null")); else $sourcerealdisk = $storage;
$reptgt = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $target);
$repsrc = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $source);
$cmdstr = "cp --reflink=always '$repsrc' '$reptgt'" ;
if ($reflink == true) { $refcmd = $cmdstr ; } else {$refcmd = false; }
$cmdstr = "rsync -ahPIXS --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$source' '$target'" ;
$cmdstr = "rsync -ahPIXS --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$repsrc' '$reptgt'" ;
$error = execCommand_nchan_clone($cmdstr,$target,$refcmd) ;
if (!$error) { write("addLog\0".htmlspecialchars("Image copied failed.")); return( false) ; }
}
@@ -1695,7 +1697,7 @@ private static $encoding = 'UTF-8';
write("addLog\0".htmlspecialchars("Creating new XML $clone"));
$xml = $lv->config_to_xml($config, true) ;
file_put_contents("/tmp/clonexml" ,$xml) ;
file_put_contents("/tmp/clonexml" ,$xml) ; ## Remove before stable.
$rtn = $lv->domain_define($xml) ;
return($rtn) ;
@@ -1841,6 +1843,8 @@ private static $encoding = 'UTF-8';
$res = $lv->get_domain_by_name($vm);
$dom = $lv->domain_get_info($res);
$state = $lv->domain_state_translate($dom['state']);
$storage = $lv->_get_single_xpath_result($vm, '//domain/metadata/*[local-name()=\'vmtemplate\']/@storage');
if (empty($storage)) $storage = "default" ;
#Get disks for --diskspec
$disks =$lv->get_disk_stats($vm) ;
@@ -1852,16 +1856,23 @@ private static $encoding = 'UTF-8';
foreach($disks as $disk) {
$file = $disk["file"] ;
$pathinfo = pathinfo($file) ;
$filenew = $pathinfo["dirname"].'/'.$pathinfo["filename"].'.'.$name.'qcow2' ;
$dirpath = $pathinfo["dirname"];
if ($storage == "default") {
$dirpath = $pathinfo["dirname"];
} else {
$storagelocation = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($file)." 2>/dev/null"));
$dirpath= str_replace('/mnt/user/', "/mnt/$storagelocation/", $dirpath);
}
$filenew = $dirpath.'/'.$pathinfo["filename"].'.'.$name.'qcow2' ;
$diskspec .= " --diskspec '".$disk["device"]."',snapshot=external,file='".$filenew."'" ;
$capacity = $capacity + $disk["capacity"] ;
}
$dirpath = $pathinfo["dirname"] ;
#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 = "" ;
if ($memorysnap = "yes") $memspec = ' --memspec "'.$dirpath.'/memory'.$name.'.mem",snapshot=external' ; else $memspec = "" ;
$cmdstr = "virsh snapshot-create-as '$vm' --name '$name' $snapshotdesc --atomic" ;
@@ -1874,7 +1885,7 @@ private static $encoding = 'UTF-8';
}
#Check free space.
$dirfree = disk_free_space($pathinfo["dirname"]) ;
$dirfree = disk_free_space($dirpath) ;
$capacity *= 1 ;
@@ -1883,8 +1894,8 @@ private static $encoding = 'UTF-8';
#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" ;
file_put_contents("/tmp/xmltst", "$xmlfile" ) ;
$xmlfile = $dirpath."/".$name.".running" ;
file_put_contents("/tmp/xmltst", "$xmlfile" ) ;## Remove before stable.
if ($state == "running") exec("virsh dumpxml '$vm' > ".escapeshellarg($xmlfile),$outxml,$rtnxml) ;
$output= [] ;
@@ -1943,7 +1954,9 @@ private static $encoding = 'UTF-8';
}
}
$xml = custom::createXML('domain',$xmlobj)->saveXML();
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
$new = $lv->domain_define($xml);
file_put_contents("/tmp/xmlrevert", "$xml" ) ;## Remove before stable.
if ($new)
$arrResponse = ['success' => true] ; else
$arrResponse = ['error' => $lv->get_last_error()] ;
@@ -1978,13 +1991,14 @@ private static $encoding = 'UTF-8';
$xml = file_get_contents($xmlfile) ;
$xmlobj = custom::createArray('domain',$xml) ;
$xml = custom::createXML('domain',$xmlobj)->saveXML();
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
file_put_contents("/tmp/xmlrevert2", "$xml" ) ;## Remove before stable.
$rtn = $lv->domain_define($xml) ;
# Restore Memory.
$makerun = true ;
if ($makerun == true) exec("virsh restore ".escapeshellarg($memoryfile)) ;
#exec("virsh restore $memoryfile") ;
}
#Delete Metadata only.
if ($actionmeta == "yes") {
@@ -2027,8 +2041,6 @@ private static $encoding = 'UTF-8';
$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"] ;
}

View File

@@ -41,7 +41,8 @@
'template' => [
'name' => $strSelectedTemplate,
'icon' => $arrAllTemplates[$strSelectedTemplate]['icon'],
'os' => $arrAllTemplates[$strSelectedTemplate]['os']
'os' => $arrAllTemplates[$strSelectedTemplate]['os'],
'storage' => "default"
],
'domain' => [
'name' => $strSelectedTemplate,
@@ -216,11 +217,15 @@
}
}
}
$newuuid = $uuid;
$olduuid = $uuid;
// construct updated config
if (isset($_POST['xmldesc'])) {
// XML view
$xml = $_POST['xmldesc'];
$arrExistingConfig = custom::createArray('domain',$xml);
$newuuid = $arrExistingConfig['uuid'] ;
$xml = str_replace($olduuid,$newuuid,$xml);
} else {
// form view
if ($error = create_vdisk($_POST) === false) {
@@ -239,6 +244,7 @@
$lv->nvram_backup($uuid);
$lv->domain_undefine($dom);
$lv->nvram_restore($uuid);
if ($newuuid != $olduuid) $lv->nvram_rename($olduuid,$newuuid);
$new = $lv->domain_define($xml);
if ($new) {
$lv->domain_set_autostart($new, $newAutoStart);
@@ -295,6 +301,7 @@
<input type="hidden" name="domain[uuid]" value="<?=htmlspecialchars($arrConfig['domain']['uuid'])?>">
<input type="hidden" name="domain[arch]" value="<?=htmlspecialchars($arrConfig['domain']['arch'])?>">
<input type="hidden" name="domain[oldname]" id="domain_oldname" value="<?=htmlspecialchars($arrConfig['domain']['name'])?>">
<!--<input type="hidden" name="template[oldstorage]" id="storage_oldname" value="<?=htmlspecialchars($arrConfig['template']['storage'])?>"> -->
<input type="hidden" name="domain[memoryBacking]" id="domain_memorybacking" value="<?=htmlspecialchars($arrConfig['domain']['memoryBacking'])?>">
<table>
@@ -319,6 +326,66 @@
</blockquote>
</div>
<table>
<tr>
<?if (!$boolNew) $disablestorage = " disabled "; else $disablestorage = "";?>
<td>_(Override Storage Location)_:</td><td>
<select <?=$disablestorage?> name="template[storage]" class="disk_select narrow" id="storage_location" title="_(Location of virtual machine files)_">
<?
$default_storage=htmlspecialchars($arrConfig['template']['storage']);
echo mk_option($default_storage, 'default', _('Default'));
$strShareUserLocalInclude = '';
$strShareUserLocalExclude = '';
$strShareUserLocalUseCache = 'no';
// Get the share name and its configuration
$arrDomainDirParts = explode('/', $domain_cfg['DOMAINDIR']);
$strShareName = $arrDomainDirParts[3];
if (!empty($strShareName) && is_file('/boot/config/shares/'.$strShareName.'.cfg')) {
$arrShareCfg = parse_ini_file('/boot/config/shares/'.$strShareName.'.cfg');
if (!empty($arrShareCfg['shareInclude'])) {
$strShareUserLocalInclude = $arrShareCfg['shareInclude'];
}
if (!empty($arrShareCfg['shareExclude'])) {
$strShareUserLocalExclude = $arrShareCfg['shareExclude'];
}
if (!empty($arrShareCfg['shareUseCache'])) {
$strShareUserLocalUseCache = $arrShareCfg['shareUseCache'];
}
}
// Available cache pools
foreach ($pools as $pool) {
$strLabel = $pool.' - '.my_scale($disks[$pool]['fsFree']*1024, $strUnit).' '.$strUnit.' '._('free');
echo mk_option($default_storage, $pool, $strLabel);
}
// Determine which disks from the array are available for this share:
foreach ($disks as $name => $disk) {
if ((strpos($name, 'disk') === 0) && (!empty($disk['device']))) {
if ((!empty($strShareUserLocalInclude) && (strpos($strShareUserLocalInclude.',', $name.',') === false)) ||
(!empty($strShareUserLocalExclude) && (strpos($strShareUserLocalExclude.',', $name.',') !== false)) ||
(!empty($var['shareUserInclude']) && (strpos($var['shareUserInclude'].',', $name.',') === false)) ||
(!empty($var['shareUserExclude']) && (strpos($var['shareUserExclude'].',', $name.',') !== false))) {
// skip this disk based on local and global share settings
continue;
}
$strLabel = _(my_disk($name),3).' - '.my_scale($disk['fsFree']*1024, $strUnit).' '.$strUnit.' '._('free');
echo mk_option($default_storage, $name, $strLabel);
}
}
?>
</td></tr>
</table>
<blockquote class="inline_help">
<p>Specify the overide storage pool for VM. This option allows you to specify the physical pool/disk used to store the disk images and snapshot data.
Default will follow standard processing and store images in the default location for the share defined in the settings.
A pool/disk(Volume) will be the location for images if the default is overridden.
</p>
</blockquote>
<?
$migratehidden = "disabled hidden" ;
if ($arrConfig['domain']['cpumode'] == 'host-passthrough') $migratehidden = "" ;
@@ -692,6 +759,8 @@
</td>
</tr>
<input type="hidden" name="disk[<?=$i?>][storage]" id="disk[<?=$i?>][storage]" value="<?=htmlspecialchars($arrConfig['template']['storage'])?>">
<tr class="disk_file_options">
<td>_(vDisk Size)_:</td>
<td>
@@ -835,7 +904,7 @@
</select><input type="text" name="disk[{{INDEX}}][new]" autocomplete="off" spellcheck="false" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="img,qcow,qcow2" data-pickmatch="^[^.].*" data-pickroot="/mnt/" class="disk" id="disk_{{INDEX}}" value="" placeholder="_(Separate sub-folder and image will be created based on Name)_"><div class="disk_preview"></div>
</td>
</tr>
<input type="hidden" name="disk[{{INDEX}}][storage]" id="disk[{{INDEX}}][storage]" value="<?=htmlspecialchars($arrConfig['template']['storage'])?>">
<tr class="disk_file_options">
<td>_(vDisk Size)_:</td>
<td>

View File

@@ -229,10 +229,10 @@ case "stop":
exec("smartctl -X $type ".escapeshellarg("/dev/$port"));
break;
case "update":
if ($disk["transport"] == "scsi") {
if ($disk["transport"] == "scsi" || $disk["transport"] == "nvme") {
$progress = exec("smartctl -n standby -l selftest $type ".escapeshellarg("/dev/$port")."|grep -Pom1 '\d+%'");
if ($progress) {
echo "<span class='big'><i class='fa fa-spinner fa-pulse'></i> "._('self-test in progress').", ".(100-substr($progress,0,-1))."% "._('complete')."</span>";
if ($disk["transport"] == "nvme") echo "<span class='big'><i class='fa fa-spinner fa-pulse'></i> "._('self-test in progress').", ".(substr($progress,0,-1))."% "._('complete')."</span>"; else echo "<span class='big'><i class='fa fa-spinner fa-pulse'></i> "._('self-test in progress').", ".(100-substr($progress,0,-1))."% "._('complete')."</span>";
break;
}
} else {
@@ -243,7 +243,8 @@ case "update":
}
}
if ($disk["transport"] == "scsi") $result = trim(exec("smartctl -n standby -l selftest $type ".escapeshellarg("/dev/$port")."|grep -m1 '^# 1'|cut -c24-50"));
else $result = trim(exec("smartctl -n standby -l selftest $type ".escapeshellarg("/dev/$port")."|grep -m1 '^# 1'|cut -c26-55"));
else if ($disk["transport"] == "nvme") $result = trim(exec("smartctl -n standby -l selftest $type ".escapeshellarg("/dev/$port")."|grep -m1 '^ 0'|cut -c24-50"));
else $result = trim(exec("smartctl -n standby -l selftest $type ".escapeshellarg("/dev/$port")."|grep -m1 '^# 1'|cut -c26-55"));
if (!$result) {
$spundown = $disk['spundown'] ? "Device spundown, spinup to get information" : "No self-tests logged on this disk" ;
echo "<span class='big'>"._($spundown)."</span>";