2016-04-11 19:17:46 +02:00
|
|
|
<?php
|
|
|
|
|
2016-04-26 14:43:35 +02:00
|
|
|
final class DiffusionRepositoryStorageManagementPanel
|
2016-04-11 19:17:46 +02:00
|
|
|
extends DiffusionRepositoryManagementPanel {
|
|
|
|
|
2016-04-26 14:43:35 +02:00
|
|
|
const PANELKEY = 'storage';
|
2016-04-11 19:17:46 +02:00
|
|
|
|
|
|
|
public function getManagementPanelLabel() {
|
2016-04-26 14:43:35 +02:00
|
|
|
return pht('Storage');
|
2016-04-11 19:17:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getManagementPanelOrder() {
|
2016-04-17 02:33:27 +02:00
|
|
|
return 600;
|
2016-04-11 19:17:46 +02:00
|
|
|
}
|
|
|
|
|
2016-05-03 15:54:50 +02:00
|
|
|
public function getManagementPanelIcon() {
|
|
|
|
$repository = $this->getRepository();
|
|
|
|
|
|
|
|
if ($repository->getAlmanacServicePHID()) {
|
|
|
|
return 'fa-sitemap';
|
|
|
|
} else if ($repository->isHosted()) {
|
|
|
|
return 'fa-folder';
|
|
|
|
} else {
|
|
|
|
return 'fa-download';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-11 19:17:46 +02:00
|
|
|
public function buildManagementPanelContent() {
|
2016-04-26 14:43:35 +02:00
|
|
|
return array(
|
|
|
|
$this->buildStorageStatusPanel(),
|
|
|
|
$this->buildClusterStatusPanel(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function buildStorageStatusPanel() {
|
|
|
|
$repository = $this->getRepository();
|
|
|
|
$viewer = $this->getViewer();
|
|
|
|
|
|
|
|
$view = id(new PHUIPropertyListView())
|
|
|
|
->setViewer($viewer);
|
|
|
|
|
|
|
|
if ($repository->usesLocalWorkingCopy()) {
|
2016-05-03 21:22:40 +02:00
|
|
|
$storage_path = $repository->getLocalPath();
|
2016-04-26 14:43:35 +02:00
|
|
|
} else {
|
|
|
|
$storage_path = phutil_tag('em', array(), pht('No Local Working Copy'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$service_phid = $repository->getAlmanacServicePHID();
|
|
|
|
if ($service_phid) {
|
|
|
|
$storage_service = $viewer->renderHandle($service_phid);
|
|
|
|
} else {
|
|
|
|
$storage_service = phutil_tag('em', array(), pht('Local'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$view->addProperty(pht('Storage Path'), $storage_path);
|
|
|
|
$view->addProperty(pht('Storage Cluster'), $storage_service);
|
|
|
|
|
|
|
|
$header = id(new PHUIHeaderView())
|
|
|
|
->setHeader(pht('Storage'));
|
|
|
|
|
|
|
|
return id(new PHUIObjectBoxView())
|
|
|
|
->setHeader($header)
|
|
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
|
|
|
->addPropertyList($view);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function buildClusterStatusPanel() {
|
2016-04-11 20:37:41 +02:00
|
|
|
$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');
|
|
|
|
|
Move toward multi-master replicated repositories
Summary:
Ref T4292. This mostly implements the locking/versioning logic for multi-master repositories. It is only active on Git SSH pathways, and doesn't actually do anything useful yet: it just does bookkeeping so far.
When we read (e.g., `git fetch`) the logic goes like this:
- Get the read lock (unique to device + repository).
- Read all the versions of the repository on every other device.
- If any node has a newer version:
- Fetch the newer version.
- Increment our version to be the same as the version we fetched.
- Release the read lock.
- Actually do the fetch.
This makes sure that any time you do a read, you always read the most recently acknowledged write. You may have to wait for an internal fetch to happen (this isn't actually implemented yet) but the operation will always work like you expect it to.
When we write (e.g., `git push`) the logic goes like this:
- Get the write lock (unique to the repository).
- Do all the read steps so we're up to date.
- Mark a write pending.
- Do the actual write.
- Bump our version and mark our write finished.
- Release the write lock.
This allows you to write to any replica. Again, you might have to wait for a fetch first, but everything will work like you expect.
There's one notable failure mode here: if the network connection between the repository node and the database fails during the write, the write lock might be released even though a write is ongoing.
The "isWriting" column protects against that, by staying locked if we lose our connection to the database. This will currently "freeze" the repository (prevent any new writes) until an administrator can sort things out, since it'd dangerous to continue doing writes (we may lose data).
(Since we won't actually acknowledge the write, I think, we could probably smooth this out a bit and make it self-healing //most// of the time: basically, have the broken node rewind itself by updating from another good node. But that's a little more complex.)
Test Plan:
- Pushed changes to a cluster-mode repository.
- Viewed web interface, saw "writing" flag and version changes.
- Pulled changes.
- Faked various failures, got sensible states.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4292
Differential Revision: https://secure.phabricator.com/D15688
2016-04-11 20:58:02 +02:00
|
|
|
// This is an unusual read which always comes from the master.
|
|
|
|
if (PhabricatorEnv::isReadOnly()) {
|
|
|
|
$versions = array();
|
|
|
|
} else {
|
|
|
|
$versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
|
|
|
|
$repository->getPHID());
|
|
|
|
}
|
|
|
|
|
|
|
|
$versions = mpull($versions, null, 'getDevicePHID');
|
|
|
|
|
2016-04-11 20:37:41 +02:00
|
|
|
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();
|
|
|
|
|
Move toward multi-master replicated repositories
Summary:
Ref T4292. This mostly implements the locking/versioning logic for multi-master repositories. It is only active on Git SSH pathways, and doesn't actually do anything useful yet: it just does bookkeeping so far.
When we read (e.g., `git fetch`) the logic goes like this:
- Get the read lock (unique to device + repository).
- Read all the versions of the repository on every other device.
- If any node has a newer version:
- Fetch the newer version.
- Increment our version to be the same as the version we fetched.
- Release the read lock.
- Actually do the fetch.
This makes sure that any time you do a read, you always read the most recently acknowledged write. You may have to wait for an internal fetch to happen (this isn't actually implemented yet) but the operation will always work like you expect it to.
When we write (e.g., `git push`) the logic goes like this:
- Get the write lock (unique to the repository).
- Do all the read steps so we're up to date.
- Mark a write pending.
- Do the actual write.
- Bump our version and mark our write finished.
- Release the write lock.
This allows you to write to any replica. Again, you might have to wait for a fetch first, but everything will work like you expect.
There's one notable failure mode here: if the network connection between the repository node and the database fails during the write, the write lock might be released even though a write is ongoing.
The "isWriting" column protects against that, by staying locked if we lose our connection to the database. This will currently "freeze" the repository (prevent any new writes) until an administrator can sort things out, since it'd dangerous to continue doing writes (we may lose data).
(Since we won't actually acknowledge the write, I think, we could probably smooth this out a bit and make it self-healing //most// of the time: basically, have the broken node rewind itself by updating from another good node. But that's a little more complex.)
Test Plan:
- Pushed changes to a cluster-mode repository.
- Viewed web interface, saw "writing" flag and version changes.
- Pulled changes.
- Faked various failures, got sensible states.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4292
Differential Revision: https://secure.phabricator.com/D15688
2016-04-11 20:58:02 +02:00
|
|
|
$version = idx($versions, $device->getPHID());
|
|
|
|
if ($version) {
|
|
|
|
$version_number = $version->getRepositoryVersion();
|
|
|
|
$version_number = phutil_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => "/diffusion/pushlog/view/{$version_number}/",
|
|
|
|
),
|
|
|
|
$version_number);
|
|
|
|
} else {
|
|
|
|
$version_number = '-';
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($version && $version->getIsWriting()) {
|
|
|
|
$is_writing = id(new PHUIIconView())
|
|
|
|
->setIcon('fa-pencil green');
|
|
|
|
} else {
|
|
|
|
$is_writing = id(new PHUIIconView())
|
|
|
|
->setIcon('fa-pencil grey');
|
|
|
|
}
|
|
|
|
|
2016-04-20 14:05:49 +02:00
|
|
|
$write_properties = null;
|
|
|
|
if ($version) {
|
|
|
|
$write_properties = $version->getWriteProperties();
|
|
|
|
if ($write_properties) {
|
|
|
|
try {
|
|
|
|
$write_properties = phutil_json_decode($write_properties);
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
$write_properties = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($write_properties) {
|
|
|
|
$writer_phid = idx($write_properties, 'userPHID');
|
|
|
|
$last_writer = $viewer->renderHandle($writer_phid);
|
|
|
|
|
|
|
|
$writer_epoch = idx($write_properties, 'epoch');
|
|
|
|
$writer_epoch = phabricator_datetime($writer_epoch, $viewer);
|
|
|
|
} else {
|
|
|
|
$last_writer = null;
|
|
|
|
$writer_epoch = null;
|
|
|
|
}
|
|
|
|
|
2016-04-11 20:37:41 +02:00
|
|
|
$rows[] = array(
|
|
|
|
$binding_icon,
|
|
|
|
phutil_tag(
|
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => $device->getURI(),
|
|
|
|
),
|
|
|
|
$device->getName()),
|
Move toward multi-master replicated repositories
Summary:
Ref T4292. This mostly implements the locking/versioning logic for multi-master repositories. It is only active on Git SSH pathways, and doesn't actually do anything useful yet: it just does bookkeeping so far.
When we read (e.g., `git fetch`) the logic goes like this:
- Get the read lock (unique to device + repository).
- Read all the versions of the repository on every other device.
- If any node has a newer version:
- Fetch the newer version.
- Increment our version to be the same as the version we fetched.
- Release the read lock.
- Actually do the fetch.
This makes sure that any time you do a read, you always read the most recently acknowledged write. You may have to wait for an internal fetch to happen (this isn't actually implemented yet) but the operation will always work like you expect it to.
When we write (e.g., `git push`) the logic goes like this:
- Get the write lock (unique to the repository).
- Do all the read steps so we're up to date.
- Mark a write pending.
- Do the actual write.
- Bump our version and mark our write finished.
- Release the write lock.
This allows you to write to any replica. Again, you might have to wait for a fetch first, but everything will work like you expect.
There's one notable failure mode here: if the network connection between the repository node and the database fails during the write, the write lock might be released even though a write is ongoing.
The "isWriting" column protects against that, by staying locked if we lose our connection to the database. This will currently "freeze" the repository (prevent any new writes) until an administrator can sort things out, since it'd dangerous to continue doing writes (we may lose data).
(Since we won't actually acknowledge the write, I think, we could probably smooth this out a bit and make it self-healing //most// of the time: basically, have the broken node rewind itself by updating from another good node. But that's a little more complex.)
Test Plan:
- Pushed changes to a cluster-mode repository.
- Viewed web interface, saw "writing" flag and version changes.
- Pulled changes.
- Faked various failures, got sensible states.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4292
Differential Revision: https://secure.phabricator.com/D15688
2016-04-11 20:58:02 +02:00
|
|
|
$version_number,
|
|
|
|
$is_writing,
|
2016-04-20 14:05:49 +02:00
|
|
|
$last_writer,
|
|
|
|
$writer_epoch,
|
2016-04-11 20:37:41 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$table = id(new AphrontTableView($rows))
|
|
|
|
->setNoDataString(pht('This is not a cluster repository.'))
|
|
|
|
->setHeaders(
|
|
|
|
array(
|
|
|
|
null,
|
|
|
|
pht('Device'),
|
Move toward multi-master replicated repositories
Summary:
Ref T4292. This mostly implements the locking/versioning logic for multi-master repositories. It is only active on Git SSH pathways, and doesn't actually do anything useful yet: it just does bookkeeping so far.
When we read (e.g., `git fetch`) the logic goes like this:
- Get the read lock (unique to device + repository).
- Read all the versions of the repository on every other device.
- If any node has a newer version:
- Fetch the newer version.
- Increment our version to be the same as the version we fetched.
- Release the read lock.
- Actually do the fetch.
This makes sure that any time you do a read, you always read the most recently acknowledged write. You may have to wait for an internal fetch to happen (this isn't actually implemented yet) but the operation will always work like you expect it to.
When we write (e.g., `git push`) the logic goes like this:
- Get the write lock (unique to the repository).
- Do all the read steps so we're up to date.
- Mark a write pending.
- Do the actual write.
- Bump our version and mark our write finished.
- Release the write lock.
This allows you to write to any replica. Again, you might have to wait for a fetch first, but everything will work like you expect.
There's one notable failure mode here: if the network connection between the repository node and the database fails during the write, the write lock might be released even though a write is ongoing.
The "isWriting" column protects against that, by staying locked if we lose our connection to the database. This will currently "freeze" the repository (prevent any new writes) until an administrator can sort things out, since it'd dangerous to continue doing writes (we may lose data).
(Since we won't actually acknowledge the write, I think, we could probably smooth this out a bit and make it self-healing //most// of the time: basically, have the broken node rewind itself by updating from another good node. But that's a little more complex.)
Test Plan:
- Pushed changes to a cluster-mode repository.
- Viewed web interface, saw "writing" flag and version changes.
- Pulled changes.
- Faked various failures, got sensible states.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4292
Differential Revision: https://secure.phabricator.com/D15688
2016-04-11 20:58:02 +02:00
|
|
|
pht('Version'),
|
|
|
|
pht('Writing'),
|
2016-04-20 14:05:49 +02:00
|
|
|
pht('Last Writer'),
|
|
|
|
pht('Last Write At'),
|
2016-04-11 20:37:41 +02:00
|
|
|
))
|
|
|
|
->setColumnClasses(
|
|
|
|
array(
|
|
|
|
null,
|
Move toward multi-master replicated repositories
Summary:
Ref T4292. This mostly implements the locking/versioning logic for multi-master repositories. It is only active on Git SSH pathways, and doesn't actually do anything useful yet: it just does bookkeeping so far.
When we read (e.g., `git fetch`) the logic goes like this:
- Get the read lock (unique to device + repository).
- Read all the versions of the repository on every other device.
- If any node has a newer version:
- Fetch the newer version.
- Increment our version to be the same as the version we fetched.
- Release the read lock.
- Actually do the fetch.
This makes sure that any time you do a read, you always read the most recently acknowledged write. You may have to wait for an internal fetch to happen (this isn't actually implemented yet) but the operation will always work like you expect it to.
When we write (e.g., `git push`) the logic goes like this:
- Get the write lock (unique to the repository).
- Do all the read steps so we're up to date.
- Mark a write pending.
- Do the actual write.
- Bump our version and mark our write finished.
- Release the write lock.
This allows you to write to any replica. Again, you might have to wait for a fetch first, but everything will work like you expect.
There's one notable failure mode here: if the network connection between the repository node and the database fails during the write, the write lock might be released even though a write is ongoing.
The "isWriting" column protects against that, by staying locked if we lose our connection to the database. This will currently "freeze" the repository (prevent any new writes) until an administrator can sort things out, since it'd dangerous to continue doing writes (we may lose data).
(Since we won't actually acknowledge the write, I think, we could probably smooth this out a bit and make it self-healing //most// of the time: basically, have the broken node rewind itself by updating from another good node. But that's a little more complex.)
Test Plan:
- Pushed changes to a cluster-mode repository.
- Viewed web interface, saw "writing" flag and version changes.
- Pulled changes.
- Faked various failures, got sensible states.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4292
Differential Revision: https://secure.phabricator.com/D15688
2016-04-11 20:58:02 +02:00
|
|
|
null,
|
|
|
|
null,
|
|
|
|
'right wide',
|
2016-04-20 14:05:49 +02:00
|
|
|
null,
|
|
|
|
'date',
|
2016-04-11 20:37:41 +02:00
|
|
|
));
|
|
|
|
|
|
|
|
$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')));
|
|
|
|
|
|
|
|
return id(new PHUIObjectBoxView())
|
|
|
|
->setHeader($header)
|
2016-04-17 14:10:18 +02:00
|
|
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
2016-04-11 20:37:41 +02:00
|
|
|
->setTable($table);
|
2016-04-11 19:17:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|