summaryrefslogblamecommitdiffstats
path: root/modules-available/dnbd3/inc/dnbd3util.inc.php
blob: 33581b778042a0797ef761e88489bed3f1f632ea (plain) (tree)
1
2
3
4
5
6
7
8






                                                                                 
                                                       




                                                                                                                           
                                                        
                                                      

                                                               


                                                 
                                                            
                                                                        




                                                                                                                     


                                                                                                  












                                                                                                                     
                                                                                                                              


                                                                                                                        



                                                                                                                 


                                               



















                                                                                                                                                      






                                                                                                                                                                        
                                                                                                                                                         






                                                                              


                                                                           



                           








                                                                                          
                                                




                                                                                   



                                                              
                                                                                               
                                                                                                     








                                                                                                                         
                                                                               










                                                                                                    

                                                       
                                                   












                                                                                                                       
                                       
                                                              
                                                                                   







                                                                       
                                                    



                                                    
                                         


                                                                                     
                                          

                                                                                       



                                                                  
                                                          



                                                                     
                                                                          












                                                                            




                                                                             
                                 
                                               
                                          
                                                     

         





























                                                                                                                                                                   







                                                     
 
<?php

class Dnbd3Util {

	public static function updateServerStatus()
	{
		$dynClients = RunMode::getForMode('dnbd3', 'proxy', false, true);
		$satServerIp = Property::getServerIp();
		$servers = array();
		$res = Database::simpleQuery('SELECT s.serverid, s.machineuuid, s.fixedip, s.lastup, s.lastdown, m.clientip
			FROM dnbd3_server s
			LEFT JOIN machine m USING (machineuuid)');
		while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
			if (!is_null($row['fixedip'])) {
				$ip = $row['fixedip'];
			} elseif (!is_null($row['clientip'])) {
				$ip = $row['clientip'];
			} else {
				continue; // Huh?
			}
			if (!is_null($row['machineuuid'])) {
				unset($dynClients[$row['machineuuid']]);
				if ($row['clientip'] === $satServerIp) {
					// Lolwut, sat server is openslx client configured for proxy mode!? baleeted.
					RunMode::setRunMode($row['machineuuid'], 'dnbd3', null, null, null);
					continue;
				}
			} elseif ($ip === $satServerIp) {
				// Manually configured sat server as dnbd3 server - makes no sense
				continue;
			}
			$server = array(
				'serverid' => $row['serverid'],
				'addr' => $ip,
			);
			$servers[] = $server;
		}
		// See if any clients are in dnbd3 proxy mode but don't have a matching row in the dnbd3_server table
		foreach ($dynClients as $client) {
			Database::exec('INSERT IGNORE INTO dnbd3_server (machineuuid) VALUES (:machineuuid)',
				array('machineuuid' => $client['machineuuid']));
			// Missing from $servers now but we'll handle them in the next run, so don't bother
		}
		// Same for this server - we use the special fixedip '<self>' for it and need to make surecx we don't have the
		// IP address of the server itself in the list.
		Database::exec('DELETE FROM dnbd3_server WHERE fixedip = :serverip', array('serverip' => $satServerIp));
		Database::exec("INSERT IGNORE INTO dnbd3_server (fixedip) VALUES ('<self>')");
		// Delete orphaned entires with machineuuid from dnbd3_server where we don't have a runmode entry
		Database::exec('DELETE s FROM dnbd3_server s
				LEFT JOIN runmode r USING (machineuuid)
				WHERE s.machineuuid IS NOT NULL AND r.module IS NULL');
		// Now query them all
		$NOW = time();
		foreach ($servers as $server) {
			$port = 5003;
			$data = Dnbd3Rpc::query($server['addr'], $port, true, false, false, true);
			if ($data === Dnbd3Rpc::QUERY_UNREACHABLE) {
				$error = 'No (HTTP) reply on port ' . $port;
			} elseif ($data === Dnbd3Rpc::QUERY_NOT_200) {
				$error = 'No HTTP 200 OK on port ' . $port;
			} elseif ($data === Dnbd3Rpc::QUERY_NOT_JSON) {
				$error = 'Reply to status query is not JSON';
			} elseif (!is_array($data) || !isset($data['runId'])) {
				if (is_array($data) && isset($data['errorMsg'])) {
					$error = 'DNBD3: ' . $data['errorMsg'];
				} else {
					$error = 'Reply to status query has unexpected format';
				}
			} else {
				$error = false;
			}
			if ($error !== false) {
				Database::exec('UPDATE dnbd3_server SET uptime = 0, clientcount = 0, errormsg = :errormsg WHERE serverid = :serverid',
					array('serverid' => $server['serverid'], 'errormsg' => $error));
				continue;
			}
			// Seems up - since we only get absolute rx/tx values from the server, we have to prevent update race conditions
			// and make sure the server was not restarted in the meantime (use runid and uptime for this)
			Database::exec('UPDATE dnbd3_server SET runid = :runid, lastseen = :now, uptime = :uptime,
				totalup = totalup + If(runid = :runid AND uptime <= :uptime, If(lastup < :up, :up - lastup, 0), If(:uptime < 1800, :up, 0)),
				totaldown = totaldown + If(runid = :runid AND uptime <= :uptime, If(lastdown < :down, :down - lastdown, 0), If(:uptime < 1800, :up, 0)),
				lastup = :up, lastdown = :down, clientcount = :clientcount, disktotal = :disktotal, diskfree = :diskfree, errormsg = NULL
				WHERE serverid = :serverid', array(
					'runid' => $data['runId'],
					'now' => $NOW,
					'uptime' => $data['uptime'],
					'up' => $data['bytesSent'],
					'down' => $data['bytesReceived'],
					'clientcount' => $data['clientCount'],
					'serverid' => $server['serverid'],
					'disktotal' => $data['spaceTotal'],
					'diskfree' => $data['spaceFree'],
			));
		}
	}

