summaryrefslogtreecommitdiffstats
path: root/modules-available/vmstore/page.inc.php
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/vmstore/page.inc.php')
-rw-r--r--modules-available/vmstore/page.inc.php270
1 files changed, 264 insertions, 6 deletions
diff --git a/modules-available/vmstore/page.inc.php b/modules-available/vmstore/page.inc.php
index 1e0cc619..9d7f16c2 100644
--- a/modules-available/vmstore/page.inc.php
+++ b/modules-available/vmstore/page.inc.php
@@ -2,12 +2,28 @@
class Page_VmStore extends Page
{
- private $mountTask = false;
+ /**
+ * @var ?string
+ */
+ private $mountTask = null;
protected function doPreprocess()
{
User::load();
+ if (User::hasPermission('edit')) {
+ Dashboard::addSubmenu('?do=vmstore', Dictionary::translate('menu_edit'));
+ }
+ if (User::hasPermission('benchmark')) {
+ Dashboard::addSubmenu('?do=vmstore&show=benchmark', Dictionary::translate('menu_benchmark'));
+ }
+
+ if (Request::any('show') === 'benchmark') {
+ User::assertPermission('benchmark');
+ $this->benchmarkDoPreprocess();
+ return;
+ }
+
User::assertPermission('edit');
$action = Request::post('action');
@@ -19,10 +35,15 @@ class Page_VmStore extends Page
protected function doRender()
{
+ if (Request::any('show') === 'benchmark') {
+ $this->benchmarkDoRender();
+ return;
+ }
+
$action = Request::post('action');
- if ($action === 'setstore' && !Taskmanager::isFailed($this->mountTask)) {
+ if ($action === 'setstore' && !Taskmanager::isFailed(Taskmanager::status($this->mountTask))) {
Render::addTemplate('mount', array(
- 'task' => $this->mountTask['id']
+ 'task' => $this->mountTask
));
return;
}
@@ -50,19 +71,256 @@ class Page_VmStore extends Page
Util::redirect('?do=VmStore');
}
// Validate syntax of nfs/cifs
- if ($storetype === 'nfs' && !preg_match('#^\S+:\S+$#is', $vmstore['nfsaddr'])) {
+ if ($storetype === 'nfs' && !preg_match('#^\S+:\S+$#i', $vmstore['nfsaddr'])) {
Message::addError('main.value-invalid', 'nfsaddr', $vmstore['nfsaddr']);
Util::redirect('?do=VmStore');
}
$vmstore['cifsaddr'] = str_replace('\\', '/', $vmstore['cifsaddr']);
- if ($storetype === 'cifs' && !preg_match('#^//\S+/.+$#is', $vmstore['cifsaddr'])) {
+ if ($storetype === 'cifs' && !preg_match('#^//\S+/.+$#i', $vmstore['cifsaddr'])) {
Message::addError('main.value-invalid', 'nfsaddr', $vmstore['nfsaddr']);
Util::redirect('?do=VmStore');
}
$this->mountTask = Trigger::mount($vmstore);
- if ($this->mountTask !== false) {
+ if ($this->mountTask !== null) {
TaskmanagerCallback::addCallback($this->mountTask, 'manualMount', $vmstore);
}
}
+ private function benchmarkDoPreprocess()
+ {
+ if (!Module::isAvailable('rebootcontrol')) {
+ ErrorHandler::traceError('rebootcontrol module not enabled');
+ }
+ Render::setTitle(Dictionary::translate('page-title-benchmark'));
+ if (Request::post('action') === 'start') {
+ $this->benchmarkActionStart();
+ }
+ }
+
+ private function benchmarkDoRender()
+ {
+ switch (Request::get('action')) {
+ case 'select':
+ $this->benchmarkShowImageSelect();
+ break;
+ case 'result':
+ $this->benchmarkShowResult();
+ break;
+ default:
+ Render::addTemplate('benchmark-nothing');
+ }
+ }
+
+ private function getJobFromId(int $id): ?array
+ {
+ $data = Property::getListEntry(VmStoreBenchmark::PROP_LIST_KEY, $id);
+ if ($data !== null) {
+ $data = json_decode($data, true);
+ }
+ if (!is_array($data) || !isset($data['machines'])) {
+ Message::addError('invalid-benchmark-job', $id);
+ return null;
+ }
+ return $data;
+ }
+
+ private function benchmarkActionStart()
+ {
+ Module::isAvailable('dnbd3');
+ $id = Request::post('id', Request::REQUIRED, 'int');
+ $data = $this->getJobFromId($id);
+ if ($data === null)
+ return;
+ if (isset($data['task'])) {
+ if ($data['task'] === 'inprogress') {
+ // Let's hope the proper ID gets written in a short while
+ sleep(1);
+ }
+ Util::redirect('?do=vmstore&show=benchmark&action=result&id=' . $id);
+ }
+ $selectedServer = Request::post('server', 'auto', 'string');
+ if ($selectedServer === 'nfs' || !Dnbd3::isEnabled()) {
+ $selectedServer = 'nfs';
+ } elseif ($selectedServer !== 'auto') {
+ $ip = Dnbd3::getServer($selectedServer);
+ if ($ip === false) {
+ Message::addError('invalid-dnbd3-server-id', $selectedServer);
+ return;
+ }
+ $selectedServer = $ip['clientip'];
+ }
+ $data['image'] = Request::post('image', Request::REQUIRED, 'string');
+ // Save once first to minimize race window
+ $data['task'] = 'inprogress';
+ Property::updateListEntry(VmStoreBenchmark::PROP_LIST_KEY, $id, json_encode($data), 30);
+ $start = 0;
+ $data['task'] = VmStoreBenchmark::start($id, $data['machines'], $data['image'], $selectedServer, $start);
+ if ($data['task'] === null) {
+ $data['task'] = 'failed';
+ } else {
+ // Test is 2x 30 seconds
+ $data['expected'] = $start + 64;
+ }
+ error_log('Saving: ' . json_encode($data));
+ Property::updateListEntry(VmStoreBenchmark::PROP_LIST_KEY, $id, json_encode($data), 30);
+ Util::redirect('?do=vmstore&show=benchmark&action=result&id=' . $id);
+ }
+
+ private function benchmarkShowImageSelect()
+ {
+ $id = Request::get('id', Request::REQUIRED, 'int');
+ $data = $this->getJobFromId($id);
+ if ($data === null)
+ return;
+ if (isset($data['task'])) {
+ Message::addWarning('benchmark-already-started');
+ Util::redirect('?do=vmstore&show=benchmark&action=result&id=' . $id);
+ }
+ Module::isAvailable('dnbd3');
+ $lookup = Dnbd3::getActiveServers();
+ $list = Dnbd3Rpc::getStatsMulti(array_keys($lookup), [Dnbd3Rpc::QUERY_IMAGES]);
+ if (empty($list)) {
+ Message::addError('dnbd3-failed');
+ Util::redirect('?do=vmstore');
+ }
+ $images = [];
+ foreach ($list as $json) {
+ foreach ($json['images'] as $img) {
+ $name = $img['name'] . ':' . $img['rid'];
+ if (!isset($images[$name])) {
+ $images[$name] = [
+ 'users' => 0,
+ 'size' => $img['size'],
+ 'size_s' => Util::readableFileSize($img['size'], 1),
+ 'name' => $name,
+ 'id' => count($images)
+ ];
+ }
+ $images[$name]['users'] += $img['users'];
+ }
+ }
+ $servers = [];
+ if (Dnbd3::isEnabled()) {
+ $servers[] = ['idx' => 'auto',
+ 'server' => Dictionary::translate('dnbd3-all-loadbalance')];
+ foreach ($lookup as $ip => $idx) {
+ $servers[] = ['idx' => $idx, 'server' => $ip];
+ }
+ }
+ if (!Dnbd3::isEnabled() || Dnbd3::hasNfsFallback()) {
+ $servers[] = ['idx' => 'nfs', 'server' => 'NFS'];
+ }
+ $servers[0]['checked'] = 'checked';
+ ArrayUtil::sortByColumn($images, 'users', SORT_DESC, SORT_NUMERIC);
+ Module::isAvailable('js_stupidtable');
+ Render::addTemplate('benchmark-imgselect', [
+ 'id' => $id,
+ 'list' => array_values($images),
+ 'servers' => $servers,
+ ]);
+ }
+
+ private function benchmarkShowResult()
+ {
+ $id = Request::get('id', Request::REQUIRED, 'int');
+ $data = $this->getJobFromId($id);
+ if ($data === null)
+ return;
+ if (!isset($data['task'])) {
+ Message::addWarning('select-image-first');
+ Util::redirect('?do=vmstore&show=benchmark&action=select&id=' . $id);
+ }
+ if ($data['task'] === 'failed') {
+ Message::addError('benchmark-failed');
+ return;
+ }
+ $remaining = 0;
+ if ($data['task'] !== 'done') {
+ $remaining = ($data['expected'] ?? 0) - time();
+ if ($remaining < 0) {
+ $remaining = 0;
+ }
+ $this->processRunningBenchmark($id, $data, $remaining === 0);
+ $refresh = $remaining;
+ Util::clamp($refresh, 2, 64);
+ }
+ $args = [
+ 'id' => $id,
+ 'result' => json_encode($data['result'] ?? []),
+ 'wanted' => json_encode($data['machines']),
+ ];
+ if ($remaining > 0) {
+ $args['remaining'] = $remaining;
+ $args['refresh'] = $refresh ?? 60;
+ }
+ Module::isAvailable('js_chart');
+ Render::addTemplate('benchmark-result', $args);
+ }
+
+ private function processRunningBenchmark(int $id, array &$data, bool $timeout)
+ {
+ Module::isAvailable('rebootcontrol');
+ $changed = false;
+
+ $active = array_filter($data['machines'], function ($e) use ($data) { return !isset($data['result'][$e]); });
+ if (empty($active)) {
+ $timeout = true;
+ } else {
+ if ($timeout) {
+ // cat everything for easier troubleshooting
+ $command = <<<EOF
+cat "/tmp/speedcheck-$id"
+EOF;
+ } else {
+ $command = <<<EOF
+grep -q '^Seq:' "/tmp/speedcheck-$id" && cat "/tmp/speedcheck-$id"
+EOF;
+ }
+ $task = RebootControl::runScript($active, $command);
+ $task = Taskmanager::waitComplete($task, 4000);
+ if ($task === false) {
+ $data['task'] = 'failed';
+ return;
+ }
+ if (!isset($data['result'])) {
+ $data['result'] = [];
+ }
+ $res =& $task['data'];
+ foreach ($res['result'] as $uuid => $out) {
+ if (isset($data['result'][$uuid]))
+ continue;
+ error_log(json_encode($out));
+ // Not finished, ignore
+ if (($out['state'] !== 'DONE' || $out['exitCode'] !== 0) && !$timeout)
+ continue;
+ $changed = true;
+ unset($client);
+ $client = ['machineuuid' => $uuid];
+ $data['result'][$uuid] =& $client;
+ if (preg_match_all("/^\+(\w{3}):(\d+),(.*)$/m", $out['stdout'], $modes, PREG_SET_ORDER)) {
+ foreach ($modes as $mode) {
+ $client[$mode[1]] = [
+ 'start' => $mode[2],
+ 'values' => VmStoreBenchmark::parseBenchLine($mode[3]),
+ ];
+ }
+ } else {
+ $client['stderr'] = substr($out['stderr'], 0, 4000)
+ . "\nStatus: {$out['state']}, ExitCode: {$out['exitCode']}";
+ $client['stdout'] = substr($out['stdout'], 0, 4000);
+ }
+ $m = Database::queryFirst('SELECT clientip, hostname FROM machine WHERE machineuuid = :uuid',
+ ['uuid' => $uuid]);
+ $client['name'] = empty($m['hostname']) ? $m['clientip'] : $m['hostname'];
+ }
+ }
+ if (count($data['result']) === count($data['machines']) || $timeout) {
+ $data['task'] = 'done';
+ $changed = true;
+ }
+ if ($changed) {
+ Property::updateListEntry(VmStoreBenchmark::PROP_LIST_KEY, $id, json_encode($data), 30);
+ }
+ }
+
} \ No newline at end of file