1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Allow Almanac bindings to be disabled and unused interfaces to be removed

Summary:
Fixes T9762. Ref T10246.

**Disabling Bindings**: Previously, there was no formal way to disable bindings. The internal callers sometimes check some informal property on the binding, but this is a common need and deserves first-class support in the UI. Allow bindings to be disabled.

**Deleting Interfaces**: Previously, you could not delete interfaces. Now, you can delete unused interfaces.

Also some minor cleanup and slightly less mysterious documentation.

Test Plan: Disabled bindings and deleted interfaces.

Reviewers: chad

Reviewed By: chad

Subscribers: yelirekim

Maniphest Tasks: T9762, T10246

Differential Revision: https://secure.phabricator.com/D15345
This commit is contained in:
epriestley 2016-02-25 03:52:41 -08:00
parent 5b9d8aeae7
commit 4c97d88aa4
19 changed files with 357 additions and 14 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_almanac.almanac_binding
ADD isDisabled BOOL NOT NULL;

View file

@ -11,6 +11,7 @@ phutil_register_library_map(array(
'class' => array(
'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php',
'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php',
'AlmanacBindingDisableController' => 'applications/almanac/controller/AlmanacBindingDisableController.php',
'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php',
'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php',
'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php',
@ -51,6 +52,7 @@ phutil_register_library_map(array(
'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php',
'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php',
'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php',
'AlmanacInterfaceDeleteController' => 'applications/almanac/controller/AlmanacInterfaceDeleteController.php',
'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php',
'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php',
'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php',
@ -3996,6 +3998,7 @@ phutil_register_library_map(array(
'PhabricatorDestructibleInterface',
'PhabricatorExtendedPolicyInterface',
),
'AlmanacBindingDisableController' => 'AlmanacServiceController',
'AlmanacBindingEditController' => 'AlmanacServiceController',
'AlmanacBindingEditor' => 'AlmanacEditor',
'AlmanacBindingPHIDType' => 'PhabricatorPHIDType',
@ -4049,8 +4052,10 @@ phutil_register_library_map(array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
'PhabricatorExtendedPolicyInterface',
),
'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource',
'AlmanacInterfaceDeleteController' => 'AlmanacDeviceController',
'AlmanacInterfaceEditController' => 'AlmanacDeviceController',
'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType',
'AlmanacInterfaceQuery' => 'AlmanacQuery',

View file

@ -55,9 +55,11 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
),
'interface/' => array(
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacInterfaceEditController',
'delete/(?:(?P<id>\d+)/)?' => 'AlmanacInterfaceDeleteController',
),
'binding/' => array(
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacBindingEditController',
'disable/(?:(?P<id>\d+)/)?' => 'AlmanacBindingDisableController',
'(?P<id>\d+)/' => 'AlmanacBindingViewController',
),
'network/' => array(
@ -80,6 +82,17 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
}
protected function getCustomCapabilities() {
$cluster_caption = pht(
'This permission is very dangerous. %s',
phutil_tag(
'a',
array(
'href' => PhabricatorEnv::getDoclink(
'User Guide: Phabricator Clusters'),
'target' => '_blank',
),
pht('Learn More')));
return array(
AlmanacCreateServicesCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
@ -94,7 +107,8 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
'default' => PhabricatorPolicies::POLICY_ADMIN,
),
AlmanacManageClusterServicesCapability::CAPABILITY => array(
'default' => PhabricatorPolicies::POLICY_ADMIN,
'default' => PhabricatorPolicies::POLICY_NOONE,
'caption' => $cluster_caption,
),
);
}

View file

@ -0,0 +1,69 @@
<?php
final class AlmanacBindingDisableController
extends AlmanacServiceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$binding = id(new AlmanacBindingQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$binding) {
return new Aphront404Response();
}
$id = $binding->getID();
$is_disable = !$binding->getIsDisabled();
$done_uri = $binding->getURI();
if ($is_disable) {
$disable_title = pht('Disable Binding');
$disable_body = pht('Disable this binding?');
$disable_button = pht('Disable Binding');
$v_disable = 1;
} else {
$disable_title = pht('Enable Binding');
$disable_body = pht('Enable this binding?');
$disable_button = pht('Enable Binding');
$v_disable = 0;
}
if ($request->isFormPost()) {
$type_disable = AlmanacBindingTransaction::TYPE_DISABLE;
$xactions = array();
$xactions[] = id(new AlmanacBindingTransaction())
->setTransactionType($type_disable)
->setNewValue($v_disable);
$editor = id(new AlmanacBindingEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($binding, $xactions);
return id(new AphrontRedirectResponse())->setURI($done_uri);
}
return $this->newDialog()
->setTitle($disable_title)
->appendParagraph($disable_body)
->addSubmitButton($disable_button)
->addCancelButton($done_uri);
}
}

View file

@ -35,6 +35,10 @@ final class AlmanacBindingViewController
->setHeader($title)
->setPolicyObject($binding);
if ($binding->getIsDisabled()) {
$header->setStatus('fa-ban', 'red', pht('Disabled'));
}
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($property_list);
@ -114,6 +118,24 @@ final class AlmanacBindingViewController
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit));
if ($binding->getIsDisabled()) {
$disable_icon = 'fa-check';
$disable_text = pht('Enable Binding');
} else {
$disable_icon = 'fa-ban';
$disable_text = pht('Disable Binding');
}
$disable_href = $this->getApplicationURI("binding/disable/{$id}/");
$actions->addAction(
id(new PhabricatorActionView())
->setIcon($disable_icon)
->setName($disable_text)
->setHref($disable_href)
->setWorkflow(true)
->setDisabled(!$can_edit));
return $actions;
}

