Page MenuHomePhabricator

D15685.diff
No OneTemporary

D15685.diff

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

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)

Event Timeline