	/**
	 * A client is booting that has runmode dnbd3 proxy - set config vars accordingly.
	 *
	 * @param string $machineUuid
	 * @param string $mode always 'proxy'
	 * @param string $modeData
	 */
	public static function runmodeConfigHook($machineUuid, $mode, $modeData)
	{
		$self = Property::getServerIp();
		// Get all directly assigned locations
		$res = Database::simpleQuery('SELECT locationid FROM dnbd3_server
				INNER JOIN dnbd3_server_x_location USING (serverid)
				WHERE machineuuid = :uuid',
			array('uuid' => $machineUuid));
		$assignedLocs = array();
		while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
			$assignedLocs[] = $row['locationid'];
		}
		$modeData = (array)json_decode($modeData, true) + self::defaultRunmodeConfig();
		if (!empty($assignedLocs) && isset($modeData['firewall']) && $modeData['firewall']) {
			// Get all sub-locations too
			$recursiveLocs = $assignedLocs;
			$locations = Location::getLocationsAssoc();
			foreach ($assignedLocs as $l) {
				if (isset($locations[$l])) {
					$recursiveLocs = array_merge($recursiveLocs, $locations[$l]['children']);
				}
			}
			$res = Database::simpleQuery('SELECT startaddr, endaddr FROM subnet WHERE locationid IN (:locs)',
				array('locs' => array_values($recursiveLocs)));
			// Coalesce overlapping ranges
			$floatIp = ip2long($self); // Float for 32bit php :/
			if (PHP_INT_SIZE === 4) {
				$floatIp = (float)sprintf('%u', $floatIp); // Float for 32bit php :/
			}
			$ranges = [['startaddr' => $floatIp, 'endaddr' => $floatIp]];
			while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
				settype($row['startaddr'], PHP_INT_SIZE === 4 ? 'float' : 'int');
				settype($row['endaddr'], PHP_INT_SIZE === 4 ? 'float' : 'int');
				self::mergeRanges($ranges, $row);
			}
			// Got subnets, build whitelist
			$opt = '';
			foreach ($ranges as $row) {
				$opt .= ' ' . self::range2Cidr($row['startaddr'], $row['endaddr']);
			}
			if (!empty($opt)) {
				ConfigHolder::add('SLX_DNBD3_WHITELIST', $opt, 1000);
			}
		}
		// Send list of other proxy servers
		$res = Database::simpleQuery('SELECT s.fixedip, m.clientip, sxl.locationid FROM dnbd3_server s
				LEFT JOIN machine m USING (machineuuid)
				LEFT JOIN dnbd3_server_x_location sxl USING (serverid)
				WHERE s.machineuuid <> :uuid OR s.machineuuid IS NULL', array('uuid' => $machineUuid));
		$public = array();
		$private = array();
		$public[$self] = $self;
		while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
			$ip = $row['fixedip'] ? $row['fixedip'] : $row['clientip'];
			if ($ip === '<self>') {
				continue;
			}
			if (is_null($row['locationid'])) {
				if (!array_key_exists($ip, $private)) {
					$public[$ip] = $ip;
				}
			} else {
				unset($public[$ip]);
				$private[$ip] = $ip;
			}
		}
		if (!empty($public)) {
			shuffle($public);
			ConfigHolder::add('SLX_DNBD3_PUBLIC', implode(' ', $public));
		}
		if (!empty($private)) {
			shuffle($private);
			ConfigHolder::add('SLX_DNBD3_PRIVATE', implode(' ', $private));
		}
		if (isset($modeData['bgr']) && $modeData['bgr']) {
			// Background replication
			ConfigHolder::add('SLX_DNBD3_BGR', '1');
		}
		ConfigHolder::add('SLX_ADDONS', '', 1000);
		ConfigHolder::add('SLX_SHUTDOWN_TIMEOUT', '', 1000);
		ConfigHolder::add('SLX_SHUTDOWN_SCHEDULE', '', 1000);
		ConfigHolder::add('SLX_REBOOT_TIMEOUT', '', 1000);
		ConfigHolder::add('SLX_REBOOT_SCHEDULE', '', 1000);
		ConfigHolder::add('SLX_SYSTEM_STANDBY_TIMEOUT', '', 1000);
	}

	/**
	 * Get smallest subnet in CIDR notation that covers the given range.
	 * The subnet denoted by the CIDR notation might actually be larger
	 * than the range described by $start and $end.
	 *
	 * @param int $start start address
	 * @param int $end end address
	 * @return string CIDR notation
	 */
	private static function range2Cidr($start, $end)
	{
		if (PHP_INT_SIZE > 4) {
			$bin = decbin((int)$start ^ (int)$end);
		} else {
			$bin = decbin((int)(float)$start ^ (int)(float)$end);
		}
		if ($bin === '0')
			return long2ip($start);
		$mask = 32 - strlen($bin);
		return long2ip($start) . '/' . $mask;
	}

	private static function mergeRanges(&$ranges, $row)
	{
		if ($row['startaddr'] >= $row['endaddr'])
			return; // Don't even bother
		foreach (array_keys($ranges) as $key) {
			if ($row['startaddr'] <= $ranges[$key]['startaddr'] && $row['endaddr'] >= $ranges[$key]['endaddr']) {
				// Fully dominated
				unset($ranges[$key]);
				continue; // Might partially overlap with additional ranges, keep going
			}
			if ($ranges[$key]['startaddr'] <= $row['startaddr'] && $ranges[$key]['endaddr'] >= $row['startaddr']) {
				// $row['startaddr'] lies within existing range
				if ($ranges[$key]['startaddr'] <= $row['endaddr'] && $ranges[$key]['endaddr'] >= $row['endaddr'])
					return; // Fully in existing range, do nothing
				// $row['endaddr'] seems to extend range we're checking against but $row['startaddr'] lies within this range, update and keep going
				$row['startaddr'] = $ranges[$key]['startaddr'];
				unset($ranges[$key]);
				continue;
			}
			// Last possibility: $row['startaddr'] is before range, $row['endaddr'] within range
			if ($ranges[$key]['startaddr'] <= $row['endaddr'] && $ranges[$key]['endaddr'] >= $row['endaddr']) {
				// $row['startaddr'] must lie before range start, otherwise we'd have hit the case above
				$row['endaddr'] = $ranges[$key]['endaddr'];
				unset($ranges[$key]);
				continue;
			}
		}
		$ranges[] = $row;
	}

	public static function defaultRunmodeConfig()
	{
		return array(
			'bgr' => true,
			'firewall' => false
		);
	}

}