3 final class PhabricatorRepositoryManagementThawWorkflow
4 extends PhabricatorRepositoryManagementWorkflow
{
6 protected function didConstruct() {
9 ->setExamples('**thaw** [options] __repository__ ...')
12 'Resolve issues with frozen cluster repositories. Very advanced '.
18 'param' => 'device|service',
20 'Demote a device (or all devices in a service) discarding '.
21 'unsynchronized changes. Clears stuck write locks and recovers '.
22 'from lost leaders.'),
28 'Promote a device, discarding changes on other devices. '.
29 'Resolves ambiguous leadership and recovers from demotion '.
34 'help' => pht('Run operations without asking for confirmation.'),
37 'name' => 'all-repositories',
39 'Apply the promotion or demotion to all repositories hosted '.
43 'name' => 'repositories',
49 public function execute(PhutilArgumentParser
$args) {
50 $viewer = $this->getViewer();
52 $promote = $args->getArg('promote');
53 $demote = $args->getArg('demote');
55 if (!$promote && !$demote) {
56 throw new PhutilArgumentUsageException(
57 pht('You must choose a device to --promote or --demote.'));
60 if ($promote && $demote) {
61 throw new PhutilArgumentUsageException(
62 pht('Specify either --promote or --demote, but not both.'));
65 $target_name = nonempty($promote, $demote);
67 $devices = id(new AlmanacDeviceQuery())
69 ->withNames(array($target_name))
72 $service = id(new AlmanacServiceQuery())
74 ->withNames(array($target_name))
78 throw new PhutilArgumentUsageException(
79 pht('No device or service named "%s" exists.', $target_name));
83 throw new PhutilArgumentUsageException(
85 'You can not "--promote" an entire service ("%s"). Only a single '.
86 'device may be promoted.',
90 $bindings = id(new AlmanacBindingQuery())
92 ->withServicePHIDs(array($service->getPHID()))
95 throw new PhutilArgumentUsageException(
97 'Service "%s" is not bound to any devices.',
101 $interfaces = id(new AlmanacInterfaceQuery())
103 ->withPHIDs(mpull($bindings, 'getInterfacePHID'))
106 $device_phids = mpull($interfaces, 'getDevicePHID');
108 $devices = id(new AlmanacDeviceQuery())
110 ->withPHIDs($device_phids)
114 $repository_names = $args->getArg('repositories');
115 $all_repositories = $args->getArg('all-repositories');
116 if ($repository_names && $all_repositories) {
117 throw new PhutilArgumentUsageException(
119 'Specify a list of repositories or "--all-repositories", '.
121 } else if (!$repository_names && !$all_repositories) {
122 throw new PhutilArgumentUsageException(
124 'Select repositories to affect by providing a list of repositories '.
125 'or using the "--all-repositories" flag.'));
128 if ($repository_names) {
129 $repositories = $this->loadRepositories($args, 'repositories');
130 if (!$repositories) {
131 throw new PhutilArgumentUsageException(
132 pht('Specify one or more repositories to thaw.'));
135 $repositories = array();
137 $services = id(new AlmanacServiceQuery())
139 ->withDevicePHIDs(mpull($devices, 'getPHID'))
142 $repositories = id(new PhabricatorRepositoryQuery())
144 ->withAlmanacServicePHIDs(mpull($services, 'getPHID'))
148 if (!$repositories) {
149 throw new PhutilArgumentUsageException(
150 pht('There are no repositories on the selected device or service.'));
154 $display_list = new PhutilConsoleList();
155 foreach ($repositories as $repository) {
156 $display_list->addItem(
159 $repository->getMonogram(),
160 $repository->getName()));
165 pht('These repositories will be thawed:'),
166 $display_list->drawConsoleString());
170 'Promoting a device can cause the loss of any repository data which '.
171 'only exists on other devices. The version of the repository on the '.
172 'promoted device will become authoritative.');
175 'Demoting a device can cause the loss of any repository data which '.
176 'only exists on the demoted device. The version of the repository '.
177 'on some other device will become authoritative.');
181 "**<bg:red> %s </bg>** %s\n",
185 $is_force = $args->getArg('force');
186 $prompt = pht('Accept the possibility of permanent data loss?');
187 if (!$is_force && !phutil_console_confirm($prompt)) {
188 throw new PhutilArgumentUsageException(
189 pht('User aborted the workflow.'));
192 foreach ($devices as $device) {
193 foreach ($repositories as $repository) {
194 $repository_phid = $repository->getPHID();
196 $write_lock = PhabricatorRepositoryWorkingCopyVersion
::getWriteLock(
202 'Waiting to acquire write lock for "%s"...',
203 $repository->getDisplayName()));
205 $write_lock->lock(phutil_units('5 minutes in seconds'));
208 $service = $repository->loadAlmanacService();
210 throw new PhutilArgumentUsageException(
212 'Repository "%s" is not a cluster repository: it is not '.
213 'bound to an Almanac service.',
214 $repository->getDisplayName()));
218 // You can only promote active devices. (You may demote active or
219 // inactive devices.)
220 $bindings = $service->getActiveBindings();
221 $bindings = mpull($bindings, null, 'getDevicePHID');
222 if (empty($bindings[$device->getPHID()])) {
223 throw new PhutilArgumentUsageException(
225 'Repository "%s" has no active binding to device "%s". '.
226 'Only actively bound devices can be promoted.',
227 $repository->getDisplayName(),
228 $device->getName()));
231 $versions = PhabricatorRepositoryWorkingCopyVersion
::loadVersions(
232 $repository->getPHID());
233 $versions = mpull($versions, null, 'getDevicePHID');
235 // Before we promote, make sure there are no outstanding versions
236 // on devices with inactive bindings. If there are, you need to
237 // demote these first.
239 foreach ($versions as $device_phid => $version) {
240 if (isset($bindings[$device_phid])) {
243 $inactive[$device_phid] = $version;
247 $handles = $viewer->loadHandles(array_keys($inactive));
249 $handle_list = iterator_to_array($handles);
250 $handle_list = mpull($handle_list, 'getName');
251 $handle_list = implode(', ', $handle_list);
253 throw new PhutilArgumentUsageException(
255 'Repository "%s" has versions on inactive devices. Demote '.
256 '(or reactivate) these devices before promoting a new '.
258 $repository->getDisplayName(),
262 // Now, make sure there are no outstanding versions on devices with
263 // active bindings. These also need to be demoted (or promoting is
264 // a mistake or already happened).
265 $active = array_select_keys($versions, array_keys($bindings));
267 $handles = $viewer->loadHandles(array_keys($active));
269 $handle_list = iterator_to_array($handles);
270 $handle_list = mpull($handle_list, 'getName');
271 $handle_list = implode(', ', $handle_list);
273 throw new PhutilArgumentUsageException(
275 'Unable to promote "%s" for repository "%s" because this '.
276 'cluster already has one or more unambiguous leaders: %s.',
278 $repository->getDisplayName(),
282 PhabricatorRepositoryWorkingCopyVersion
::updateVersion(
283 $repository->getPHID(),
290 'Promoted "%s" to become a leader for "%s".',
292 $repository->getDisplayName()));
296 PhabricatorRepositoryWorkingCopyVersion
::demoteDevice(
297 $repository->getPHID(),
303 'Demoted "%s" from leadership of repository "%s".',
305 $repository->getDisplayName()));
307 } catch (Exception
$ex) {
308 $write_lock->unlock();
312 $write_lock->unlock();