Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/config/check/PhabricatorSetupCheck.php
12256 views
1
<?php
2
3
abstract class PhabricatorSetupCheck extends Phobject {
4
5
private $issues;
6
7
abstract protected function executeChecks();
8
9
const GROUP_OTHER = 'other';
10
const GROUP_MYSQL = 'mysql';
11
const GROUP_PHP = 'php';
12
const GROUP_IMPORTANT = 'important';
13
14
public function getExecutionOrder() {
15
if ($this->isPreflightCheck()) {
16
return 0;
17
} else {
18
return 1000;
19
}
20
}
21
22
/**
23
* Should this check execute before we load configuration?
24
*
25
* The majority of checks (particularly, those checks which examine
26
* configuration) should run in the normal setup phase, after configuration
27
* loads. However, a small set of critical checks (mostly, tests for PHP
28
* setup and extensions) need to run before we can load configuration.
29
*
30
* @return bool True to execute before configuration is loaded.
31
*/
32
public function isPreflightCheck() {
33
return false;
34
}
35
36
final protected function newIssue($key) {
37
$issue = id(new PhabricatorSetupIssue())
38
->setIssueKey($key);
39
$this->issues[$key] = $issue;
40
41
if ($this->getDefaultGroup()) {
42
$issue->setGroup($this->getDefaultGroup());
43
}
44
45
return $issue;
46
}
47
48
final public function getIssues() {
49
return $this->issues;
50
}
51
52
protected function addIssue(PhabricatorSetupIssue $issue) {
53
$this->issues[$issue->getIssueKey()] = $issue;
54
return $this;
55
}
56
57
public function getDefaultGroup() {
58
return null;
59
}
60
61
final public function runSetupChecks() {
62
$this->issues = array();
63
$this->executeChecks();
64
}
65
66
final public static function getOpenSetupIssueKeys() {
67
$cache = PhabricatorCaches::getSetupCache();
68
return $cache->getKey('phabricator.setup.issue-keys');
69
}
70
71
final public static function resetSetupState() {
72
$cache = PhabricatorCaches::getSetupCache();
73
$cache->deleteKey('phabricator.setup.issue-keys');
74
75
$server_cache = PhabricatorCaches::getServerStateCache();
76
$server_cache->deleteKey('phabricator.in-flight');
77
78
$use_scope = AphrontWriteGuard::isGuardActive();
79
if ($use_scope) {
80
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
81
} else {
82
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
83
}
84
85
try {
86
$db_cache = new PhabricatorKeyValueDatabaseCache();
87
$db_cache->deleteKey('phabricator.setup.issue-keys');
88
} catch (Exception $ex) {
89
// If we hit an exception here, just ignore it. In particular, this can
90
// happen on initial startup before the databases are initialized.
91
}
92
93
if ($use_scope) {
94
unset($unguarded);
95
} else {
96
AphrontWriteGuard::allowDangerousUnguardedWrites(false);
97
}
98
}
99
100
final public static function setOpenSetupIssueKeys(
101
array $keys,
102
$update_database) {
103
$cache = PhabricatorCaches::getSetupCache();
104
$cache->setKey('phabricator.setup.issue-keys', $keys);
105
106
$server_cache = PhabricatorCaches::getServerStateCache();
107
$server_cache->setKey('phabricator.in-flight', 1);
108
109
if ($update_database) {
110
$db_cache = new PhabricatorKeyValueDatabaseCache();
111
try {
112
$json = phutil_json_encode($keys);
113
$db_cache->setKey('phabricator.setup.issue-keys', $json);
114
} catch (Exception $ex) {
115
// Ignore any write failures, since they likely just indicate that we
116
// have a database-related setup issue that needs to be resolved.
117
}
118
}
119
}
120
121
final public static function getOpenSetupIssueKeysFromDatabase() {
122
$db_cache = new PhabricatorKeyValueDatabaseCache();
123
try {
124
$value = $db_cache->getKey('phabricator.setup.issue-keys');
125
if ($value === null || !strlen($value)) {
126
return null;
127
}
128
return phutil_json_decode($value);
129
} catch (Exception $ex) {
130
return null;
131
}
132
}
133
134
final public static function getUnignoredIssueKeys(array $all_issues) {
135
assert_instances_of($all_issues, 'PhabricatorSetupIssue');
136
$keys = array();
137
foreach ($all_issues as $issue) {
138
if (!$issue->getIsIgnored()) {
139
$keys[] = $issue->getIssueKey();
140
}
141
}
142
return $keys;
143
}
144
145
final public static function getConfigNeedsRepair() {
146
$cache = PhabricatorCaches::getSetupCache();
147
return $cache->getKey('phabricator.setup.needs-repair');
148
}
149
150
final public static function setConfigNeedsRepair($needs_repair) {
151
$cache = PhabricatorCaches::getSetupCache();
152
$cache->setKey('phabricator.setup.needs-repair', $needs_repair);
153
}
154
155
final public static function deleteSetupCheckCache() {
156
$cache = PhabricatorCaches::getSetupCache();
157
$cache->deleteKeys(
158
array(
159
'phabricator.setup.needs-repair',
160
'phabricator.setup.issue-keys',
161
));
162
}
163
164
final public static function willPreflightRequest() {
165
$checks = self::loadAllChecks();
166
167
foreach ($checks as $check) {
168
if (!$check->isPreflightCheck()) {
169
continue;
170
}
171
172
$check->runSetupChecks();
173
174
foreach ($check->getIssues() as $key => $issue) {
175
return self::newIssueResponse($issue);
176
}
177
}
178
179
return null;
180
}
181
182
public static function newIssueResponse(PhabricatorSetupIssue $issue) {
183
$view = id(new PhabricatorSetupIssueView())
184
->setIssue($issue);
185
186
return id(new PhabricatorConfigResponse())
187
->setView($view);
188
}
189
190
final public static function willProcessRequest() {
191
$issue_keys = self::getOpenSetupIssueKeys();
192
if ($issue_keys === null) {
193
$engine = new PhabricatorSetupEngine();
194
$response = $engine->execute();
195
if ($response) {
196
return $response;
197
}
198
} else if ($issue_keys) {
199
// If Phabricator is configured in a cluster with multiple web devices,
200
// we can end up with setup issues cached on every device. This can cause
201
// a warning banner to show on every device so that each one needs to
202
// be dismissed individually, which is pretty annoying. See T10876.
203
204
// To avoid this, check if the issues we found have already been cleared
205
// in the database. If they have, we'll just wipe out our own cache and
206
// move on.
207
$issue_keys = self::getOpenSetupIssueKeysFromDatabase();
208
if ($issue_keys !== null) {
209
self::setOpenSetupIssueKeys($issue_keys, $update_database = false);
210
}
211
}
212
213
// Try to repair configuration unless we have a clean bill of health on it.
214
// We need to keep doing this on every page load until all the problems
215
// are fixed, which is why it's separate from setup checks (which run
216
// once per restart).
217
$needs_repair = self::getConfigNeedsRepair();
218
if ($needs_repair !== false) {
219
$needs_repair = self::repairConfig();
220
self::setConfigNeedsRepair($needs_repair);
221
}
222
}
223
224
/**
225
* Test if we've survived through setup on at least one normal request
226
* without fataling.
227
*
228
* If we've made it through setup without hitting any fatals, we switch
229
* to render a more friendly error page when encountering issues like
230
* database connection failures. This gives users a smoother experience in
231
* the face of intermittent failures.
232
*
233
* @return bool True if we've made it through setup since the last restart.
234
*/
235
final public static function isInFlight() {
236
$cache = PhabricatorCaches::getServerStateCache();
237
return (bool)$cache->getKey('phabricator.in-flight');
238
}
239
240
final public static function loadAllChecks() {
241
return id(new PhutilClassMapQuery())
242
->setAncestorClass(__CLASS__)
243
->setSortMethod('getExecutionOrder')
244
->execute();
245
}
246
247
final public static function runNormalChecks() {
248
$checks = self::loadAllChecks();
249
250
foreach ($checks as $key => $check) {
251
if ($check->isPreflightCheck()) {
252
unset($checks[$key]);
253
}
254
}
255
256
$issues = array();
257
foreach ($checks as $check) {
258
$check->runSetupChecks();
259
foreach ($check->getIssues() as $key => $issue) {
260
if (isset($issues[$key])) {
261
throw new Exception(
262
pht(
263
"Two setup checks raised an issue with key '%s'!",
264
$key));
265
}
266
$issues[$key] = $issue;
267
if ($issue->getIsFatal()) {
268
break 2;
269
}
270
}
271
}
272
273
$ignore_issues = PhabricatorEnv::getEnvConfig('config.ignore-issues');
274
foreach ($ignore_issues as $ignorable => $derp) {
275
if (isset($issues[$ignorable])) {
276
$issues[$ignorable]->setIsIgnored(true);
277
}
278
}
279
280
return $issues;
281
}
282
283
final public static function repairConfig() {
284
$needs_repair = false;
285
286
$options = PhabricatorApplicationConfigOptions::loadAllOptions();
287
foreach ($options as $option) {
288
try {
289
$option->getGroup()->validateOption(
290
$option,
291
PhabricatorEnv::getEnvConfig($option->getKey()));
292
} catch (PhabricatorConfigValidationException $ex) {
293
PhabricatorEnv::repairConfig($option->getKey(), $option->getDefault());
294
$needs_repair = true;
295
}
296
}
297
298
return $needs_repair;
299
}
300
301
}
302
303