Page MenuHomePhabricator

D15662.id37746.diff
No OneTemporary

D15662.id37746.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,8 +7,8 @@
*/
return array(
'names' => array(
- 'core.pkg.css' => '03a2a623',
- 'core.pkg.js' => 'e5484f37',
+ 'core.pkg.css' => '3d122af0',
+ 'core.pkg.js' => '8a616602',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '7ba78475',
'differential.pkg.js' => 'd0cd0df6',
@@ -22,7 +22,7 @@
'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
'rsrc/css/aphront/multi-column.css' => 'fd18389d',
- 'rsrc/css/aphront/notification.css' => '7f684b62',
+ 'rsrc/css/aphront/notification.css' => '3f6c89c9',
'rsrc/css/aphront/panel-view.css' => '8427b78d',
'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758',
'rsrc/css/aphront/table-view.css' => '9258e19f',
@@ -494,6 +494,7 @@
'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '340c8eff',
+ 'rsrc/js/core/behavior-read-only-warning.js' => 'f8ea359c',
'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b',
'rsrc/js/core/behavior-remarkup-preview.js' => '4b700e9e',
'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e',
@@ -666,6 +667,7 @@
'javelin-behavior-project-boards' => '14a1faae',
'javelin-behavior-project-create' => '065227cc',
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
+ 'javelin-behavior-read-only-warning' => 'f8ea359c',
'javelin-behavior-recurring-edit' => '5f1c4d5f',
'javelin-behavior-refresh-csrf' => 'ab2f381b',
'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf',
@@ -766,7 +768,7 @@
'phabricator-main-menu-view' => 'd00a795a',
'phabricator-nav-view-css' => 'ac79a758',
'phabricator-notification' => 'ccf1cbf8',
- 'phabricator-notification-css' => '7f684b62',
+ 'phabricator-notification-css' => '3f6c89c9',
'phabricator-notification-menu-css' => 'f31c0bde',
'phabricator-object-selector-css' => '85ee8ce6',
'phabricator-phtize' => 'd254d646',
@@ -2109,6 +2111,11 @@
'javelin-util',
'phabricator-busy',
),
+ 'f8ea359c' => array(
+ 'javelin-behavior',
+ 'javelin-uri',
+ 'phabricator-notification',
+ ),
'fa0f4fc2' => array(
'javelin-behavior',
'javelin-dom',
@@ -2284,6 +2291,7 @@
'javelin-quicksand',
'javelin-behavior-quicksand-blacklist',
'javelin-behavior-high-security-warning',
+ 'javelin-behavior-read-only-warning',
'javelin-scrollbar',
'javelin-behavior-scrollbar',
'javelin-behavior-durable-column',
diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php
--- a/resources/celerity/packages.php
+++ b/resources/celerity/packages.php
@@ -76,6 +76,7 @@
'javelin-quicksand',
'javelin-behavior-quicksand-blacklist',
'javelin-behavior-high-security-warning',
+ 'javelin-behavior-read-only-warning',
'javelin-scrollbar',
'javelin-behavior-scrollbar',
'javelin-behavior-durable-column',
diff --git a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php
--- a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php
+++ b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php
@@ -7,6 +7,10 @@
const CACHE_FORMAT_DEFLATE = 'deflate';
public function setKeys(array $keys, $ttl = null) {
+ if (PhabricatorEnv::isReadOnly()) {
+ return;
+ }
+
if ($keys) {
$map = $this->digestKeys(array_keys($keys));
$conn_w = $this->establishConnection('w');
@@ -30,19 +34,19 @@
$guard = AphrontWriteGuard::beginScopedUnguardedWrites();
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
- queryfx(
- $conn_w,
- 'INSERT INTO %T
- (cacheKeyHash, cacheKey, cacheFormat, cacheData,
- cacheCreated, cacheExpires) VALUES %Q
- ON DUPLICATE KEY UPDATE
- cacheKey = VALUES(cacheKey),
- cacheFormat = VALUES(cacheFormat),
- cacheData = VALUES(cacheData),
- cacheCreated = VALUES(cacheCreated),
- cacheExpires = VALUES(cacheExpires)',
- $this->getTableName(),
- $chunk);
+ queryfx(
+ $conn_w,
+ 'INSERT INTO %T
+ (cacheKeyHash, cacheKey, cacheFormat, cacheData,
+ cacheCreated, cacheExpires) VALUES %Q
+ ON DUPLICATE KEY UPDATE
+ cacheKey = VALUES(cacheKey),
+ cacheFormat = VALUES(cacheFormat),
+ cacheData = VALUES(cacheData),
+ cacheCreated = VALUES(cacheCreated),
+ cacheExpires = VALUES(cacheExpires)',
+ $this->getTableName(),
+ $chunk);
}
unset($guard);
}
diff --git a/src/applications/config/option/PhabricatorClusterConfigOptions.php b/src/applications/config/option/PhabricatorClusterConfigOptions.php
--- a/src/applications/config/option/PhabricatorClusterConfigOptions.php
+++ b/src/applications/config/option/PhabricatorClusterConfigOptions.php
@@ -73,6 +73,22 @@
'subprocesses and commit hooks in the `%s` environmental variable.',
'PhabricatorConfigSiteSource',
'PHABRICATOR_INSTANCE')),
+ $this->newOption('cluster.read-only', 'bool', false)
+ ->setLocked(true)
+ ->setSummary(
+ pht(
+ 'Activate read-only mode for maintenance or disaster recovery.'))
+ ->setDescription(
+ pht(
+ 'WARNING: This is a prototype option and the description below '.
+ 'is currently pure fantasy.'.
+ "\n\n".
+ 'Switch Phabricator to read-only mode. In this mode, users will '.
+ 'be unable to write new data. Normally, the cluster degrades '.
+ 'into this mode automatically when it detects that the database '.
+ 'master is unreachable, but you can activate it manually in '.
+ 'order to perform maintenance or test configuration.')),
+
);
}
diff --git a/src/applications/multimeter/data/MultimeterControl.php b/src/applications/multimeter/data/MultimeterControl.php
--- a/src/applications/multimeter/data/MultimeterControl.php
+++ b/src/applications/multimeter/data/MultimeterControl.php
@@ -124,6 +124,10 @@
}
private function writeEvents() {
+ if (PhabricatorEnv::isReadOnly()) {
+ return;
+ }
+
$events = $this->events;
$random = Filesystem::readRandomBytes(32);
diff --git a/src/applications/notification/storage/PhabricatorFeedStoryNotification.php b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php
--- a/src/applications/notification/storage/PhabricatorFeedStoryNotification.php
+++ b/src/applications/notification/storage/PhabricatorFeedStoryNotification.php
@@ -39,6 +39,10 @@
PhabricatorUser $user,
$object_phid) {
+ if (PhabricatorEnv::isReadOnly()) {
+ return;
+ }
+
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$notification_table = new PhabricatorFeedStoryNotification();
diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php
--- a/src/infrastructure/env/PhabricatorEnv.php
+++ b/src/infrastructure/env/PhabricatorEnv.php
@@ -56,6 +56,7 @@
private static $requestBaseURI;
private static $cache;
private static $localeCode;
+ private static $readOnly;
/**
* @phutil-external-symbol class PhabricatorStartup
@@ -439,6 +440,18 @@
self::$requestBaseURI = $uri;
}
+ public static function isReadOnly() {
+ if (self::$readOnly !== null) {
+ return self::$readOnly;
+ }
+ return self::getEnvConfig('cluster.read-only');
+ }
+
+ public static function setReadOnly($read_only) {
+ self::$readOnly = $read_only;
+ }
+
+
/* -( Unit Test Support )-------------------------------------------------- */
diff --git a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
--- a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
+++ b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
@@ -57,7 +57,16 @@
'mysql.configuration-provider',
array($this, $mode, $namespace));
- return PhabricatorEnv::newObjectFromConfig(
+ $is_readonly = PhabricatorEnv::isReadOnly();
+ if ($is_readonly && ($mode != 'r')) {
+ throw new Exception(
+ pht(
+ 'Attempting to establish write-mode connection from a read-only '.
+ 'page (to database "%s").',
+ $conf->getDatabase()));
+ }
+
+ $connection = PhabricatorEnv::newObjectFromConfig(
'mysql.implementation',
array(
array(
@@ -69,6 +78,16 @@
'retries' => 3,
),
));
+
+ // TODO: This should be testing if the mode is "r", but that would proably
+ // break a lot of things. Perform a more narrow test for readonly mode
+ // until we have greater certainty that this works correctly most of the
+ // time.
+ if ($is_readonly) {
+ $connection->setReadOnly(true);
+ }
+
+ return $connection;
}
/**
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php
@@ -50,6 +50,17 @@
$this->setDryRun($args->getArg('dryrun'));
$this->setForce($args->getArg('force'));
+ if (PhabricatorEnv::isReadOnly()) {
+ if ($this->isForce()) {
+ PhabricatorEnv::setReadOnly(false);
+ } else {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Phabricator is currently in read-only mode. Use --force to '.
+ 'override this mode.'));
+ }
+ }
+
$this->didExecute($args);
}
diff --git a/src/infrastructure/testing/PhabricatorTestCase.php b/src/infrastructure/testing/PhabricatorTestCase.php
--- a/src/infrastructure/testing/PhabricatorTestCase.php
+++ b/src/infrastructure/testing/PhabricatorTestCase.php
@@ -126,6 +126,8 @@
// Tests do their own stubbing/voiding for events.
$this->env->overrideEnvConfig('phabricator.silent', false);
+
+ $this->env->overrideEnvConfig('cluster.read-only', false);
}
protected function didRunTests() {
diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php
--- a/src/view/page/PhabricatorStandardPageView.php
+++ b/src/view/page/PhabricatorStandardPageView.php
@@ -272,6 +272,14 @@
'high-security-warning',
$this->getHighSecurityWarningConfig());
+ if (PhabricatorEnv::isReadOnly()) {
+ Javelin::initBehavior(
+ 'read-only-warning',
+ array(
+ 'message' => pht('This install is currently in read-only mode.'),
+ ));
+ }
+
if ($console) {
require_celerity_resource('aphront-dark-console-css');
diff --git a/webroot/rsrc/css/aphront/notification.css b/webroot/rsrc/css/aphront/notification.css
--- a/webroot/rsrc/css/aphront/notification.css
+++ b/webroot/rsrc/css/aphront/notification.css
@@ -52,6 +52,11 @@
border: 1px solid {$violet};
}
+.jx-notification-read-only {
+ background: {$greybackground};
+ border: 1px solid {$darkgreyborder};
+}
+
.jx-notification-container .phabricator-notification {
padding: 0;
}
diff --git a/webroot/rsrc/js/core/behavior-read-only-warning.js b/webroot/rsrc/js/core/behavior-read-only-warning.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/core/behavior-read-only-warning.js
@@ -0,0 +1,16 @@
+/**
+ * @provides javelin-behavior-read-only-warning
+ * @requires javelin-behavior
+ * javelin-uri
+ * phabricator-notification
+ */
+
+JX.behavior('read-only-warning', function(config) {
+
+ new JX.Notification()
+ .setContent(config.message)
+ .setDuration(0)
+ .alterClassName('jx-notification-read-only', true)
+ .show();
+
+});

File Metadata

Mime Type
text/plain
Expires
Fri, Mar 21, 9:04 PM (3 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7697915
Default Alt Text
D15662.id37746.diff (12 KB)

Event Timeline