Page MenuHomePhabricator

D15772.id38018.diff
No OneTemporary

D15772.id38018.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2055,6 +2055,7 @@
'PhabricatorConfigCacheController' => 'applications/config/controller/PhabricatorConfigCacheController.php',
'PhabricatorConfigClusterDatabasesController' => 'applications/config/controller/PhabricatorConfigClusterDatabasesController.php',
'PhabricatorConfigClusterNotificationsController' => 'applications/config/controller/PhabricatorConfigClusterNotificationsController.php',
+ 'PhabricatorConfigClusterRepositoriesController' => 'applications/config/controller/PhabricatorConfigClusterRepositoriesController.php',
'PhabricatorConfigCollectorsModule' => 'applications/config/module/PhabricatorConfigCollectorsModule.php',
'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php',
'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php',
@@ -6502,6 +6503,7 @@
'PhabricatorConfigCacheController' => 'PhabricatorConfigController',
'PhabricatorConfigClusterDatabasesController' => 'PhabricatorConfigController',
'PhabricatorConfigClusterNotificationsController' => 'PhabricatorConfigController',
+ 'PhabricatorConfigClusterRepositoriesController' => 'PhabricatorConfigController',
'PhabricatorConfigCollectorsModule' => 'PhabricatorConfigModule',
'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType',
diff --git a/src/applications/config/application/PhabricatorConfigApplication.php b/src/applications/config/application/PhabricatorConfigApplication.php
--- a/src/applications/config/application/PhabricatorConfigApplication.php
+++ b/src/applications/config/application/PhabricatorConfigApplication.php
@@ -65,6 +65,7 @@
'cluster/' => array(
'databases/' => 'PhabricatorConfigClusterDatabasesController',
'notifications/' => 'PhabricatorConfigClusterNotificationsController',
+ 'repositories/' => 'PhabricatorConfigClusterRepositoriesController',
),
),
);
diff --git a/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php
@@ -0,0 +1,343 @@
+<?php
+
+final class PhabricatorConfigClusterRepositoriesController
+ extends PhabricatorConfigController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $nav = $this->buildSideNavView();
+ $nav->selectFilter('cluster/repositories/');
+
+ $title = pht('Repository Servers');
+
+ $crumbs = $this
+ ->buildApplicationCrumbs($nav)
+ ->addTextCrumb(pht('Repository Servers'));
+
+ $repository_status = $this->buildClusterRepositoryStatus();
+
+ $view = id(new PHUITwoColumnView())
+ ->setNavigation($nav)
+ ->setMainColumn($repository_status);
+
+ return $this->newPage()
+ ->setTitle($title)
+ ->setCrumbs($crumbs)
+ ->appendChild($view);
+ }
+
+ private function buildClusterRepositoryStatus() {
+ $viewer = $this->getViewer();
+
+ Javelin::initBehavior('phabricator-tooltips');
+
+ $all_services = id(new AlmanacServiceQuery())
+ ->setViewer($viewer)
+ ->withServiceTypes(
+ array(
+ AlmanacClusterRepositoryServiceType::SERVICETYPE,
+ ))
+ ->needBindings(true)
+ ->needProperties(true)
+ ->execute();
+ $all_services = mpull($all_services, null, 'getPHID');
+
+ $all_repositories = id(new PhabricatorRepositoryQuery())
+ ->setViewer($viewer)
+ ->withHosted(PhabricatorRepositoryQuery::HOSTED_PHABRICATOR)
+ ->withTypes(
+ array(
+ PhabricatorRepositoryType::REPOSITORY_TYPE_GIT,
+ ))
+ ->execute();
+ $all_repositories = mpull($all_repositories, null, 'getPHID');
+
+ $all_versions = id(new PhabricatorRepositoryWorkingCopyVersion())
+ ->loadAll();
+
+ $all_devices = $this->getDevices($all_services, false);
+ $all_active_devices = $this->getDevices($all_services, true);
+
+ $leader_versions = $this->getLeaderVersionsByRepository(
+ $all_repositories,
+ $all_versions,
+ $all_active_devices);
+
+ $push_times = $this->loadLeaderPushTimes($leader_versions);
+
+ $repository_groups = mgroup($all_repositories, 'getAlmanacServicePHID');
+ $repository_versions = mgroup($all_versions, 'getRepositoryPHID');
+
+ $rows = array();
+ foreach ($all_services as $service) {
+ $service_phid = $service->getPHID();
+
+ if ($service->getAlmanacPropertyValue('closed')) {
+ $status_icon = 'fa-folder';
+ $status_tip = pht('Closed');
+ } else {
+ $status_icon = 'fa-folder-open green';
+ $status_tip = pht('Open');
+ }
+
+ $status_icon = id(new PHUIIconView())
+ ->setIcon($status_icon)
+ ->addSigil('has-tooltip')
+ ->setMetadata(
+ array(
+ 'tip' => $status_tip,
+ ));
+
+ $devices = idx($all_devices, $service_phid, array());
+ $active_devices = idx($all_active_devices, $service_phid, array());
+
+ $device_icon = 'fa-server green';
+
+ $device_label = pht(
+ '%s Active',
+ phutil_count($active_devices));
+
+ $device_status = array(
+ id(new PHUIIconView())->setIcon($device_icon),
+ ' ',
+ $device_label,
+ );
+
+ $repositories = idx($repository_groups, $service_phid, array());
+
+ $repository_status = pht(
+ '%s',
+ phutil_count($repositories));
+
+ $no_leader = array();
+ $full_sync = array();
+ $partial_sync = array();
+ $no_sync = array();
+ $lag = array();
+
+ // Threshold in seconds before we start complaining that repositories
+ // are not synchronized when there is only one leader.
+ $threshold = phutil_units('5 minutes in seconds');
+
+ $messages = array();
+
+ foreach ($repositories as $repository) {
+ $repository_phid = $repository->getPHID();
+
+ $leader_version = idx($leader_versions, $repository_phid);
+ if ($leader_version === null) {
+ $no_leader[] = $repository;
+ $messages[] = pht(
+ 'Repository %s has an ambiguous leader.',
+ $viewer->renderHandle($repository_phid)->render());
+ continue;
+ }
+
+ $versions = idx($repository_versions, $repository_phid, array());
+
+ $leaders = 0;
+ foreach ($versions as $version) {
+ if ($version->getRepositoryVersion() == $leader_version) {
+ $leaders++;
+ }
+ }
+
+ if ($leaders == count($active_devices)) {
+ $full_sync[] = $repository;
+ } else {
+ $push_epoch = idx($push_times, $repository_phid);
+ if ($push_epoch) {
+ $duration = (PhabricatorTime::getNow() - $push_epoch);
+ $lag[] = $duration;
+ } else {
+ $duration = null;
+ }
+
+ if ($leaders >= 2 || ($duration && ($duration < $threshold))) {
+ $partial_sync[] = $repository;
+ } else {
+ $no_sync[] = $repository;
+ if ($push_epoch) {
+ $messages[] = pht(
+ 'Repository %s has unreplicated changes (for %s).',
+ $viewer->renderHandle($repository_phid)->render(),
+ phutil_format_relative_time($duration));
+ } else {
+ $messages[] = pht(
+ 'Repository %s has unreplicated changes.',
+ $viewer->renderHandle($repository_phid)->render());
+ }
+ }
+
+ }
+ }
+
+ $with_lag = false;
+
+ if ($no_leader) {
+ $replication_icon = 'fa-times red';
+ $replication_label = pht('Ambiguous Leader');
+ } else if ($no_sync) {
+ $replication_icon = 'fa-refresh yellow';
+ $replication_label = pht('Unsynchronized');
+ $with_lag = true;
+ } else if ($partial_sync) {
+ $replication_icon = 'fa-refresh green';
+ $replication_label = pht('Partial');
+ $with_lag = true;
+ } else if ($full_sync) {
+ $replication_icon = 'fa-check green';
+ $replication_label = pht('Synchronized');
+ } else {
+ $replication_icon = 'fa-times grey';
+ $replication_label = pht('No Repositories');
+ }
+
+ if ($with_lag && $lag) {
+ $lag_status = phutil_format_relative_time(max($lag));
+ $lag_status = pht(' (%s)', $lag_status);
+ } else {
+ $lag_status = null;
+ }
+
+ $replication_status = array(
+ id(new PHUIIconView())->setIcon($replication_icon),
+ ' ',
+ $replication_label,
+ $lag_status,
+ );
+
+ $messages = phutil_implode_html(phutil_tag('br'), $messages);
+
+ $rows[] = array(
+ $status_icon,
+ $viewer->renderHandle($service->getPHID()),
+ $device_status,
+ $repository_status,
+ $replication_status,
+ $messages,
+ );
+ }
+
+
+ $table = id(new AphrontTableView($rows))
+ ->setNoDataString(
+ pht('No repository cluster services are configured.'))
+ ->setHeaders(
+ array(
+ null,
+ pht('Service'),
+ pht('Devices'),
+ pht('Repos'),
+ pht('Sync'),
+ pht('Messages'),
+ ))
+ ->setColumnClasses(
+ array(
+ null,
+ 'pri',
+ null,
+ null,
+ null,
+ 'wide',
+ ));
+
+ $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories');
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader(pht('Cluster Repository Status'))
+ ->addActionLink(
+ id(new PHUIButtonView())
+ ->setIcon('fa-book')
+ ->setHref($doc_href)
+ ->setTag('a')
+ ->setText(pht('Documentation')));
+
+ return id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->setTable($table);
+ }
+
+ private function getDevices(
+ array $all_services,
+ $only_active) {
+
+ $devices = array();
+ foreach ($all_services as $service) {
+ $map = array();
+ foreach ($service->getBindings() as $binding) {
+ if ($only_active && $binding->getIsDisabled()) {
+ continue;
+ }
+
+ $device = $binding->getDevice();
+ $device_phid = $device->getPHID();
+
+ $map[$device_phid] = $device;
+ }
+ $devices[$service->getPHID()] = $map;
+ }
+
+ return $devices;
+ }
+
+ private function getLeaderVersionsByRepository(
+ array $all_repositories,
+ array $all_versions,
+ array $active_devices) {
+
+ $version_map = mgroup($all_versions, 'getRepositoryPHID');
+
+ $result = array();
+ foreach ($all_repositories as $repository_phid => $repository) {
+ $service_phid = $repository->getAlmanacServicePHID();
+ if (!$service_phid) {
+ continue;
+ }
+
+ $devices = idx($active_devices, $service_phid);
+ if (!$devices) {
+ continue;
+ }
+
+ $versions = idx($version_map, $repository_phid, array());
+ $versions = mpull($versions, null, 'getDevicePHID');
+ $versions = array_select_keys($versions, array_keys($devices));
+ if (!$versions) {
+ continue;
+ }
+
+ $leader = (int)max(mpull($versions, 'getRepositoryVersion'));
+ $result[$repository_phid] = $leader;
+ }
+
+ return $result;
+ }
+
+ private function loadLeaderPushTimes(array $leader_versions) {
+ $viewer = $this->getViewer();
+
+ if (!$leader_versions) {
+ return array();
+ }
+
+ $events = id(new PhabricatorRepositoryPushEventQuery())
+ ->setViewer($viewer)
+ ->withIDs($leader_versions)
+ ->execute();
+ $events = mpull($events, null, 'getID');
+
+ $result = array();
+ foreach ($leader_versions as $key => $version) {
+ $event = idx($events, $version);
+ if (!$event) {
+ continue;
+ }
+
+ $result[$key] = $event->getEpoch();
+ }
+
+ return $result;
+ }
+
+
+}
diff --git a/src/applications/config/controller/PhabricatorConfigController.php b/src/applications/config/controller/PhabricatorConfigController.php
--- a/src/applications/config/controller/PhabricatorConfigController.php
+++ b/src/applications/config/controller/PhabricatorConfigController.php
@@ -25,6 +25,7 @@
$nav->addLabel(pht('Cluster'));
$nav->addFilter('cluster/databases/', pht('Database Servers'));
$nav->addFilter('cluster/notifications/', pht('Notification Servers'));
+ $nav->addFilter('cluster/repositories/', pht('Repository Servers'));
$nav->addLabel(pht('Welcome'));
$nav->addFilter('welcome/', pht('Welcome Screen'));
$nav->addLabel(pht('Modules'));
diff --git a/src/docs/user/cluster/cluster_repositories.diviner b/src/docs/user/cluster/cluster_repositories.diviner
--- a/src/docs/user/cluster/cluster_repositories.diviner
+++ b/src/docs/user/cluster/cluster_repositories.diviner
@@ -95,14 +95,41 @@
similar agents of other rogue nations is beyond the scope of this document.
-Monitoring Replication
-======================
+Monitoring Services
+===================
+
+You can get an overview of repository cluster status from the
+{nav Config > Repository Servers} screen. This table shows a high-level
+overview of all active repository services.
+
+**Repos**: The number of repositories hosted on this service.
+
+**Sync**: Synchronization status of repositories on this service. This is an
+at-a-glance view of service health, and can show these values:
+
+ - **Synchronized**: All nodes are fully synchronized and have the latest
+ version of all repositories.
+ - **Partial**: All repositories either have at least two leaders, or have
+ a very recent write which is not expected to have propagated yet.
+ - **Unsynchronized**: At least one repository has changes which are
+ only available on one node and were not pushed very recently. Data may
+ be at risk.
+ - **No Repositories**: This service has no repositories.
+ - **Ambiguous Leader**: At least one repository has an ambiguous leader.
-You can review the current status of a repository on cluster devices in
-{nav Diffusion > (Repository) > Manage Repository > Cluster Configuration}.
+If this screen identifies problems, you can drill down into repository details
+to get more information about them. See the next section for details.
+
+
+Monitoring Repositories
+=======================
+
+You can get a more detailed view the current status of a specific repository on
+cluster devices in {nav Diffusion > (Repository) > Manage Repository > Cluster
+Configuration}.
This screen shows all the configured devices which are hosting the repository
-and the available version.
+and the available version on that device.
**Version**: When a repository is mutated by a push, Phabricator increases
an internal version number for the repository. This column shows which version
@@ -131,6 +158,8 @@
currently held, this shows when the lock was acquired.
+
+
Cluster Failure Modes
=====================

File Metadata

Mime Type
text/plain
Expires
Fri, Mar 21, 11:46 AM (31 m, 19 s)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7684476
Default Alt Text
D15772.id38018.diff (15 KB)

Event Timeline