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 = "
',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 @@ - - $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; ?>