View file

@ -177,7 +177,8 @@ abstract class AlmanacController
$doc_link = phutil_tag(
'a',
array(
'href' => PhabricatorEnv::getDoclink('Almanac User Guide'),
'href' => PhabricatorEnv::getDoclink(
'User Guide: Phabricator Clusters'),
'target' => '_blank',
),
pht('Learn More'));

View file

@ -0,0 +1,72 @@
<?php
final class AlmanacInterfaceDeleteController
extends AlmanacDeviceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$interface = id(new AlmanacInterfaceQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$interface) {
return new Aphront404Response();
}
$device = $interface->getDevice();
$device_uri = $device->getURI();
if ($interface->loadIsInUse()) {
return $this->newDialog()
->setTitle(pht('Interface In Use'))
->appendParagraph(
pht(
'You can not delete this interface because it is currently in '.
'use. One or more services are bound to it.'))
->addCancelButton($device_uri);
}
if ($request->isFormPost()) {
$type_interface = AlmanacDeviceTransaction::TYPE_INTERFACE;
$xactions = array();
$v_old = array(
'id' => $interface->getID(),
) + $interface->toAddress()->toDictionary();
$xactions[] = id(new AlmanacDeviceTransaction())
->setTransactionType($type_interface)
->setOldValue($v_old)
->setNewValue(null);
$editor = id(new AlmanacDeviceEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($device, $xactions);
return id(new AphrontRedirectResponse())->setURI($device_uri);
}
return $this->newDialog()
->setTitle(pht('Delete Interface'))
->appendParagraph(
pht(
'Remove interface %s on device %s?',
phutil_tag('strong', array(), $interface->renderDisplayAddress()),
phutil_tag('strong', array(), $device->getName())))
->addCancelButton($device_uri)
->addSubmitButton(pht('Delete Interface'));
}
}

View file

@ -122,7 +122,8 @@ final class AlmanacServiceViewController
->setNoDataString(
pht('This service has not been bound to any device interfaces yet.'))
->setUser($viewer)
->setBindings($bindings);
->setBindings($bindings)
->setHideServiceColumn(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Service Bindings'))

View file

@ -13,6 +13,7 @@ final class AlmanacBindingEditor
$types = parent::getTransactionTypes();
$types[] = AlmanacBindingTransaction::TYPE_INTERFACE;
$types[] = AlmanacBindingTransaction::TYPE_DISABLE;
return $types;
}
@ -23,6 +24,8 @@ final class AlmanacBindingEditor
switch ($xaction->getTransactionType()) {
case AlmanacBindingTransaction::TYPE_INTERFACE:
return $object->getInterfacePHID();
case AlmanacBindingTransaction::TYPE_DISABLE:
return $object->getIsDisabled();
}
return parent::getCustomTransactionOldValue($object, $xaction);
@ -35,6 +38,8 @@ final class AlmanacBindingEditor
switch ($xaction->getTransactionType()) {
case AlmanacBindingTransaction::TYPE_INTERFACE:
return $xaction->getNewValue();
case AlmanacBindingTransaction::TYPE_DISABLE:
return (int)$xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
@ -53,6 +58,9 @@ final class AlmanacBindingEditor
$object->setDevicePHID($interface->getDevicePHID());
$object->setInterfacePHID($interface->getPHID());
return;
case AlmanacBindingTransaction::TYPE_DISABLE:
$object->setIsDisabled($xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
@ -63,6 +71,8 @@ final class AlmanacBindingEditor
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case AlmanacBindingTransaction::TYPE_DISABLE:
return;
case AlmanacBindingTransaction::TYPE_INTERFACE:
$interface_phids = array();

View file

@ -310,6 +310,19 @@ final class AlmanacDeviceEditor
pht('You can not edit an invalid or restricted interface.'),
$xaction);
$errors[] = $error;
continue;
}
$new = $xaction->getNewValue();
if (!$new) {
if ($interface->loadIsInUse()) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('In Use'),
pht('You can not delete an interface which is still in use.'),
$xaction);
$errors[] = $error;
}
}
}
}

