diff --git a/emhttp/plugins/dynamix/CPUvms.page b/emhttp/plugins/dynamix/CPUvms.page
index 2edd7a867..327167393 100644
--- a/emhttp/plugins/dynamix/CPUvms.page
+++ b/emhttp/plugins/dynamix/CPUvms.page
@@ -24,165 +24,228 @@ $spinner = "
| ";
- if ($cpu2) $text[$n] .= "$cpu2
";
- }
- }
- $label = implode('
',array_fill(0,$loop,'CPU:'.($cpu2 ? '
HT:':'')));
- echo "$label | ".implode(array_map(function($t){return "$t | ";},$text));
+ // create the table header. Make multiple rows when CPU cores are many ;)
+ global $total, $cpus;
+ $loop = floor(($total-1)/32) + 1;
+ $text = [];
+ for ($c = 0; $c < $loop; $c++) {
+ $max = ($c == $loop-1 ? ($total % 32 ?: 32) : 32);
+ for ($n = 0; $n < $max; $n++) {
+ [$cpu1, $cpu2] = my_preg_split('/[,-]/', $cpus[$c * 32 + $n]);
+ if (empty($text[$n])) $text[$n] = '';
+ $text[$n] .= "$cpu1
";
+ if ($cpu2) $text[$n] .= "$cpu2
";
+ }
+ }
+ $label = implode('
', array_fill(0, $loop, 'CPU:' . ($cpu2 ? '
CPU:' : '')));
+ echo "$label | " . implode(array_map(function($t) {
+ return "$t | ";
+ }, $text));
}
?>
+
diff --git a/emhttp/plugins/dynamix/include/UpdateOne.php b/emhttp/plugins/dynamix/include/UpdateOne.php
index ebfe8539c..16bff2a60 100644
--- a/emhttp/plugins/dynamix/include/UpdateOne.php
+++ b/emhttp/plugins/dynamix/include/UpdateOne.php
@@ -1,4 +1,4 @@
-
-
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Helpers.php";
-// add translations
+/* add translations */
$_SERVER['REQUEST_URI'] = 'settings';
require_once "$docroot/webGui/include/Translations.php";
$map = $changes = [];
+$data = json_decode($_POST['data'], true);
+
function decode($data) {
- return str_replace('%2e','.',urldecode($data));
+ return str_replace('%2e', '.', urldecode($data));
}
-foreach (array_map('decode',explode(';',$_POST['names'])) as $name) $map[$name] = '';
+foreach (array_map('decode', explode(';', $data['names'])) as $name) $map[$name] = '';
-foreach($_POST as $key => $val) {
- if ($val != 'on') continue;
- [$name,$cpu] = my_explode(':',$key);
- $map[decode($name)] .= "$cpu,";
+foreach ($data['cpus'] as $key => $val) {
+ if ($val != 'on') continue;
+ [$name, $cpu] = my_explode(':', $key);
+ $map[decode($name)] .= "$cpu,";
}
-// map holds the list of each vm, container or isolcpus and its newly proposed cpu assignments
-$map = array_map(function($d){return substr($d,0,-1);},$map);
+/* map holds the list of each vm, container or isolcpus and its newly proposed cpu assignments */
+$map = array_map(function($d) {
+ return substr($d, 0, -1);
+}, $map);
-switch ($_POST['id']) {
-case 'vm':
- // report changed vms in temporary file
- require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
- foreach ($map as $name => $cpuset) {
- if (!strlen($cpuset)) {
- $reply = ['error' => _("Not allowed to assign ZERO cores")];
- break 2;
- }
- $uuid = $lv->domain_get_uuid($lv->get_domain_by_name($name));
- $cfg = domain_to_config($uuid);
- $cpus = implode(',',$cfg['domain']['vcpu']);
- // only act on changes
- if ($cpus != $cpuset || strlen($cpus) != strlen($cpuset)) {
- $changes[] = $name;
- // used by UpdateTwo.php to read new assignments
- file_put_contents("/var/tmp/$name.tmp",$cpuset);
- }
- }
- $reply = ['success' => (count($changes) ? implode(';',$changes) : '')];
- break;
-case 'ct':
- // update the XML file of the container
- require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
- $DockerClient = new DockerClient();
- $DockerTemplates = new DockerTemplates();
- $containers = $DockerClient->getDockerContainers();
- foreach ($map as $name => $cpuset) {
- // set full path of template file
- $file = $DockerTemplates->getUserTemplate($name);
- $xml = simplexml_load_file($file);
- if ($xml->CPUset) {
- // update node
- if ($xml->CPUset != $cpuset || strlen($xml->CPUset) != strlen($cpuset)) $xml->CPUset = $cpuset;
- } else {
- // add node
- $xml->addChild('CPUset',$cpuset);
- }
- // only act on changes
- foreach ($containers as $ct) if ($ct['Name']==$name) break;
- if ($ct['CPUset'] != $cpuset || strlen($ct['CPUset']) != strlen($cpuset)) {
- $changes[] = $name;
- // used by UpdateTwo.php to read new assignments
- file_put_contents($file,$xml->saveXML());
- exec("sed -ri 's/^(<(\\/Container)/>\\n <\\1/' \"$file\""); // aftercare
- }
- }
- $reply = ['success' => (count($changes) ? implode(';',$changes) : '')];
- break;
-case 'is':
- // report changed isolcpus in temporary file
- foreach ($map as $name => $isolcpu) {
- file_put_contents("/var/tmp/$name.tmp",$isolcpu);
- $changes[] = $name;
- }
- $reply = ['success' => (count($changes) ? implode(';',$changes) : '')];
- break;
+switch ($data['id']) {
+ case 'vm':
+ /* report changed vms in temporary file */
+ require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
+ foreach ($map as $name => $cpuset) {
+ if (!strlen($cpuset)) {
+ $reply = ['error' => _("Not allowed to assign ZERO cores")];
+ break 2;
+ }
+ $uuid = $lv->domain_get_uuid($lv->get_domain_by_name($name));
+ $cfg = domain_to_config($uuid);
+ $cpus = implode(',', $cfg['domain']['vcpu']);
+ /* only act on changes */
+ if ($cpus != $cpuset || strlen($cpus) != strlen($cpuset)) {
+ $changes[] = $name;
+ /* used by UpdateTwo.php to read new assignments */
+ file_put_contents("/var/tmp/$name.tmp", $cpuset);
+ }
+ }
+ $reply = ['success' => (count($changes) ? implode(';', $changes) : '')];
+ break;
+ case 'ct':
+ /* update the XML file of the container */
+ require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
+ $DockerClient = new DockerClient();
+ $DockerTemplates = new DockerTemplates();
+ $containers = $DockerClient->getDockerContainers();
+ foreach ($map as $name => $cpuset) {
+ /* set full path of template file */
+ $file = $DockerTemplates->getUserTemplate($name);
+ $xml = simplexml_load_file($file);
+ if ($xml->CPUset) {
+ /* update node */
+ if ($xml->CPUset != $cpuset || strlen($xml->CPUset) != strlen($cpuset)) $xml->CPUset = $cpuset;
+ } else {
+ /* add node */
+ $xml->addChild('CPUset', $cpuset);
+ }
+ /* only act on changes */
+ foreach ($containers as $ct) if ($ct['Name'] == $name) break;
+ if ($ct['CPUset'] != $cpuset || strlen($ct['CPUset']) != strlen($cpuset)) {
+ $changes[] = $name;
+ /* used by UpdateTwo.php to read new assignments */
+ file_put_contents($file, $xml->saveXML());
+ exec("sed -ri 's/^(<(\\/Container)/>\\n <\\1/' \"$file\""); /* aftercare */
+ }
+ }
+ $reply = ['success' => (count($changes) ? implode(';', $changes) : '')];
+ break;
+ case 'is':
+ /* report changed isolcpus in temporary file */
+ foreach ($map as $name => $isolcpu) {
+ file_put_contents("/var/tmp/$name.tmp", $isolcpu);
+ $changes[] = $name;
+ }
+ $reply = ['success' => (count($changes) ? implode(';', $changes) : '')];
+ break;
}
-// signal changes
+/* signal changes */
header('Content-Type: application/json');
die(json_encode($reply));
?>
diff --git a/emhttp/plugins/dynamix/include/UpdateTwo.php b/emhttp/plugins/dynamix/include/UpdateTwo.php
index 9cb4e5047..0c3baf49e 100644
--- a/emhttp/plugins/dynamix/include/UpdateTwo.php
+++ b/emhttp/plugins/dynamix/include/UpdateTwo.php
@@ -1,4 +1,4 @@
- "File: '$file' not found"];
- break;
- }
- // read new cpu assignments
- $cpuset = explode(',',file_get_contents($file)); unlink($file);
- $vcpus = count($cpuset);
- // initial cores/threads assignment
- $cores = $vcpus;
- $threads = 1;
- $ht = exec("lscpu|grep -Po '^Thread\\(s\\) per core:\\s+\\K\\d+'") ?: 1; // fetch hyperthreading
+ /* Update VM */
+ require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
+
+ /* Path to the temporary file containing new CPU assignments */
+ $file = "/var/tmp/$name.tmp";
+ if (!file_exists($file)) {
+ $reply = ['error' => "File: '$file' not found"];
+ break;
+ }
+
+ /* Read new CPU assignments and delete the temporary file */
+ $cpuset = explode(',', file_get_contents($file));
+ unlink($file);
+ $vcpus = count($cpuset);
+
+ /* Initial cores/threads assignment */
+ $cores = $vcpus;
+ $threads = 1;
+ $ht = exec("lscpu|grep -Po '^Thread\\(s\\) per core:\\s+\\K\\d+'") ?: 1; /* Fetch hyperthreading */
+
+ /* Adjust for hyperthreading */
+ if ($vcpus > $ht && $vcpus % $ht === 0) {
+ $cores /= $ht;
+ $threads = $ht;
+ }
+
+ /* Get the UUID of the VM */
+ $uuid = $lv->domain_get_uuid($lv->get_domain_by_name($name));
+ $dom = $lv->domain_get_domain_by_uuid($uuid);
+ $auto = $lv->domain_get_autostart($dom) == 1;
+
+ /* Load the VM's XML configuration */
+ $xml = simplexml_load_string($lv->domain_get_xml($dom));
+
+ /* Update topology and vpcus in the XML configuration */
+ $xml->cpu->topology['cores'] = $cores;
+ $xml->cpu->topology['threads'] = $threads;
+ $xml->vcpu = $vcpus;
+
+ /* Preserve existing emulatorpin attributes */
+ $pin = [];
+ foreach ($xml->cputune->emulatorpin->attributes() as $key => $value) {
+ $pin[$key] = (string) $value;
+ }
+ unset($xml->cputune);
+
+ /* Add new cputune configuration */
+ $xml->addChild('cputune');
+ for ($i = 0; $i < $vcpus; $i++) {
+ $vcpu = $xml->cputune->addChild('vcpupin');
+ $vcpu['vcpu'] = $i;
+ $vcpu['cpuset'] = safe_get($cpuset, $i);
+ }
+ if ($pin) {
+ $attr = $xml->cputune->addChild('emulatorpin');
+ foreach ($pin as $key => $value) {
+ $attr[$key] = $value;
+ }
+ }
+
+ /* Stop the running VM first if it is running */
+ $running = $lv->domain_get_state($dom) == 'running';
+ if ($running) {
+ $lv->domain_shutdown($dom);
+ for ($n = 0; $n < 30; $n++) { /* Allow up to 30s for VM to shutdown */
+ sleep(1);
+ if ($stopped = $lv->domain_get_state($dom) == 'shutoff') {
+ break;
+ }
+ }
+ } else {
+ $stopped = true;
+ }
+
+ /* If the VM failed to stop, return an error */
+ if (!$stopped) {
+ $reply = ['error' => _('Failed to stop') . " '$name'"];
+ break;
+ }
+
+ /* Backup NVRAM, undefine the domain, and restore NVRAM */
+ $lv->nvram_backup($uuid);
+ $lv->domain_undefine($dom);
+ $lv->nvram_restore($uuid);
+
+ /* Define the domain with the updated XML configuration */
+ if (!$lv->domain_define($xml->saveXML())) {
+ $reply = ['error' => $lv->get_last_error()];
+ break;
+ }
+
+ /* Set autostart for the domain */
+ $lv->domain_set_autostart($dom, $auto);
+
+ /* If the VM was running before, start it again */
+ if ($running && !$lv->domain_start($dom)) {
+ $reply = ['error' => $lv->get_last_error()];
+ } else {
+ $reply = ['success' => $name];
+ }
+ break;
- // adjust for hyperthreading
- if ($vcpus > $ht && $vcpus%$ht===0) {
- $cores /= $ht;
- $threads = $ht;
- }
- $uuid = $lv->domain_get_uuid($lv->get_domain_by_name($name));
- $dom = $lv->domain_get_domain_by_uuid($uuid);
- $auto = $lv->domain_get_autostart($dom)==1;
- $xml = simplexml_load_string($lv->domain_get_xml($dom));
- // update topology and vpcus
- $xml->cpu->topology['cores'] = $cores;
- $xml->cpu->topology['threads'] = $threads;
- $xml->vcpu = $vcpus;
- $pin = []; foreach ($xml->cputune->emulatorpin->attributes() as $key => $value) $pin[$key] = (string)$value;
- unset($xml->cputune);
- $xml->addChild('cputune');
- for ($i = 0; $i < $vcpus; $i++) {
- $vcpu = $xml->cputune->addChild('vcpupin');
- $vcpu['vcpu'] = $i;
- $vcpu['cpuset'] = $cpuset[$i];
- }
- if ($pin) {
- $attr = $xml->cputune->addChild('emulatorpin');
- foreach ($pin as $key => $value) $attr[$key] = $value;
- }
- // stop running vm first?
- $running = $lv->domain_get_state($dom)=='running';
- if ($running) {
- $lv->domain_shutdown($dom);
- for ($n =0; $n < 30; $n++) { // allow up to 30s for VM to shutdown
- sleep(1);
- if ($stopped = $lv->domain_get_state($dom)=='shutoff') break;
- }
- } else $stopped = true;
- if (!$stopped) {
- $reply = ['error' => _('Failed to stop')." '$name'"];
- break;
- }
- $lv->nvram_backup($uuid);
- $lv->domain_undefine($dom);
- $lv->nvram_restore($uuid);
- if (!$lv->domain_define($xml->saveXML())) {
- $reply = ['error' => $lv->get_last_error()];
- break;
- }
- $lv->domain_set_autostart($dom, $auto);
- if ($running && !$lv->domain_start($dom)) {
- $reply = ['error' => $lv->get_last_error()];
- } else {
- $reply = ['success' => $name];
- }
- break;
case 'ct':
- // update docker container
- require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
- $DockerClient = new DockerClient();
- $DockerTemplates = new DockerTemplates();
- // get available networks
- $subnet = DockerUtil::network(DockerUtil::custom());
- // get full template path
- $xml = $DockerTemplates->getUserTemplate($name);
- list($cmd, $ct, $repository) = xmlToCommand($xml);
- $imageID = $DockerClient->getImageID($repository);
- // pull image
- $container = $DockerClient->getContainerDetails($ct);
- // determine if the container is still running
- if (!empty($container) && !empty($container['State']) && !empty($container['State']['Running'])) {
- // since container was already running, put it back it to a running state after update
- $cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
- // attempt graceful stop of container first
- $DockerClient->stopContainer($ct);
- }
- // force kill container if still running after time-out
- $DockerClient->removeContainer($ct);
- execCommand($cmd,false);
- $DockerClient->flushCaches();
- $newImageID = $DockerClient->getImageID($repository);
- // remove old orphan image since it's no longer used by this container
- if ($imageID && $imageID != $newImageID) {
- $DockerClient->removeImage($imageID);
- }
- $reply = ['success' => $name];
- break;
+ /* update docker container */
+ require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
+ $DockerClient = new DockerClient();
+ $DockerTemplates = new DockerTemplates();
+
+ /* get available networks */
+ $subnet = DockerUtil::network(DockerUtil::custom());
+
+ /* get full template path */
+ $xml = $DockerTemplates->getUserTemplate($name);
+ list($cmd, $ct, $repository) = xmlToCommand($xml);
+ $imageID = $DockerClient->getImageID($repository);
+
+ /* pull image */
+ $container = $DockerClient->getContainerDetails($ct);
+
+ /* determine if the container is still running */
+ if (!empty($container) && !empty($container['State']) && !empty($container['State']['Running'])) {
+ /* since container was already running, put it back it to a running state after update */
+ $cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
+
+ /* attempt graceful stop of container first */
+ $DockerClient->stopContainer($ct);
+ }
+
+ /* force kill container if still running after time-out */
+ $DockerClient->removeContainer($ct);
+ execCommand($cmd, false);
+ $DockerClient->flushCaches();
+ $newImageID = $DockerClient->getImageID($repository);
+
+ /* remove old orphan image since it's no longer used by this container */
+ if ($imageID && $imageID != $newImageID) {
+ $DockerClient->removeImage($imageID);
+ }
+ $reply = ['success' => $name];
+ break;
+
case 'is':
- $cfg = '/boot/syslinux/syslinux.cfg';
- $syslinux = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
- $size = count($syslinux);
- $make = false;
- $file = "/var/tmp/$name.tmp";
- $isolcpus = file_get_contents($file);
- if ($isolcpus != '') {
- $numbers = explode(',',$isolcpus);
- sort($numbers,SORT_NUMERIC);
- $isolcpus = $previous = array_shift($numbers);
- $range = false;
- // convert sequential numbers to a range
- foreach ($numbers as $number) {
- if ($number == $previous+1) {
- $range = true;
- } else {
- if ($range) {$isolcpus .= '-'.$previous; $range = false;}
- $isolcpus .= ','.$number;
- }
- $previous = $number;
- }
- if ($range) $isolcpus .= '-'.$previous;
- $isolcpus = "isolcpus=$isolcpus";
- }
- unlink($file);
- $i = 0;
- while ($i < $size) {
- // find sections and exclude safemode
- if (scan($syslinux[$i],'label ') && !scan($syslinux[$i],'safe mode') && !scan($syslinux[$i],'safemode')) {
- $n = $i + 1;
- // find the current requested setting
- while (!scan($syslinux[$n],'label ') && $n < $size) {
- if (scan($syslinux[$n],'append ')) {
- $cmd = preg_split('/\s+/',trim($syslinux[$n]));
- // replace an existing setting
- for ($c = 1; $c < count($cmd); $c++) if (scan($cmd[$c],'isolcpus')) {$make |= ($cmd[$c]!=$isolcpus); $cmd[$c] = $isolcpus; break;}
- // or insert a new setting
- if ($c==count($cmd) && $isolcpus) {array_splice($cmd,-1,0,$isolcpus); $make = true;}
- $syslinux[$n] = ' '.str_replace(' ',' ',implode(' ',$cmd));
- }
- $n++;
- }
- $i = $n - 1;
- }
- $i++;
- }
- if ($make) file_put_contents($cfg, implode("\n",$syslinux)."\n");
- $reply = ['success' => $name];
- break;
+ /* Path to syslinux configuration file */
+ $cfg = '/boot/syslinux/syslinux.cfg';
+
+ /* Read the syslinux configuration file into an array, ignoring empty lines */
+ $syslinux = file($cfg, FILE_IGNORE_NEW_LINES + FILE_SKIP_EMPTY_LINES);
+ $size = count($syslinux);
+ $make = false;
+
+ /* Path to the temporary file containing new isolcpus settings */
+ $file = "/var/tmp/$name.tmp";
+ $isolcpus = file_get_contents($file);
+
+ if ($isolcpus != '') {
+ /* Convert isolcpus string to an array of numbers and sort them */
+ $numbers = explode(',', $isolcpus);
+ sort($numbers, SORT_NUMERIC);
+
+ /* Initialize variables for range conversion */
+ $isolcpus = $previous = array_shift($numbers);
+ $range = false;
+
+ /* Convert sequential numbers to a range */
+ foreach ($numbers as $number) {
+ if ($number == $previous + 1) {
+ $range = true;
+ } else {
+ if ($range) {
+ $isolcpus .= '-' . $previous;
+ $range = false;
+ }
+ $isolcpus .= ',' . $number;
+ }
+ $previous = $number;
+ }
+ if ($range) {
+ $isolcpus .= '-' . $previous;
+ }
+
+ /* Format isolcpus string for syslinux configuration */
+ $isolcpus = "isolcpus=$isolcpus";
+ }
+
+ /* Remove the temporary file */
+ unlink($file);
+
+ $i = 0;
+ while ($i < $size) {
+ /* Find sections in syslinux config and exclude safemode */
+ if (scan($syslinux[$i], 'label ') && !scan($syslinux[$i], 'safe mode') && !scan($syslinux[$i], 'safemode')) {
+ $n = $i + 1;
+
+ /* Find the current requested setting */
+ while ($n < $size && !scan($syslinux[$n], 'label ')) {
+ if (scan($syslinux[$n], 'append ')) {
+ $cmd = preg_split('/\s+/', trim($syslinux[$n]));
+
+ /* Replace an existing isolcpus setting */
+ for ($c = 1; $c < count($cmd); $c++) {
+ if (scan($cmd[$c], 'isolcpus')) {
+ $make |= ($cmd[$c] != $isolcpus);
+ $cmd[$c] = $isolcpus;
+ break;
+ }
+ }
+
+ /* Or insert a new isolcpus setting if not found */
+ if ($c == count($cmd) && $isolcpus) {
+ array_splice($cmd, -1, 0, $isolcpus);
+ $make = true;
+ }
+
+ /* Update the syslinux configuration line */
+ $syslinux[$n] = ' ' . str_replace(' ', ' ', implode(' ', $cmd));
+ }
+ $n++;
+ }
+ $i = $n - 1;
+ }
+ $i++;
+ }
+
+ /* Write the updated syslinux configuration back to the file if changes were made */
+ if ($make) {
+ file_put_contents($cfg, implode("\n", $syslinux) . "\n");
+ }
+
+ $reply = ['success' => $name];
+ break;
}
header('Content-Type: application/json');
-die(json_encode($reply));
+echo json_encode($reply);
+exit;
?>
|---|