Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/config/check/PhabricatorDatabaseSetupCheck.php
12256 views
1
<?php
2
3
final class PhabricatorDatabaseSetupCheck extends PhabricatorSetupCheck {
4
5
public function getDefaultGroup() {
6
return self::GROUP_IMPORTANT;
7
}
8
9
public function getExecutionOrder() {
10
// This must run after basic PHP checks, but before most other checks.
11
return 500;
12
}
13
14
protected function executeChecks() {
15
$host = PhabricatorEnv::getEnvConfig('mysql.host');
16
$matches = null;
17
if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) {
18
$host = $matches[1];
19
$port = $matches[2];
20
21
$this->newIssue('storage.mysql.hostport')
22
->setName(pht('Deprecated mysql.host Format'))
23
->setSummary(
24
pht(
25
'Move port information from `%s` to `%s` in your config.',
26
'mysql.host',
27
'mysql.port'))
28
->setMessage(
29
pht(
30
'Your `%s` configuration contains a port number, but this usage '.
31
'is deprecated. Instead, put the port number in `%s`.',
32
'mysql.host',
33
'mysql.port'))
34
->addPhabricatorConfig('mysql.host')
35
->addPhabricatorConfig('mysql.port')
36
->addCommand(
37
hsprintf(
38
'<tt>$</tt> ./bin/config set mysql.host %s',
39
$host))
40
->addCommand(
41
hsprintf(
42
'<tt>$</tt> ./bin/config set mysql.port %s',
43
$port));
44
}
45
46
$refs = PhabricatorDatabaseRef::queryAll();
47
$refs = mpull($refs, null, 'getRefKey');
48
49
// Test if we can connect to each database first. If we can not connect
50
// to a particular database, we only raise a warning: this allows new web
51
// nodes to start during a disaster, when some databases may be correctly
52
// configured but not reachable.
53
54
$connect_map = array();
55
$any_connection = false;
56
foreach ($refs as $ref_key => $ref) {
57
$conn_raw = $ref->newManagementConnection();
58
59
try {
60
queryfx($conn_raw, 'SELECT 1');
61
$database_exception = null;
62
$any_connection = true;
63
} catch (AphrontInvalidCredentialsQueryException $ex) {
64
$database_exception = $ex;
65
} catch (AphrontConnectionQueryException $ex) {
66
$database_exception = $ex;
67
}
68
69
if ($database_exception) {
70
$connect_map[$ref_key] = $database_exception;
71
unset($refs[$ref_key]);
72
}
73
}
74
75
if ($connect_map) {
76
// This is only a fatal error if we could not connect to anything. If
77
// possible, we still want to start if some database hosts can not be
78
// reached.
79
$is_fatal = !$any_connection;
80
81
foreach ($connect_map as $ref_key => $database_exception) {
82
$issue = PhabricatorSetupIssue::newDatabaseConnectionIssue(
83
$database_exception,
84
$is_fatal);
85
$this->addIssue($issue);
86
}
87
}
88
89
foreach ($refs as $ref_key => $ref) {
90
if ($this->executeRefChecks($ref)) {
91
return;
92
}
93
}
94
}
95
96
private function executeRefChecks(PhabricatorDatabaseRef $ref) {
97
$conn_raw = $ref->newManagementConnection();
98
$ref_key = $ref->getRefKey();
99
100
$engines = queryfx_all($conn_raw, 'SHOW ENGINES');
101
$engines = ipull($engines, 'Support', 'Engine');
102
103
$innodb = idx($engines, 'InnoDB');
104
if ($innodb != 'YES' && $innodb != 'DEFAULT') {
105
$message = pht(
106
'The "InnoDB" engine is not available in MySQL (on host "%s"). '.
107
'Enable InnoDB in your MySQL configuration.'.
108
"\n\n".
109
'(If you aleady created tables, MySQL incorrectly used some other '.
110
'engine to create them. You need to convert them or drop and '.
111
'reinitialize them.)',
112
$ref_key);
113
114
$this->newIssue('mysql.innodb')
115
->setName(pht('MySQL InnoDB Engine Not Available'))
116
->setMessage($message)
117
->setIsFatal(true);
118
119
return true;
120
}
121
122
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
123
124
$databases = queryfx_all($conn_raw, 'SHOW DATABASES');
125
$databases = ipull($databases, 'Database', 'Database');
126
127
if (empty($databases[$namespace.'_meta_data'])) {
128
$message = pht(
129
'Run the storage upgrade script to setup databases (host "%s" has '.
130
'not been initialized).',
131
$ref_key);
132
133
$this->newIssue('storage.upgrade')
134
->setName(pht('Setup MySQL Schema'))
135
->setMessage($message)
136
->setIsFatal(true)
137
->addCommand(hsprintf('<tt>$</tt> ./bin/storage upgrade'));
138
139
return true;
140
}
141
142
$conn_meta = $ref->newApplicationConnection(
143
$namespace.'_meta_data');
144
145
$applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');
146
$applied = ipull($applied, 'patch', 'patch');
147
148
$all = PhabricatorSQLPatchList::buildAllPatches();
149
$diff = array_diff_key($all, $applied);
150
151
if ($diff) {
152
$message = pht(
153
'Run the storage upgrade script to upgrade databases (host "%s" is '.
154
'out of date). Missing patches: %s.',
155
$ref_key,
156
implode(', ', array_keys($diff)));
157
158
$this->newIssue('storage.patch')
159
->setName(pht('Upgrade MySQL Schema'))
160
->setIsFatal(true)
161
->setMessage($message)
162
->addCommand(
163
hsprintf('<tt>$</tt> ./bin/storage upgrade'));
164
165
return true;
166
}
167
168
// NOTE: It's possible that replication is broken but we have not been
169
// granted permission to "SHOW SLAVE STATUS" so we can't figure it out.
170
// We allow this kind of configuration and survive these checks, trusting
171
// that operations knows what they're doing. This issue is shown on the
172
// "Database Servers" console.
173
174
switch ($ref->getReplicaStatus()) {
175
case PhabricatorDatabaseRef::REPLICATION_MASTER_REPLICA:
176
$message = pht(
177
'Database host "%s" is configured as a master, but is replicating '.
178
'another host. This is dangerous and can mangle or destroy data. '.
179
'Only replicas should be replicating. Stop replication on the '.
180
'host or adjust configuration.',
181
$ref->getRefKey());
182
183
$this->newIssue('db.master.replicating')
184
->setName(pht('Replicating Master'))
185
->setIsFatal(true)
186
->setMessage($message);
187
188
return true;
189
case PhabricatorDatabaseRef::REPLICATION_REPLICA_NONE:
190
case PhabricatorDatabaseRef::REPLICATION_NOT_REPLICATING:
191
if (!$ref->getIsMaster()) {
192
$message = pht(
193
'Database replica "%s" is listed as a replica, but is not '.
194
'currently replicating. You are vulnerable to data loss if '.
195
'the master fails.',
196
$ref->getRefKey());
197
198
// This isn't a fatal because it can normally only put data at risk,
199
// not actually do anything destructive or unrecoverable.
200
201
$this->newIssue('db.replica.not-replicating')
202
->setName(pht('Nonreplicating Replica'))
203
->setMessage($message);
204
}
205
break;
206
}
207
208
// If we have more than one master, we require that the cluster database
209
// configuration written to each database node is exactly the same as the
210
// one we are running with.
211
$masters = PhabricatorDatabaseRef::getAllMasterDatabaseRefs();
212
if (count($masters) > 1) {
213
$state_actual = queryfx_one(
214
$conn_meta,
215
'SELECT stateValue FROM %T WHERE stateKey = %s',
216
PhabricatorStorageManagementAPI::TABLE_HOSTSTATE,
217
'cluster.databases');
218
if ($state_actual) {
219
$state_actual = $state_actual['stateValue'];
220
}
221
222
$state_expect = $ref->getPartitionStateForCommit();
223
224
if ($state_expect !== $state_actual) {
225
$message = pht(
226
'Database host "%s" has a configured cluster state which disagrees '.
227
'with the state on this host ("%s"). Run `bin/storage partition` '.
228
'to commit local state to the cluster. This host may have started '.
229
'with an out-of-date configuration.',
230
$ref->getRefKey(),
231
php_uname('n'));
232
233
$this->newIssue('db.state.desync')
234
->setName(pht('Cluster Configuration Out of Sync'))
235
->setMessage($message)
236
->setIsFatal(true);
237
return true;
238
}
239
}
240
}
241
242
}
243
244