View file

@ -28,6 +28,7 @@ abstract class AlmanacSearchEngineAttachment
'phid' => $binding->getPHID(),
'properties' => $this->getAlmanacPropertyList($binding),
'interface' => $this->getAlmanacInterfaceDictionary($interface),
'disabled' => (bool)$binding->getIsDisabled(),
);
}

View file

@ -13,6 +13,7 @@ final class AlmanacBinding
protected $devicePHID;
protected $interfacePHID;
protected $mailKey;
protected $isDisabled;
private $service = self::ATTACHABLE;
private $device = self::ATTACHABLE;
@ -22,7 +23,8 @@ final class AlmanacBinding
public static function initializeNewBinding(AlmanacService $service) {
return id(new AlmanacBinding())
->setServicePHID($service->getPHID())
->attachAlmanacProperties(array());
->attachAlmanacProperties(array())
->setIsDisabled(0);
}
protected function getConfiguration() {
@ -30,6 +32,7 @@ final class AlmanacBinding
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'mailKey' => 'bytes20',
'isDisabled' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_service' => array(

View file

@ -4,6 +4,7 @@ final class AlmanacBindingTransaction
extends AlmanacTransaction {
const TYPE_INTERFACE = 'almanac:binding:interface';
const TYPE_DISABLE = 'almanac:binding:disable';
public function getApplicationName() {
return 'almanac';
@ -57,6 +58,17 @@ final class AlmanacBindingTransaction
$this->renderHandleLink($new));
}
break;
case self::TYPE_DISABLE:
if ($new) {
return pht(
'%s disabled this binding.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s enabled this binding.',
$this->renderHandleLink($author_phid));
}
break;
}
return parent::getTitle();

View file

