summaryrefslogblamecommitdiffstats
path: root/modules-available/vmstore/page.inc.php
blob: 9d7f16c283ba2a47c7602ba1ffd4e953c69de7bb (plain) (tree)
1
2
3
4
5
6
7
8



                               
           
                       
           
                                  




                                         
                                                  
                                                                                                 

                                                       
                                                                                                                     







                                                            
                                               









                                                  




                                                           
                                                  
                                                                                                              
                                                           
                                                          






                                                                             




                                                              
                                   
                                                                                                                                                            
                                                                                 


                                                                                                
                                                                                                                           
                         


                                                                              
                                                                                    


                                                      
                                                                                               
                                                                                                


                                                                                    
                                                                                                  
                                                                                                

                                                      
                                                            
                                                

                                                                                                    

         




                                                                                     
                                                                                












                                                          


                                                                 


                 
                                                      











                                                                                     


                                               


                                                                    
                               






                                                                                             
                                                                            
                                                                       








                                                                                              


                                                                                     
                                                                                                        
                           
                                                                                                                         



                                                 
                                                        

                                                           
                                                                                                        




                                                                                     


                                                                   
                               

















                                                                                               

                                                                                                    






                                                                         


                                                      
                                                                                            







                                                                              
                                                                                   





                                                            



                                              


                                                                   
                               









                                                                                             
                                                                       













                                                                                     
                                                          




                                                               
                                                                                      







                                                                                                                             






                                                                            

                                                                  
                         



























                                                                                                                          

                                                                                           
                                                                                                            
                                                                                            










                                                                                                                             
                                                                                                                


                 
 
<?php

class Page_VmStore extends Page
{
	/**
	 * @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');
		
		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 = <<<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);
		}
	}

}