Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/config/controller/services/PhabricatorConfigClusterRepositoriesController.php
12262 views
1
<?php
2
3
final class PhabricatorConfigClusterRepositoriesController
4
extends PhabricatorConfigServicesController {
5
6
public function handleRequest(AphrontRequest $request) {
7
$title = pht('Repository Services');
8
9
$doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories');
10
$button = id(new PHUIButtonView())
11
->setIcon('fa-book')
12
->setHref($doc_href)
13
->setTag('a')
14
->setText(pht('Documentation'));
15
16
$header = $this->buildHeaderView($title, $button);
17
18
$repository_status = $this->buildClusterRepositoryStatus();
19
$repo_status = $this->buildConfigBoxView(
20
pht('Repository Status'), $repository_status);
21
22
$repository_errors = $this->buildClusterRepositoryErrors();
23
$repo_errors = $this->buildConfigBoxView(
24
pht('Repository Errors'), $repository_errors);
25
26
$crumbs = $this->newCrumbs()
27
->addTextCrumb($title);
28
29
$content = id(new PHUITwoColumnView())
30
->setHeader($header)
31
->setFooter(
32
array(
33
$repo_status,
34
$repo_errors,
35
));
36
37
$nav = $this->newNavigation('repository-servers');
38
39
return $this->newPage()
40
->setTitle($title)
41
->setCrumbs($crumbs)
42
->setNavigation($nav)
43
->appendChild($content);
44
}
45
46
private function buildClusterRepositoryStatus() {
47
$viewer = $this->getViewer();
48
49
Javelin::initBehavior('phabricator-tooltips');
50
51
$all_services = id(new AlmanacServiceQuery())
52
->setViewer($viewer)
53
->withServiceTypes(
54
array(
55
AlmanacClusterRepositoryServiceType::SERVICETYPE,
56
))
57
->needBindings(true)
58
->needProperties(true)
59
->execute();
60
$all_services = mpull($all_services, null, 'getPHID');
61
62
$all_repositories = id(new PhabricatorRepositoryQuery())
63
->setViewer($viewer)
64
->withTypes(
65
array(
66
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT,
67
))
68
->execute();
69
$all_repositories = mpull($all_repositories, null, 'getPHID');
70
71
$all_versions = id(new PhabricatorRepositoryWorkingCopyVersion())
72
->loadAll();
73
74
$all_devices = $this->getDevices($all_services, false);
75
$all_active_devices = $this->getDevices($all_services, true);
76
77
$leader_versions = $this->getLeaderVersionsByRepository(
78
$all_repositories,
79
$all_versions,
80
$all_active_devices);
81
82
$push_times = $this->loadLeaderPushTimes($leader_versions);
83
84
$repository_groups = mgroup($all_repositories, 'getAlmanacServicePHID');
85
$repository_versions = mgroup($all_versions, 'getRepositoryPHID');
86
87
$rows = array();
88
foreach ($all_services as $service) {
89
$service_phid = $service->getPHID();
90
91
if ($service->getAlmanacPropertyValue('closed')) {
92
$status_icon = 'fa-folder';
93
$status_tip = pht('Closed');
94
} else {
95
$status_icon = 'fa-folder-open green';
96
$status_tip = pht('Open');
97
}
98
99
$status_icon = id(new PHUIIconView())
100
->setIcon($status_icon)
101
->addSigil('has-tooltip')
102
->setMetadata(
103
array(
104
'tip' => $status_tip,
105
));
106
107
$devices = idx($all_devices, $service_phid, array());
108
$active_devices = idx($all_active_devices, $service_phid, array());
109
110
$device_icon = 'fa-server green';
111
112
$device_label = pht(
113
'%s Active',
114
phutil_count($active_devices));
115
116
$device_status = array(
117
id(new PHUIIconView())->setIcon($device_icon),
118
' ',
119
$device_label,
120
);
121
122
$repositories = idx($repository_groups, $service_phid, array());
123
124
$repository_status = pht(
125
'%s',
126
phutil_count($repositories));
127
128
$no_leader = array();
129
$full_sync = array();
130
$partial_sync = array();
131
$no_sync = array();
132
$lag = array();
133
134
// Threshold in seconds before we start complaining that repositories
135
// are not synchronized when there is only one leader.
136
$threshold = phutil_units('5 minutes in seconds');
137
138
$messages = array();
139
140
foreach ($repositories as $repository) {
141
$repository_phid = $repository->getPHID();
142
143
$leader_version = idx($leader_versions, $repository_phid);
144
if ($leader_version === null) {
145
$no_leader[] = $repository;
146
$messages[] = pht(
147
'Repository %s has an ambiguous leader.',
148
$viewer->renderHandle($repository_phid)->render());
149
continue;
150
}
151
152
$versions = idx($repository_versions, $repository_phid, array());
153
154
// Filter out any versions for devices which are no longer active.
155
foreach ($versions as $key => $version) {
156
$version_device_phid = $version->getDevicePHID();
157
if (empty($active_devices[$version_device_phid])) {
158
unset($versions[$key]);
159
}
160
}
161
162
$leaders = 0;
163
foreach ($versions as $version) {
164
if ($version->getRepositoryVersion() == $leader_version) {
165
$leaders++;
166
}
167
}
168
169
if ($leaders == count($active_devices)) {
170
$full_sync[] = $repository;
171
} else {
172
$push_epoch = idx($push_times, $repository_phid);
173
if ($push_epoch) {
174
$duration = (PhabricatorTime::getNow() - $push_epoch);
175
$lag[] = $duration;
176
} else {
177
$duration = null;
178
}
179
180
if ($leaders >= 2 || ($duration && ($duration < $threshold))) {
181
$partial_sync[] = $repository;
182
} else {
183
$no_sync[] = $repository;
184
if ($push_epoch) {
185
$messages[] = pht(
186
'Repository %s has unreplicated changes (for %s).',
187
$viewer->renderHandle($repository_phid)->render(),
188
phutil_format_relative_time($duration));
189
} else {
190
$messages[] = pht(
191
'Repository %s has unreplicated changes.',
192
$viewer->renderHandle($repository_phid)->render());
193
}
194
}
195
196
}
197
}
198
199
$with_lag = false;
200
201
if ($no_leader) {
202
$replication_icon = 'fa-times red';
203
$replication_label = pht('Ambiguous Leader');
204
} else if ($no_sync) {
205
$replication_icon = 'fa-refresh yellow';
206
$replication_label = pht('Unsynchronized');
207
$with_lag = true;
208
} else if ($partial_sync) {
209
$replication_icon = 'fa-refresh green';
210
$replication_label = pht('Partial');
211
$with_lag = true;
212
} else if ($full_sync) {
213
$replication_icon = 'fa-check green';
214
$replication_label = pht('Synchronized');
215
} else {
216
$replication_icon = 'fa-times grey';
217
$replication_label = pht('No Repositories');
218
}
219
220
if ($with_lag && $lag) {
221
$lag_status = phutil_format_relative_time(max($lag));
222
$lag_status = pht(' (%s)', $lag_status);
223
} else {
224
$lag_status = null;
225
}
226
227
$replication_status = array(
228
id(new PHUIIconView())->setIcon($replication_icon),
229
' ',
230
$replication_label,
231
$lag_status,
232
);
233
234
$messages = phutil_implode_html(phutil_tag('br'), $messages);
235
236
$rows[] = array(
237
$status_icon,
238
$viewer->renderHandle($service->getPHID()),
239
$device_status,
240
$repository_status,
241
$replication_status,
242
$messages,
243
);
244
}
245
246
return id(new AphrontTableView($rows))
247
->setNoDataString(
248
pht('No repository cluster services are configured.'))
249
->setHeaders(
250
array(
251
null,
252
pht('Service'),
253
pht('Devices'),
254
pht('Repos'),
255
pht('Sync'),
256
pht('Messages'),
257
))
258
->setColumnClasses(
259
array(
260
null,
261
'pri',
262
null,
263
null,
264
null,
265
'wide',
266
));
267
}
268
269
private function getDevices(
270
array $all_services,
271
$only_active) {
272
273
$devices = array();
274
foreach ($all_services as $service) {
275
$map = array();
276
foreach ($service->getBindings() as $binding) {
277
if ($only_active && $binding->getIsDisabled()) {
278
continue;
279
}
280
281
$device = $binding->getDevice();
282
$device_phid = $device->getPHID();
283
284
$map[$device_phid] = $device;
285
}
286
$devices[$service->getPHID()] = $map;
287
}
288
289
return $devices;
290
}
291
292
private function getLeaderVersionsByRepository(
293
array $all_repositories,
294
array $all_versions,
295
array $active_devices) {
296
297
$version_map = mgroup($all_versions, 'getRepositoryPHID');
298
299
$result = array();
300
foreach ($all_repositories as $repository_phid => $repository) {
301
$service_phid = $repository->getAlmanacServicePHID();
302
if (!$service_phid) {
303
continue;
304
}
305
306
$devices = idx($active_devices, $service_phid);
307
if (!$devices) {
308
continue;
309
}
310
311
$versions = idx($version_map, $repository_phid, array());
312
$versions = mpull($versions, null, 'getDevicePHID');
313
$versions = array_select_keys($versions, array_keys($devices));
314
if (!$versions) {
315
continue;
316
}
317
318
$leader = (int)max(mpull($versions, 'getRepositoryVersion'));
319
$result[$repository_phid] = $leader;
320
}
321
322
return $result;
323
}
324
325
private function loadLeaderPushTimes(array $leader_versions) {
326
$viewer = $this->getViewer();
327
328
if (!$leader_versions) {
329
return array();
330
}
331
332
$events = id(new PhabricatorRepositoryPushEventQuery())
333
->setViewer($viewer)
334
->withIDs($leader_versions)
335
->execute();
336
$events = mpull($events, null, 'getID');
337
338
$result = array();
339
foreach ($leader_versions as $key => $version) {
340
$event = idx($events, $version);
341
if (!$event) {
342
continue;
343
}
344
345
$result[$key] = $event->getEpoch();
346
}
347
348
return $result;
349
}
350
351
352
private function buildClusterRepositoryErrors() {
353
$viewer = $this->getViewer();
354
355
$messages = id(new PhabricatorRepositoryStatusMessage())->loadAllWhere(
356
'statusCode IN (%Ls)',
357
array(
358
PhabricatorRepositoryStatusMessage::CODE_ERROR,
359
));
360
361
$repository_ids = mpull($messages, 'getRepositoryID');
362
if ($repository_ids) {
363
// NOTE: We're bypassing policies when loading repositories because we
364
// want to show errors exist even if the viewer can't see the repository.
365
// We use handles to describe the repository below, so the viewer won't
366
// actually be able to see any particulars if they can't see the
367
// repository.
368
$repositories = id(new PhabricatorRepositoryQuery())
369
->setViewer(PhabricatorUser::getOmnipotentUser())
370
->withIDs($repository_ids)
371
->execute();
372
$repositories = mpull($repositories, null, 'getID');
373
}
374
375
$rows = array();
376
foreach ($messages as $message) {
377
$repository = idx($repositories, $message->getRepositoryID());
378
if (!$repository) {
379
continue;
380
}
381
382
if (!$repository->isTracked()) {
383
continue;
384
}
385
386
$icon = id(new PHUIIconView())
387
->setIcon('fa-exclamation-triangle red');
388
389
$rows[] = array(
390
$icon,
391
$viewer->renderHandle($repository->getPHID()),
392
phutil_tag(
393
'a',
394
array(
395
'href' => $repository->getPathURI('manage/status/'),
396
),
397
$message->getStatusTypeName()),
398
);
399
}
400
401
return id(new AphrontTableView($rows))
402
->setNoDataString(
403
pht('No active repositories have outstanding errors.'))
404
->setHeaders(
405
array(
406
null,
407
pht('Repository'),
408
pht('Error'),
409
))
410
->setColumnClasses(
411
array(
412
null,
413
'pri',
414
'wide',
415
));
416
}
417
418
}
419
420