summaryrefslogtreecommitdiffstats
path: root/modules-available/rebootcontrol/inc/rebootcontrol.inc.php
blob: 8a85e3ffc1056f785e36e5f0603c26d2e6e694dd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
<?php

class RebootControl
{

	const KEY_TASKLIST = 'rebootcontrol.tasklist';

	const REBOOT = 'REBOOT';
	const KEXEC_REBOOT = 'KEXEC_REBOOT';
	const SHUTDOWN = 'SHUTDOWN';

	/**
	 * @param string[] $uuids List of machineuuids to reboot
	 * @param bool $kexec whether to trigger kexec-reboot instead of full BIOS cycle
	 * @return false|array task struct for the reboot job
	 */
	public static function reboot($uuids, $kexec = false)
	{
		$list = RebootQueries::getMachinesByUuid($uuids);
		if (empty($list))
			return false;
		return self::execute($list, $kexec ? RebootControl::KEXEC_REBOOT : RebootControl::REBOOT, 0, 0);
	}

	/**
	 * @param array $list list of clients containing each keys 'machineuuid' and 'clientip'
	 * @param string $mode reboot mode: RebootControl::REBOOT ::KEXEC_REBOOT or ::SHUTDOWN
	 * @param int $minutes delay in minutes for action
	 * @param int $locationId meta data only: locationId of clients
	 * @return array|false the task, or false if it could not be started
	 */
	public static function execute($list, $mode, $minutes, $locationId)
	{
		$task = Taskmanager::submit("RemoteReboot", array(
			"clients" => $list,
			"mode" => $mode,
			"minutes" => $minutes,
			"locationId" => $locationId,
			"sshkey" => SSHKey::getPrivateKey(),
			"port" => 9922, // Hard-coded, must match mgmt-sshd module
		));
		if (!Taskmanager::isFailed($task)) {
			Property::addToList(RebootControl::KEY_TASKLIST, $locationId . '/' . $task["id"], 60 * 24);
		}
		return $task;
	}

	/**
	 * @param int[]|null $locations filter by these locations
	 * @return array list of active tasks for reboots/shutdowns.
	 */
	public static function getActiveTasks($locations = null)
	{
		if (is_array($locations) && in_array(0,$locations)) {
			$locations = null;
		}
		$list = Property::getList(RebootControl::KEY_TASKLIST);
		$return = [];
		foreach ($list as $entry) {
			$p = explode('/', $entry, 2);
			if (count($p) !== 2) {
				Property::removeFromList(RebootControl::KEY_TASKLIST, $entry);
				continue;
			}
			if (is_array($locations) && !in_array($p[0], $locations)) // Ignore
				continue;
			$id = $p[1];
			$task = Taskmanager::status($id);
			if (!Taskmanager::isTask($task)) {
				Property::removeFromList(RebootControl::KEY_TASKLIST, $entry);
				continue;
			}
			$return[] = [
				'taskId' => $task['id'],
				'locationId' => $task['data']['locationId'],
				'time' => $task['data']['time'],
				'mode' => $task['data']['mode'],
				'clientCount' => count($task['data']['clients']),
				'status' => $task['statusCode'],
			];
		}
		return $return;
	}

	/**
	 * Execute given command or script on a list of hosts. The list of hosts is an array of structs containing
	 * each a known machine-uuid and/or hostname, and optionally a port to use, which would otherwise default to 9922,
	 * and optionally a username to use, which would default to root.
	 * The command should be compatible with the remote user's default shell (most likely bash).
	 *
	 * @param array $clients [ { clientip: <host>, machineuuid: <uuid>, port: <port>, username: <username> }, ... ]
	 * @param string $command Command or script to execute on client
	 * @param int $timeout in seconds
	 * @param string|false $privkey SSH private key to use to connect
	 * @return array|false
	 */
	public static function runScript($clients, $command, $timeout = 5, $privkey = false)
	{
		$valid = [];
		$invalid = [];
		foreach ($clients as $client) {
			if (is_string($client)) {
				$invalid[strtoupper($client)] = []; // Assume machineuuid
			} elseif (!isset($client['clientip']) && !isset($client['machineuuid'])) {
				error_log('RebootControl::runScript called with list entry that has neither IP nor UUID');
			} elseif (!isset($client['clientip'])) {
				$invalid[$client['machineuuid']] = $client;
			} else {
				$valid[] = $client;
			}
		}
		if (!empty($invalid)) {
			$res = Database::simpleQuery('SELECT machineuuid, clientip FROM machine WHERE machineuuid IN (:uuids)',
				['uuids' => array_keys($invalid)]);
			while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
				if (isset($invalid[$row['machineuuid']])) {
					$valid[] = $row + $invalid[$row['machineuuid']];
				} else {
					$valid[] = $row;
				}
			}
		}
		if ($privkey === false) {
			$privkey = SSHKey::getPrivateKey();
		}
		$task = Taskmanager::submit('RemoteExec', [
			'clients' => $valid,
			'command' => $command,
			'timeoutSeconds' => $timeout,
			'sshkey' => $privkey,
			'port' => 9922, // Fallback if no port given in client struct
		]);
		if (!Taskmanager::isFailed($task)) {
			Property::addToList(RebootControl::KEY_TASKLIST, '0/' . $task["id"], 60 * 24);
		}
		return $task;
	}

	public static function connectionCheckCallback($task, $hostId)
	{
		$reachable = 0;
		if (isset($task['data']['result'])) {
			foreach ($task['data']['result'] as $res) {
				if ($res['exitCode'] == 0) {
					$reachable = 1;
				}
			}
		}
		Database::exec('UPDATE reboot_jumphost SET reachable = :reachable WHERE hostid = :id',
			['id' => $hostId, 'reachable' => $reachable]);
	}

}