Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15393616
D15662.id37755.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
D15662.id37755.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D15662: Add a `cluster.read-only` option
Attached
Detach File
Event Timeline
Log In to Comment