Page MenuHomePhabricator

D15662.id37755.diff
No OneTemporary

D15662.id37755.diff

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
Sun, Mar 16, 10:46 PM (1 w, 2 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7687552
Default Alt Text
D15662.id37755.diff (9 KB)

Event Timeline