<?php
declare(strict_types=1);
/**
* This is one giant class containing various functions that will generate
* required config files, daemon instances and more, mostly through the Taskmanager.
* Most function *should* only actually do something if it is required to do so.
* eg. a "launchSomething" function should only launch something if it isn't already
* running. Checking if something is running can happen in that very function, or in
* a task that the function is calling.
*/
class Trigger
{
/**
* Compile iPXE pxelinux menu. Needs to be done whenever the server's IP
* address changes.
*
* @return ?string null if launching task failed, task-id otherwise
*/
public static function ipxe(string $taskId = null): ?string
{
static $lastResult = null;
if ($lastResult !== null)
return $lastResult;
$hooks = Hook::load('ipxe-update');
foreach ($hooks as $hook) {
$ret = function($taskId) use ($hook) {
$ret = include_once($hook->file);
if (is_string($ret))
return $ret;
return $taskId;
};
$ret = $ret($taskId);
if (is_string($ret)) {
$taskId = $ret;
} elseif (is_array($ret) && isset($ret['id'])) {
$taskId = $ret['id'];
}
}
$lastResult = $taskId;
return $taskId;
}
/**
* Try to automatically determine the primary IP address of the server.
* This only works if the server has either one public IPv4 address (and potentially
* one or more non-public addresses), or one private address.
*
* @return boolean true if current configured IP address is still valid, or if a new address could
* successfully be determined, false otherwise
*/
public static function autoUpdateServerIp(): bool
{
for ($i = 0; $i < 5; ++$i) {
$task = Taskmanager::submit('LocalAddressesList');
if ($task !== false)
break;
sleep(1);
}
if ($task === false)
return false;
$task = Taskmanager::waitComplete($task, 10000);
if (empty($task['data']['addresses']))
return false;
$serverIp = Property::getServerIp();
$publicCandidate = 'none';
$privateCandidate = 'none';
foreach ($task['data']['addresses'] as $addr) {
if (substr($addr['ip'], 0, 4) === '127.' || preg_match('/^\d+\.\d+\.\d+\.\d+$/', $addr['ip']) !== 1)
continue;
if ($addr['ip'] === $serverIp)
return true;
if (Util::isPublicIpv4($addr['ip'])) {
if ($publicCandidate === 'none')
$publicCandidate = $addr['ip'];
else
$publicCandidate = 'many';
} else {
if ($privateCandidate === 'none')
$privateCandidate = $addr['ip'];
else
$privateCandidate = 'many';
}
}
if ($publicCandidate !== 'none' && $publicCandidate !== 'many') {
Property::setServerIp($publicCandidate, true);
return true;
}
if ($privateCandidate !== 'none' && $privateCandidate !== 'many') {
Property::setServerIp($privateCandidate, true);
return true;
}
return false;
}
/**
* Mount the VM store into the server.
*
* @param array|false $vmstore VM Store configuration to use. If false, read from properties
* @param bool $ifLocalOnly Only execute task if the storage type is local (used for DNBD3)
* @return ?string task id of mount procedure, or false on error
*/
public static function mount($vmstore = false, bool $ifLocalOnly = false): ?string
{
if ($vmstore === false) {
$vmstore = Property::getVmStoreConfig();
}
if (!is_array($vmstore))
return null;
$storetype = $vmstore['storetype'] ?? 'unknown';
if ($storetype === 'nfs') {
$addr = $vmstore['nfsaddr'];
$opts = 'nfsopts';
} elseif ($storetype === 'cifs') {
$addr = $vmstore['cifsaddr'];
$opts = 'cifsopts';
} else {
$opts = null;
$addr = 'null';
}
// Bail out if storage is not local, and we only want to run it in that case
if ($ifLocalOnly && $addr !== 'null')
return null;
$opts = $vmstore[$opts] ?? null;
$status = Taskmanager::submit('MountVmStore', array(
'address' => $addr,
'type' => 'images',
'opts' => $opts,
'localNfs' => !Module::isAvailable('dnbd3') || !Dnbd3::isEnabled() || Dnbd3::hasNfsFallback(),
'username' => $vmstore['cifsuser'],
'password' => $vmstore['cifspasswd']
));
if (!Taskmanager::isFailed($status)) {
// In case we have a concurrent active task, this should be enough
// for the taskmanager to give us the existing id
$status = Taskmanager::waitComplete($status, 100);
}
return $status['data']['existingTask'] ?? $status['id'] ?? null;
}
/**
* Check and process all callbacks.
*
* @return boolean Whether there are still callbacks pending
*/
public static function checkCallbacks(): bool
{
$tasksLeft = false;
$callbackList = TaskmanagerCallback::getPendingCallbacks();
foreach ($callbackList as $taskid => $callbacks) {
$status = Taskmanager::status($taskid);
if ($status === false)
continue;
foreach ($callbacks as $callback) {
TaskmanagerCallback::handleCallback($callback, $status);
}
if (Taskmanager::isFailed($status) || Taskmanager::isFinished($status)) {
Taskmanager::release($status);
} else {
$tasksLeft = true;
}
}
return $tasksLeft;
}
private static function triggerDaemons(string $action, ?string $parent, array &$taskids): ?string
{
$task = Taskmanager::submit('Systemctl', array(
'operation' => $action,
'service' => 'dmsd',
'parentTask' => $parent,
'failOnParentFail' => false
));
if (isset($task['id'])) {
$taskids['dmsdid'] = $task['id'];
$parent = $task['id'];
}
$task = Taskmanager::submit('Systemctl', array(
'operation' => $action,
'service' => 'dnbd3-server',
'parentTask' => $parent,
'failOnParentFail' => false
));
if (isset($task['id'])) {
$taskids['dnbd3id'] = $task['id'];
$parent = $task['id'];
}
return $parent;
}
/**
* Stop any daemons that might be sitting on the VMstore, or database.
*/
public static function stopDaemons(?string $parent, array &$taskids): ?string
{
$parent = self::triggerDaemons('stop', $parent, $taskids);
$task = Taskmanager::submit('LdadpLauncher', array(
'ids' => array(),
'parentTask' => $parent,
'failOnParentFail' => false
));
if (isset($task['id'])) {
$taskids['ldadpid'] = $task['id'];
$parent = $task['id'];
}
return $parent;
}
}