Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14843462
D15685.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
D15685.diff
View Options
diff --git a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php
--- a/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php
+++ b/src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php
@@ -203,7 +203,7 @@
->setIcon('fa-book')
->setHref($doc_href)
->setTag('a')
- ->setText(pht('Database Clustering Documentation')));
+ ->setText(pht('Documentation')));
return id(new PHUIObjectBoxView())
->setHeader($header)
diff --git a/src/applications/diffusion/management/DiffusionRepositoryClusterManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryClusterManagementPanel.php
--- a/src/applications/diffusion/management/DiffusionRepositoryClusterManagementPanel.php
+++ b/src/applications/diffusion/management/DiffusionRepositoryClusterManagementPanel.php
@@ -14,7 +14,116 @@
}
public function buildManagementPanelContent() {
- return pht('TODO: Cluster configuration management.');
+ $repository = $this->getRepository();
+ $viewer = $this->getViewer();
+
+ $service_phid = $repository->getAlmanacServicePHID();
+ if ($service_phid) {
+ $service = id(new AlmanacServiceQuery())
+ ->setViewer($viewer)
+ ->withServiceTypes(
+ array(
+ AlmanacClusterRepositoryServiceType::SERVICETYPE,
+ ))
+ ->withPHIDs(array($service_phid))
+ ->needBindings(true)
+ ->executeOne();
+ if (!$service) {
+ // TODO: Viewer may not have permission to see the service, or it may
+ // be invalid? Raise some more useful error here?
+ throw new Exception(pht('Unable to load cluster service.'));
+ }
+ } else {
+ $service = null;
+ }
+
+ Javelin::initBehavior('phabricator-tooltips');
+
+ $rows = array();
+ if ($service) {
+ $bindings = $service->getBindings();
+ $bindings = mgroup($bindings, 'getDevicePHID');
+
+ foreach ($bindings as $binding_group) {
+ $all_disabled = true;
+ foreach ($binding_group as $binding) {
+ if (!$binding->getIsDisabled()) {
+ $all_disabled = false;
+ break;
+ }
+ }
+
+ $any_binding = head($binding_group);
+
+ if ($all_disabled) {
+ $binding_icon = 'fa-times grey';
+ $binding_tip = pht('Disabled');
+ } else {
+ $binding_icon = 'fa-folder-open green';
+ $binding_tip = pht('Active');
+ }
+
+ $binding_icon = id(new PHUIIconView())
+ ->setIcon($binding_icon)
+ ->addSigil('has-tooltip')
+ ->setMetadata(
+ array(
+ 'tip' => $binding_tip,
+ ));
+
+ $device = $any_binding->getDevice();
+
+ $rows[] = array(
+ $binding_icon,
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => $device->getURI(),
+ ),
+ $device->getName()),
+ );
+ }
+ }
+
+ $table = id(new AphrontTableView($rows))
+ ->setNoDataString(pht('This is not a cluster repository.'))
+ ->setHeaders(
+ array(
+ null,
+ pht('Device'),
+ ))
+ ->setColumnClasses(
+ array(
+ null,
+ 'wide',
+ ));
+
+ $doc_href = PhabricatorEnv::getDoclink('Cluster: Repositories');
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader(pht('Cluster Status'))
+ ->addActionLink(
+ id(new PHUIButtonView())
+ ->setIcon('fa-book')
+ ->setHref($doc_href)
+ ->setTag('a')
+ ->setText(pht('Documentation')));
+
+ if ($service) {
+ $header->setSubheader(
+ pht(
+ 'This repository is hosted on %s.',
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => $service->getURI(),
+ ),
+ $service->getName())));
+ }
+
+ return id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->setTable($table);
}
}
diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
--- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
+++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
@@ -398,6 +398,10 @@
$services = id(new AlmanacServiceQuery())
->setViewer($this->getViewer())
->withPHIDs($service_phids)
+ ->withServiceTypes(
+ array(
+ AlmanacClusterRepositoryServiceType::SERVICETYPE,
+ ))
->needBindings(true)
->execute();
$services = mpull($services, null, 'getPHID');
@@ -422,9 +426,9 @@
}
$bindings = $service->getBindings();
- $bindings = mpull($bindings, null, 'getDevicePHID');
- $binding = idx($bindings, $device_phid);
- if (!$binding) {
+ $bindings = mgroup($bindings, 'getDevicePHID');
+ $bindings = idx($bindings, $device_phid);
+ if (!$bindings) {
$this->log(
pht(
'Repository "%s" is on cluster service "%s", but that service '.
@@ -437,7 +441,15 @@
continue;
}
- if ($binding->getIsDisabled()) {
+ $all_disabled = true;
+ foreach ($bindings as $binding) {
+ if (!$binding->getIsDisabled()) {
+ $all_disabled = false;
+ break;
+ }
+ }
+
+ if ($all_disabled) {
$this->log(
pht(
'Repository "%s" is on cluster service "%s", but the binding '.
diff --git a/src/docs/user/cluster/cluster.diviner b/src/docs/user/cluster/cluster.diviner
--- a/src/docs/user/cluster/cluster.diviner
+++ b/src/docs/user/cluster/cluster.diviner
@@ -26,6 +26,7 @@
The remainder of this document summarizes how to add redundancy to each
service and where your efforts are likely to have the greatest impact.
+
Cluster: Databases
=================
@@ -38,3 +39,19 @@
the master, and to quickly promote the replica as a replacement.
For details, see @{article:Cluster: Databases}.
+
+
+Cluster: Repositories
+=====================
+
+Configuring multiple repository hosts is complex.
+
+Repository replicas are important for availability if you host repositories
+on Phabricator, but less important if you host repositories elsewhere
+(instead, you should focus on making that service more available).
+
+The distributed nature of Git and Mercurial tend to mean that they are
+naturally somewhat resistant to data loss: every clone of a repository includes
+the entire history.
+
+For details, see @{article:Cluster: Repositories}.
diff --git a/src/docs/user/cluster/cluster_repositories.diviner b/src/docs/user/cluster/cluster_repositories.diviner
new file mode 100644
--- /dev/null
+++ b/src/docs/user/cluster/cluster_repositories.diviner
@@ -0,0 +1,86 @@
+@title Cluster: Repositories
+@group intro
+
+Configuring Phabricator to use multiple repository hosts.
+
+Overview
+========
+
+WARNING: This feature is a very early prototype; the features this document
+describes are mostly speculative fantasy.
+
+If you use Git or Mercurial, you can deploy Phabricator with multiple
+repository hosts, configured so that each host is readable and writable. The
+advantages of doing this are:
+
+ - you can completely survive the loss of repository hosts;
+ - reads and writes can scale across multiple machines; and
+ - read and write performance across multiple geographic regions may improve.
+
+This configuration is complex, and many installs do not need to pursue it.
+
+This configuration is not currently supported with Subversion.
+
+
+Repository Hosts
+================
+
+Repository hosts must run a complete, fully configured copy of Phabricator,
+including a webserver. If you make repositories available over SSH, they must
+also run a properly configured `sshd`.
+
+Generally, these hosts will run the same set of services and configuration that
+web hosts run. If you prefer, you can overlay these services and put web and
+repository services on the same hosts.
+
+When a user requests information about a repository that can only be satisfied
+by examining a repository working copy, the webserver receiving the reqeust
+will make an HTTP service call to a repository server which hosts the
+repository to retrieve the data it needs. It will use the result of this query
+to respond to the user.
+
+
+How Reads and Writes Work
+=========================
+
+Phabricator repository replicas are multi-master: every node is readable and
+writable, and a cluster of nodes can (almost always) survive the loss of any
+arbitrary subset of nodes so long as at least one node is still alive.
+
+Phabricator maintains an internal version for each repository, and increments
+it when the repository is mutated.
+
+Before responding to a read, replicas make sure their version of the repository
+is up to date (no node in the cluster has a newer version of the repository).
+If it isn't, they block the read until they can complete a fetch.
+
+Before responding to a write, replicas obtain a global lock, perform the same
+version check and fetch if necessary, then allow the write to continue.
+
+
+Backups
+======
+
+Even if you configure clustering, you should still consider retaining separate
+backup snapshots. Replicas protect you from data loss if you lose a host, but
+they do not let you rewind time to recover from data mutation mistakes.
+
+If something issues a `--force` push that destroys branch heads, the mutation
+will propagate to the replicas.
+
+You may be able to manually restore the branches by using tools like the
+Phabricator push log or the Git reflog so it is less important to retain
+repository snapshots than database snapshots, but it is still possible for
+data to be lost permanently, especially if you don't notice the problem for
+some time.
+
+Retaining separate backup snapshots will improve your ability to recover more
+data more easily in a wider range of disaster situations.
+
+
+Next Steps
+==========
+
+Continue by:
+
+ - returning to @{article:Clustering Introduction}.
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Feb 3, 7:00 AM (14 h, 49 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7086443
Default Alt Text
D15685.diff (10 KB)
Attached To
Mode
D15685: Rough cut of repository cluster status panel
Attached
Detach File
Event Timeline
Log In to Comment