@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a "Repository Servers" cluster administration panel

Summary: Ref T4292. This adds a new high-level overview panel.

Test Plan: {F1238854}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4292

Differential Revision: https://secure.phabricator.com/D15772

+381 -5
+2
src/__phutil_library_map__.php
··· 2055 2055 'PhabricatorConfigCacheController' => 'applications/config/controller/PhabricatorConfigCacheController.php', 2056 2056 'PhabricatorConfigClusterDatabasesController' => 'applications/config/controller/PhabricatorConfigClusterDatabasesController.php', 2057 2057 'PhabricatorConfigClusterNotificationsController' => 'applications/config/controller/PhabricatorConfigClusterNotificationsController.php', 2058 + 'PhabricatorConfigClusterRepositoriesController' => 'applications/config/controller/PhabricatorConfigClusterRepositoriesController.php', 2058 2059 'PhabricatorConfigCollectorsModule' => 'applications/config/module/PhabricatorConfigCollectorsModule.php', 2059 2060 'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php', 2060 2061 'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php', ··· 6502 6503 'PhabricatorConfigCacheController' => 'PhabricatorConfigController', 6503 6504 'PhabricatorConfigClusterDatabasesController' => 'PhabricatorConfigController', 6504 6505 'PhabricatorConfigClusterNotificationsController' => 'PhabricatorConfigController', 6506 + 'PhabricatorConfigClusterRepositoriesController' => 'PhabricatorConfigController', 6505 6507 'PhabricatorConfigCollectorsModule' => 'PhabricatorConfigModule', 6506 6508 'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema', 6507 6509 'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType',
+1
src/applications/config/application/PhabricatorConfigApplication.php
··· 65 65 'cluster/' => array( 66 66 'databases/' => 'PhabricatorConfigClusterDatabasesController', 67 67 'notifications/' => 'PhabricatorConfigClusterNotificationsController', 68 + 'repositories/' => 'PhabricatorConfigClusterRepositoriesController', 68 69 ), 69 70 ), 70 71 );
+343
src/applications/config/controller/PhabricatorConfigClusterRepositoriesController.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigClusterRepositoriesController 4 + extends PhabricatorConfigController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $nav = $this->buildSideNavView(); 8 + $nav->selectFilter('cluster/repositories/'); 9 + 10 + $title = pht('Repository Servers'); 11 + 12 + $crumbs = $this 13 + ->buildApplicationCrumbs($nav) 14 + ->addTextCrumb(pht('Repository Servers')); 15 + 16 + $repository_status = $this->buildClusterRepositoryStatus(); 17 + 18 + $view = id(new PHUITwoColumnView()) 19 + ->setNavigation($nav) 20 + ->setMainColumn($repository_status); 21 + 22 + return $this->newPage() 23 + ->setTitle($title) 24 + ->setCrumbs($crumbs) 25 + ->appendChild($view); 26 + } 27 + 28 + private function buildClusterRepositoryStatus() { 29 + $viewer = $this->getViewer(); 30 + 31 + Javelin::initBehavior('phabricator-tooltips'); 32 + 33 + $all_services = id(new AlmanacServiceQuery()) 34 + ->setViewer($viewer) 35 + ->withServiceTypes( 36 + array( 37 + AlmanacClusterRepositoryServiceType::SERVICETYPE, 38 + )) 39 + ->needBindings(true) 40 + ->needProperties(true) 41 + ->execute(); 42 + $all_services = mpull($all_services, null, 'getPHID'); 43 + 44 + $all_repositories = id(new PhabricatorRepositoryQuery()) 45 + ->setViewer($viewer) 46 + ->withHosted(PhabricatorRepositoryQuery::HOSTED_PHABRICATOR) 47 + ->withTypes( 48 + array( 49 + PhabricatorRepositoryType::REPOSITORY_TYPE_GIT, 50 + )) 51 + ->execute(); 52 + $all_repositories = mpull($all_repositories, null, 'getPHID'); 53 + 54 + $all_versions = id(new PhabricatorRepositoryWorkingCopyVersion()) 55 + ->loadAll(); 56 + 57 + $all_devices = $this->getDevices($all_services, false); 58 + $all_active_devices = $this->getDevices($all_services, true); 59 + 60 + $leader_versions = $this->getLeaderVersionsByRepository( 61 + $all_repositories, 62 + $all_versions, 63 + $all_active_devices); 64 + 65 + $push_times = $this->loadLeaderPushTimes($leader_versions); 66 + 67 + $repository_groups = mgroup($all_repositories, 'getAlmanacServicePHID'); 68 + $repository_versions = mgroup($all_versions, 'getRepositoryPHID'); 69 + 70 + $rows = array(); 71 + foreach ($all_services as $service) { 72 + $service_phid = $service->getPHID(); 73 + 74 + if ($service->getAlmanacPropertyValue('closed')) { 75 + $status_icon = 'fa-folder'; 76 + $status_tip = pht('Closed'); 77 + } else { 78 + $status_icon = 'fa-folder-open green'; 79 + $status_tip = pht('Open'); 80 + } 81 + 82 + $status_icon = id(new PHUIIconView()) 83 + ->setIcon($status_icon) 84 + ->addSigil('has-tooltip') 85 + ->setMetadata( 86 + array( 87 + 'tip' => $status_tip, 88 + )); 89 + 90 + $devices = idx($all_devices, $service_phid, array()); 91 + $active_devices = idx($all_active_devices, $service_phid, array()); 92 + 93 + $device_icon = 'fa-server green'; 94 + 95 + $device_label = pht( 96 + '%s Active', 97 + phutil_count($active_devices)); 98 + 99 + $device_status = array( 100 + id(new PHUIIconView())->setIcon($device_icon), 101 + ' ', 102 + $device_label, 103 + ); 104 + 105 + $repositories = idx($repository_groups, $service_phid, array()); 106 + 107 + $repository_status = pht( 108 + '%s', 109 + phutil_count($repositories)); 110 + 111 + $no_leader = array(); 112 + $full_sync = array(); 113 + $partial_sync = array(); 114 + $no_sync = array(); 115 + $lag = array(); 116 + 117 + // Threshold in seconds before we start complaining that repositories 118 + // are not synchronized when there is only one leader. 119 + $threshold = phutil_units('5 minutes in seconds'); 120 + 121 + $messages = array(); 122 + 123 + foreach ($repositories as $repository) { 124 + $repository_phid = $repository->getPHID(); 125 + 126 + $leader_version = idx($leader_versions, $repository_phid); 127 + if ($leader_version === null) { 128 + $no_leader[] = $repository; 129 + $messages[] = pht( 130 + 'Repository %s has an ambiguous leader.', 131 + $viewer->renderHandle($repository_phid)->render()); 132 + continue; 133 + } 134 + 135 + $versions = idx($repository_versions, $repository_phid, array()); 136 + 137 + $leaders = 0; 138 + foreach ($versions as $version) { 139 + if ($version->getRepositoryVersion() == $leader_version) { 140 + $leaders++; 141 + } 142 + } 143 + 144 + if ($leaders == count($active_devices)) { 145 + $full_sync[] = $repository; 146 + } else { 147 + $push_epoch = idx($push_times, $repository_phid); 148 + if ($push_epoch) { 149 + $duration = (PhabricatorTime::getNow() - $push_epoch); 150 + $lag[] = $duration; 151 + } else { 152 + $duration = null; 153 + } 154 + 155 + if ($leaders >= 2 || ($duration && ($duration < $threshold))) { 156 + $partial_sync[] = $repository; 157 + } else { 158 + $no_sync[] = $repository; 159 + if ($push_epoch) { 160 + $messages[] = pht( 161 + 'Repository %s has unreplicated changes (for %s).', 162 + $viewer->renderHandle($repository_phid)->render(), 163 + phutil_format_relative_time($duration)); 164 + } else { 165 + $messages[] = pht( 166 + 'Repository %s has unreplicated changes.', 167 + $viewer->renderHandle($repository_phid)->render()); 168 + } 169 + } 170 + 171 + } 172 + } 173 + 174 + $with_lag = false; 175 + 176 + if ($no_leader) { 177 + $replication_icon = 'fa-times red'; 178 + $replication_label = pht('Ambiguous Leader'); 179 + } else if ($no_sync) { 180 + $replication_icon = 'fa-refresh yellow'; 181 + $replication_label = pht('Unsynchronized'); 182 + $with_lag = true; 183 + } else if ($partial_sync) { 184 + $replication_icon = 'fa-refresh green'; 185 + $replication_label = pht('Partial'); 186 + $with_lag = true; 187 + } else if ($full_sync) { 188 + $replication_icon = 'fa-check green'; 189 + $replication_label = pht('Synchronized'); 190 + } else { 191 + $replication_icon = 'fa-times grey'; 192 + $replication_label = pht('No Repositories'); 193 + } 194 + 195 + if ($with_lag && $lag) { 196 + $lag_status = phutil_format_relative_time(max($lag)); 197 + $lag_status = pht(' (%s)', $lag_status); 198 + } else { 199 + $lag_status = null; 200 + } 201 + 202 + $replication_status = array( 203 + id(new PHUIIconView())->setIcon($replication_icon), 204 + ' ', 205 + $replication_label, 206 + $lag_status, 207 + ); 208 + 209 + $messages = phutil_implode_html(phutil_tag('br'), $messages); 210 + 211 + $rows[] = array( 212 + $status_icon, 213 + $viewer->renderHandle($service->getPHID()), 214 + $device_status, 215 + $repository_status, 216 + $replication_status, 217 + $messages, 218 + ); 219 + } 220 + 221 + 222 + $table = id(new AphrontTableView($rows)) 223 + ->setNoDataString( 224 + pht('No repository cluster services are configured.')) 225 + ->setHeaders( 226 + array( 227 + null, 228 + pht('Service'), 229 + pht('Devices'), 230 + pht('Repos'), 231 + pht('Sync'), 232 + pht('Messages'), 233 + )) 234 + ->setColumnClasses( 235 + array( 236 + null, 237 + 'pri', 238 + null, 239 + null, 240 + null, 241 + 'wide', 242 + )); 243 + 244 + $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories'); 245 + 246 + $header = id(new PHUIHeaderView()) 247 + ->setHeader(pht('Cluster Repository Status')) 248 + ->addActionLink( 249 + id(new PHUIButtonView()) 250 + ->setIcon('fa-book') 251 + ->setHref($doc_href) 252 + ->setTag('a') 253 + ->setText(pht('Documentation'))); 254 + 255 + return id(new PHUIObjectBoxView()) 256 + ->setHeader($header) 257 + ->setTable($table); 258 + } 259 + 260 + private function getDevices( 261 + array $all_services, 262 + $only_active) { 263 + 264 + $devices = array(); 265 + foreach ($all_services as $service) { 266 + $map = array(); 267 + foreach ($service->getBindings() as $binding) { 268 + if ($only_active && $binding->getIsDisabled()) { 269 + continue; 270 + } 271 + 272 + $device = $binding->getDevice(); 273 + $device_phid = $device->getPHID(); 274 + 275 + $map[$device_phid] = $device; 276 + } 277 + $devices[$service->getPHID()] = $map; 278 + } 279 + 280 + return $devices; 281 + } 282 + 283 + private function getLeaderVersionsByRepository( 284 + array $all_repositories, 285 + array $all_versions, 286 + array $active_devices) { 287 + 288 + $version_map = mgroup($all_versions, 'getRepositoryPHID'); 289 + 290 + $result = array(); 291 + foreach ($all_repositories as $repository_phid => $repository) { 292 + $service_phid = $repository->getAlmanacServicePHID(); 293 + if (!$service_phid) { 294 + continue; 295 + } 296 + 297 + $devices = idx($active_devices, $service_phid); 298 + if (!$devices) { 299 + continue; 300 + } 301 + 302 + $versions = idx($version_map, $repository_phid, array()); 303 + $versions = mpull($versions, null, 'getDevicePHID'); 304 + $versions = array_select_keys($versions, array_keys($devices)); 305 + if (!$versions) { 306 + continue; 307 + } 308 + 309 + $leader = (int)max(mpull($versions, 'getRepositoryVersion')); 310 + $result[$repository_phid] = $leader; 311 + } 312 + 313 + return $result; 314 + } 315 + 316 + private function loadLeaderPushTimes(array $leader_versions) { 317 + $viewer = $this->getViewer(); 318 + 319 + if (!$leader_versions) { 320 + return array(); 321 + } 322 + 323 + $events = id(new PhabricatorRepositoryPushEventQuery()) 324 + ->setViewer($viewer) 325 + ->withIDs($leader_versions) 326 + ->execute(); 327 + $events = mpull($events, null, 'getID'); 328 + 329 + $result = array(); 330 + foreach ($leader_versions as $key => $version) { 331 + $event = idx($events, $version); 332 + if (!$event) { 333 + continue; 334 + } 335 + 336 + $result[$key] = $event->getEpoch(); 337 + } 338 + 339 + return $result; 340 + } 341 + 342 + 343 + }
+1
src/applications/config/controller/PhabricatorConfigController.php
··· 25 25 $nav->addLabel(pht('Cluster')); 26 26 $nav->addFilter('cluster/databases/', pht('Database Servers')); 27 27 $nav->addFilter('cluster/notifications/', pht('Notification Servers')); 28 + $nav->addFilter('cluster/repositories/', pht('Repository Servers')); 28 29 $nav->addLabel(pht('Welcome')); 29 30 $nav->addFilter('welcome/', pht('Welcome Screen')); 30 31 $nav->addLabel(pht('Modules'));
+34 -5
src/docs/user/cluster/cluster_repositories.diviner
··· 95 95 similar agents of other rogue nations is beyond the scope of this document. 96 96 97 97 98 - Monitoring Replication 99 - ====================== 98 + Monitoring Services 99 + =================== 100 + 101 + You can get an overview of repository cluster status from the 102 + {nav Config > Repository Servers} screen. This table shows a high-level 103 + overview of all active repository services. 104 + 105 + **Repos**: The number of repositories hosted on this service. 106 + 107 + **Sync**: Synchronization status of repositories on this service. This is an 108 + at-a-glance view of service health, and can show these values: 109 + 110 + - **Synchronized**: All nodes are fully synchronized and have the latest 111 + version of all repositories. 112 + - **Partial**: All repositories either have at least two leaders, or have 113 + a very recent write which is not expected to have propagated yet. 114 + - **Unsynchronized**: At least one repository has changes which are 115 + only available on one node and were not pushed very recently. Data may 116 + be at risk. 117 + - **No Repositories**: This service has no repositories. 118 + - **Ambiguous Leader**: At least one repository has an ambiguous leader. 119 + 120 + If this screen identifies problems, you can drill down into repository details 121 + to get more information about them. See the next section for details. 122 + 123 + 124 + Monitoring Repositories 125 + ======================= 100 126 101 - You can review the current status of a repository on cluster devices in 102 - {nav Diffusion > (Repository) > Manage Repository > Cluster Configuration}. 127 + You can get a more detailed view the current status of a specific repository on 128 + cluster devices in {nav Diffusion > (Repository) > Manage Repository > Cluster 129 + Configuration}. 103 130 104 131 This screen shows all the configured devices which are hosting the repository 105 - and the available version. 132 + and the available version on that device. 106 133 107 134 **Version**: When a repository is mutated by a push, Phabricator increases 108 135 an internal version number for the repository. This column shows which version ··· 129 156 130 157 **Last Write At**: When the most recent write started. If the write lock is 131 158 currently held, this shows when the lock was acquired. 159 + 160 + 132 161 133 162 134 163 Cluster Failure Modes