@ -69,7 +69,7 @@ final class AlmanacDeviceTransaction
return pht(
'%s removed the interface %s from this device.',
$this->renderHandleLink($author_phid),
$this->describeInterface($new));
$this->describeInterface($old));
} else if ($new) {
return pht(
'%s added the interface %s to this device.',

View file

@ -4,7 +4,8 @@ final class AlmanacInterface
extends AlmanacDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
PhabricatorDestructibleInterface,
PhabricatorExtendedPolicyInterface {
protected $devicePHID;
protected $networkPHID;
@ -74,6 +75,16 @@ final class AlmanacInterface
return $this->getAddress().':'.$this->getPort();
}
public function loadIsInUse() {
$binding = id(new AlmanacBindingQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withInterfacePHIDs(array($this->getPHID()))
->setLimit(1)
->executeOne();
return (bool)$binding;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -105,6 +116,27 @@ final class AlmanacInterface
}
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_EDIT:
if ($this->getDevice()->isClusterDevice()) {
return array(
array(
new PhabricatorAlmanacApplication(),
AlmanacManageClusterServicesCapability::CAPABILITY,
),
);
}
break;
}
return array();
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */

View file

@ -5,6 +5,8 @@ final class AlmanacBindingTableView extends AphrontView {
private $bindings;
private $noDataString;
private $hideServiceColumn;
public function setNoDataString($no_data_string) {
$this->noDataString = $no_data_string;
return $this;
@ -23,6 +25,15 @@ final class AlmanacBindingTableView extends AphrontView {
return $this->bindings;
}
public function setHideServiceColumn($hide_service_column) {
$this->hideServiceColumn = $hide_service_column;
return $this;
}
public function getHideServiceColumn() {
return $this->hideServiceColumn;
}
public function render() {
$bindings = $this->getBindings();
$viewer = $this->getUser();
@ -35,6 +46,22 @@ final class AlmanacBindingTableView extends AphrontView {
}
$handles = $viewer->loadHandles($phids);
$icon_disabled = id(new PHUIIconView())
->setIcon('fa-ban')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Disabled'),
));
$icon_active = id(new PHUIIconView())
->setIcon('fa-check')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Active'),
));
$rows = array();
foreach ($bindings as $binding) {
$addr = $binding->getInterface()->getAddress();
@ -42,6 +69,7 @@ final class AlmanacBindingTableView extends AphrontView {
$rows[] = array(
$binding->getID(),
($binding->getIsDisabled() ? $icon_disabled : $icon_active),
$handles->renderHandle($binding->getServicePHID()),
$handles->renderHandle($binding->getDevicePHID()),
$handles->renderHandle($binding->getInterface()->getNetworkPHID()),
@ -61,6 +89,7 @@ final class AlmanacBindingTableView extends AphrontView {
->setHeaders(
array(
pht('ID'),
null,
pht('Service'),
pht('Device'),
pht('Network'),
@ -70,11 +99,18 @@ final class AlmanacBindingTableView extends AphrontView {
->setColumnClasses(
array(
'',
'icon',
'',
'',
'',
'wide',
'action',
))
->setColumnVisibility(
array(
true,
true,
!$this->getHideServiceColumn(),
));
return $table;

View file

@ -27,7 +27,9 @@ final class AlmanacInterfaceTableView extends AphrontView {
$interfaces = $this->getInterfaces();
$viewer = $this->getUser();
if ($this->getCanEdit()) {
$can_edit = $this->getCanEdit();
if ($can_edit) {
$button_class = 'small grey button';
} else {
$button_class = 'small grey button disabled';
@ -42,13 +44,22 @@ final class AlmanacInterfaceTableView extends AphrontView {
$handles->renderHandle($interface->getNetworkPHID()),
$interface->getAddress(),
$interface->getPort(),
phutil_tag(
javelin_tag(
'a',
array(
'class' => $button_class,
'href' => '/almanac/interface/edit/'.$interface->getID().'/',
'sigil' => ($can_edit ? null : 'workflow'),
),
pht('Edit')),
javelin_tag(
'a',
array(
'class' => $button_class,
'href' => '/almanac/interface/delete/'.$interface->getID().'/',
'sigil' => 'workflow',
),
pht('Delete')),
);
}
@ -60,6 +71,7 @@ final class AlmanacInterfaceTableView extends AphrontView {
pht('Address'),
pht('Port'),
null,
null,
))
->setColumnClasses(
array(
@ -68,6 +80,7 @@ final class AlmanacInterfaceTableView extends AphrontView {
'',
'',
'action',
'action',
));
return $table;

View file

@ -267,6 +267,11 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
$free = array();
foreach ($bindings as $binding) {
// Don't consider disabled bindings to be available.
if ($binding->getIsDisabled()) {
continue;
}
if (empty($allocated_phids[$binding->getPHID()])) {
$free[] = $binding;
}

View file

@ -1,7 +1,7 @@
@title User Guide: Phabricator Clusters
@group config
Guide on scaling Phabricator across multiple machines, for large installs.
Guide on scaling Phabricator across multiple machines.
Overview
========
@ -9,10 +9,42 @@ Overview
IMPORTANT: Phabricator clustering is in its infancy and does not work at all
yet. This document is mostly a placeholder.
Locking Services
================
Very briefly, you can set "Can Manage Cluster Services" to "No One" to lock
the cluster definition.
IMPORTANT: DO NOT CONFIGURE CLUSTER SERVICES UNLESS YOU HAVE **TWENTY YEARS OF
EXPERIENCE WITH PHABRICATOR** AND **A MINIMUM OF 17 PHABRICATOR PHDs**. YOU
WILL BREAK YOUR INSTALL AND BE UNABLE TO REPAIR IT.
See also @{article:Almanac User Guide}.
Managing Cluster Configuration
==============================
Cluster configuration is managed primarily from the **Almanac** application.
To define cluster services and create or edit cluster configuration, you must
have the **Can Manage Cluster Services** application permission in Almanac. If
you do not have this permission, all cluster services and all connected devices
will be locked and not editable.
The **Can Manage Cluster Services** permission is stronger than service and
device policies, and overrides them. You can never edit a cluster service if
you don't have this permission, even if the **Can Edit** policy on the service
itself is very permissive.
Locking Cluster Configuration
=============================
IMPORTANT: Managing cluster services is **dangerous** and **fragile**.
If you make a mistake, you can break your install. Because the install is
broken, you will be unable to load the web interface in order to repair it.
IMPORTANT: Currently, broken clusters must be repaired by manually fixing them
in the database. There are no instructions available on how to do this, and no
tools to help you. Do not configure cluster services.
If an attacker gains access to an account with permission to manage cluster
services, they can add devices they control as database servers. These servers
will then receive sensitive data and traffic, and allow the attacker to
escalate their access and completely compromise an install.