public function execute(PhutilArgumentParser $args) { $viewer = $this->getViewer(); $repositories = $this->loadRepositories($args, 'repositories'); if (!$repositories) { throw new PhutilArgumentUsageException(pht('Specify one or more repositories to thaw.')); } $promote = $args->getArg('promote'); $demote = $args->getArg('demote'); if (!$promote && !$demote) { throw new PhutilArgumentUsageException(pht('You must choose a device to --promote or --demote.')); } if ($promote && $demote) { throw new PhutilArgumentUsageException(pht('Specify either --promote or --demote, but not both.')); } $device_name = nonempty($promote, $demote); $device = id(new AlmanacDeviceQuery())->setViewer($viewer)->withNames(array($device_name))->executeOne(); if (!$device) { throw new PhutilArgumentUsageException(pht('No device "%s" exists.', $device_name)); } if ($promote) { $risk_message = pht('Promoting a device can cause the loss of any repository data which ' . 'only exists on other devices. The version of the repository on the ' . 'promoted device will become authoritative.'); } else { $risk_message = pht('Demoting a device can cause the loss of any repository data which ' . 'only exists on the demoted device. The version of the repository ' . 'on some other device will become authoritative.'); } echo tsprintf("**<bg:red> %s </bg>** %s\n", pht('DATA AT RISK'), $risk_message); $is_force = $args->getArg('force'); $prompt = pht('Accept the possibilty of permanent data loss?'); if (!$is_force && !phutil_console_confirm($prompt)) { throw new PhutilArgumentUsageException(pht('User aborted the workflow.')); } foreach ($repositories as $repository) { $repository_phid = $repository->getPHID(); $write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock($repository_phid); echo tsprintf("%s\n", pht('Waiting to acquire write lock for "%s"...', $repository->getDisplayName())); $write_lock->lock(phutil_units('5 minutes in seconds')); try { $service = $repository->loadAlmanacService(); if (!$service) { throw new PhutilArgumentUsageException(pht('Repository "%s" is not a cluster repository: it is not ' . 'bound to an Almanac service.', $repository->getDisplayName())); } $bindings = $service->getActiveBindings(); $bindings = mpull($bindings, null, 'getDevicePHID'); if (empty($bindings[$device->getPHID()])) { throw new PhutilArgumentUsageException(pht('Repository "%s" has no active binding to device "%s". Only ' . 'actively bound devices can be promoted or demoted.', $repository->getDisplayName(), $device->getName())); } $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions($repository->getPHID()); $versions = mpull($versions, null, 'getDevicePHID'); $versions = array_select_keys($versions, array_keys($bindings)); if ($versions && $promote) { throw new PhutilArgumentUsageException(pht('Unable to promote "%s" for repository "%s": the leaders for ' . 'this cluster are not ambiguous.', $device->getName(), $repository->getDisplayName())); } if ($promote) { PhabricatorRepositoryWorkingCopyVersion::updateVersion($repository->getPHID(), $device->getPHID(), 0); echo tsprintf("%s\n", pht('Promoted "%s" to become a leader for "%s".', $device->getName(), $repository->getDisplayName())); } if ($demote) { PhabricatorRepositoryWorkingCopyVersion::demoteDevice($repository->getPHID(), $device->getPHID()); echo tsprintf("%s\n", pht('Demoted "%s" from leadership of repository "%s".', $device->getName(), $repository->getDisplayName())); } } catch (Exception $ex) { $write_lock->unlock(); throw $ex; } $write_lock->unlock(); } return 0; }
/** * @task sync */ public function synchronizeWorkingCopyBeforeWrite() { if (!$this->shouldEnableSynchronization()) { return; } $repository = $this->getRepository(); $viewer = $this->getViewer(); $repository_phid = $repository->getPHID(); $device = AlmanacKeys::getLiveDevice(); $device_phid = $device->getPHID(); $table = new PhabricatorRepositoryWorkingCopyVersion(); $locked_connection = $table->establishConnection('w'); $write_lock = PhabricatorRepositoryWorkingCopyVersion::getWriteLock($repository_phid); $write_lock->useSpecificConnection($locked_connection); $lock_wait = phutil_units('2 minutes in seconds'); $this->logLine(pht('Waiting up to %s second(s) for a cluster write lock...', new PhutilNumber($lock_wait))); try { $start = PhabricatorTime::getNow(); $write_lock->lock($lock_wait); $waited = PhabricatorTime::getNow() - $start; if ($waited) { $this->logLine(pht('Acquired write lock after %s second(s).', new PhutilNumber($waited))); } else { $this->logLine(pht('Acquired write lock immediately.')); } } catch (Exception $ex) { throw new PhutilProxyException(pht('Failed to acquire write lock after waiting %s second(s). You ' . 'may be able to retry later.', new PhutilNumber($lock_wait))); } $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions($repository_phid); foreach ($versions as $version) { if (!$version->getIsWriting()) { continue; } throw new Exception(pht('An previous write to this repository was interrupted; refusing ' . 'new writes. This issue requires operator intervention to resolve, ' . 'see "Write Interruptions" in the "Cluster: Repositories" in the ' . 'documentation for instructions.')); } try { $max_version = $this->synchronizeWorkingCopyBeforeRead(); } catch (Exception $ex) { $write_lock->unlock(); throw $ex; } $pid = getmypid(); $hash = Filesystem::readRandomCharacters(12); $this->clusterWriteOwner = "{$pid}.{$hash}"; PhabricatorRepositoryWorkingCopyVersion::willWrite($locked_connection, $repository_phid, $device_phid, array('userPHID' => $viewer->getPHID(), 'epoch' => PhabricatorTime::getNow(), 'devicePHID' => $device_phid), $this->clusterWriteOwner); $this->clusterWriteVersion = $max_version; $this->clusterWriteLock = $write_lock; }