benchmarkDoPreprocess(); return; } User::assertPermission('edit'); $action = Request::post('action'); if ($action === 'setstore') { $this->setStore(); } } protected function doRender() { if (Request::any('show') === 'benchmark') { $this->benchmarkDoRender(); return; } $action = Request::post('action'); if ($action === 'setstore' && !Taskmanager::isFailed(Taskmanager::status($this->mountTask))) { Render::addTemplate('mount', array( 'task' => $this->mountTask )); return; } $vmstore = Property::getVmStoreConfig(); if (isset($vmstore['storetype'])) { $vmstore['pre-' . $vmstore['storetype']] = 'checked'; } Render::addTemplate('page-vmstore', $vmstore); } private function setStore() { $vmstore = array(); foreach (array('storetype', 'nfsaddr', 'nfsopts', 'cifsaddr', 'cifsuser', 'cifspasswd', 'cifsuserro', 'cifspasswdro', 'cifsopts') as $key) { $vmstore[$key] = trim(Request::post($key, '', 'string')); // Remove rw setting if ($key === 'cifsopts' || $key === 'nfsopts') { $vmstore[$key] = preg_replace('/\s+,\s+/', ',', $vmstore[$key]); $vmstore[$key] = preg_replace('/^rw,|,rw$/', '', str_replace(',rw,', ',', $vmstore[$key])); } } $storetype = $vmstore['storetype']; if (!in_array($storetype, array('internal', 'nfs', 'cifs'))) { Message::addError('main.value-invalid', 'type', $storetype); Util::redirect('?do=VmStore'); } // Validate syntax of nfs/cifs 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+/.+$#i', $vmstore['cifsaddr'])) { Message::addError('main.value-invalid', 'nfsaddr', $vmstore['nfsaddr']); Util::redirect('?do=VmStore'); } $this->mountTask = Trigger::mount($vmstore); 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 = << $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); } } }