Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php
12256 views
1
<?php
2
3
final class DrydockManagementLeaseWorkflow
4
extends DrydockManagementWorkflow {
5
6
protected function didConstruct() {
7
$this
8
->setName('lease')
9
->setSynopsis(pht('Lease a resource.'))
10
->setArguments(
11
array(
12
array(
13
'name' => 'type',
14
'param' => 'resource_type',
15
'help' => pht('Resource type.'),
16
),
17
array(
18
'name' => 'until',
19
'param' => 'time',
20
'help' => pht('Set lease expiration time.'),
21
),
22
array(
23
'name' => 'attributes',
24
'param' => 'file',
25
'help' => pht(
26
'JSON file with lease attributes. Use "-" to read attributes '.
27
'from stdin.'),
28
),
29
array(
30
'name' => 'count',
31
'param' => 'N',
32
'default' => 1,
33
'help' => pht('Lease a given number of identical resources.'),
34
),
35
array(
36
'name' => 'blueprint',
37
'param' => 'identifier',
38
'repeat' => true,
39
'help' => pht('Lease resources from a specific blueprint.'),
40
),
41
));
42
}
43
44
public function execute(PhutilArgumentParser $args) {
45
$viewer = $this->getViewer();
46
47
$resource_type = $args->getArg('type');
48
if (!phutil_nonempty_string($resource_type)) {
49
throw new PhutilArgumentUsageException(
50
pht(
51
'Specify a resource type with "--type".'));
52
}
53
54
$until = $args->getArg('until');
55
if (phutil_nonempty_string($until)) {
56
$until = strtotime($until);
57
if ($until <= 0) {
58
throw new PhutilArgumentUsageException(
59
pht(
60
'Unable to parse argument to "--until".'));
61
}
62
}
63
64
$count = $args->getArgAsInteger('count');
65
if ($count < 1) {
66
throw new PhutilArgumentUsageException(
67
pht(
68
'Value provided to "--count" must be a nonzero, positive '.
69
'number.'));
70
}
71
72
$attributes_file = $args->getArg('attributes');
73
if (phutil_nonempty_string($attributes_file)) {
74
if ($attributes_file == '-') {
75
echo tsprintf(
76
"%s\n",
77
pht('Reading JSON attributes from stdin...'));
78
$data = file_get_contents('php://stdin');
79
} else {
80
$data = Filesystem::readFile($attributes_file);
81
}
82
83
$attributes = phutil_json_decode($data);
84
} else {
85
$attributes = array();
86
}
87
88
$filter_identifiers = $args->getArg('blueprint');
89
if ($filter_identifiers) {
90
$filter_blueprints = $this->getBlueprintFilterMap($filter_identifiers);
91
} else {
92
$filter_blueprints = array();
93
}
94
95
$blueprint_phids = null;
96
97
$leases = array();
98
for ($idx = 0; $idx < $count; $idx++) {
99
$lease = id(new DrydockLease())
100
->setResourceType($resource_type);
101
102
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
103
$lease->setAuthorizingPHID($drydock_phid);
104
105
if ($attributes) {
106
$lease->setAttributes($attributes);
107
}
108
109
if ($blueprint_phids === null) {
110
$blueprint_phids = $this->newAllowedBlueprintPHIDs(
111
$lease,
112
$filter_blueprints);
113
}
114
115
$lease->setAllowedBlueprintPHIDs($blueprint_phids);
116
117
if ($until) {
118
$lease->setUntil($until);
119
}
120
121
// If something fatals or the user interrupts the process (for example,
122
// with "^C"), release the lease. We'll cancel this below, if the lease
123
// actually activates.
124
$lease->setReleaseOnDestruction(true);
125
126
$leases[] = $lease;
127
}
128
129
// TODO: This would probably be better handled with PhutilSignalRouter,
130
// but it currently doesn't route SIGINT. We're initializing it to setup
131
// SIGTERM handling and make eventual migration easier.
132
$router = PhutilSignalRouter::getRouter();
133
pcntl_signal(SIGINT, array($this, 'didReceiveInterrupt'));
134
135
$t_start = microtime(true);
136
137
138
echo tsprintf(
139
"%s\n\n",
140
pht('Leases queued for activation:'));
141
142
foreach ($leases as $lease) {
143
$lease->queueForActivation();
144
145
echo tsprintf(
146
" __%s__\n",
147
PhabricatorEnv::getProductionURI($lease->getURI()));
148
}
149
150
echo tsprintf(
151
"\n%s\n\n",
152
pht('Waiting for daemons to activate leases...'));
153
154
foreach ($leases as $lease) {
155
$this->waitUntilActive($lease);
156
}
157
158
// Now that we've survived activation and the lease is good, make it
159
// durable.
160
foreach ($leases as $lease) {
161
$lease->setReleaseOnDestruction(false);
162
}
163
164
$t_end = microtime(true);
165
166
echo tsprintf(
167
"\n%s\n\n",
168
pht(
169
'Activation complete. Leases are permanent until manually '.
170
'released with:'));
171
172
foreach ($leases as $lease) {
173
echo tsprintf(
174
" %s\n",
175
pht('$ ./bin/drydock release-lease --id %d', $lease->getID()));
176
}
177
178
echo tsprintf(
179
"\n%s\n",
180
pht(
181
'Leases activated in %sms.',
182
new PhutilNumber((int)(($t_end - $t_start) * 1000))));
183
184
return 0;
185
}
186
187
public function didReceiveInterrupt($signo) {
188
// Doing this makes us run destructors, particularly the "release on
189
// destruction" trigger on the lease.
190
exit(128 + $signo);
191
}
192
193
private function waitUntilActive(DrydockLease $lease) {
194
$viewer = $this->getViewer();
195
196
$log_cursor = 0;
197
$log_types = DrydockLogType::getAllLogTypes();
198
199
$is_active = false;
200
while (!$is_active) {
201
$lease->reload();
202
203
$pager = id(new AphrontCursorPagerView())
204
->setBeforeID($log_cursor);
205
206
// While we're waiting, show the user any logs which the daemons have
207
// generated to give them some clue about what's going on.
208
$logs = id(new DrydockLogQuery())
209
->setViewer($viewer)
210
->withLeasePHIDs(array($lease->getPHID()))
211
->executeWithCursorPager($pager);
212
if ($logs) {
213
$logs = mpull($logs, null, 'getID');
214
ksort($logs);
215
$log_cursor = last_key($logs);
216
}
217
218
foreach ($logs as $log) {
219
$type_key = $log->getType();
220
if (isset($log_types[$type_key])) {
221
$type_object = id(clone $log_types[$type_key])
222
->setLog($log)
223
->setViewer($viewer);
224
225
$log_data = $log->getData();
226
227
$type = $type_object->getLogTypeName();
228
$data = $type_object->renderLogForText($log_data);
229
} else {
230
$type = pht('Unknown ("%s")', $type_key);
231
$data = null;
232
}
233
234
echo tsprintf(
235
"(Lease #%d) <%s> %B\n",
236
$lease->getID(),
237
$type,
238
$data);
239
}
240
241
$status = $lease->getStatus();
242
243
switch ($status) {
244
case DrydockLeaseStatus::STATUS_ACTIVE:
245
$is_active = true;
246
break;
247
case DrydockLeaseStatus::STATUS_RELEASED:
248
throw new Exception(pht('Lease has already been released!'));
249
case DrydockLeaseStatus::STATUS_DESTROYED:
250
throw new Exception(pht('Lease has already been destroyed!'));
251
case DrydockLeaseStatus::STATUS_BROKEN:
252
throw new Exception(pht('Lease has been broken!'));
253
case DrydockLeaseStatus::STATUS_PENDING:
254
case DrydockLeaseStatus::STATUS_ACQUIRED:
255
break;
256
default:
257
throw new Exception(
258
pht(
259
'Lease has unknown status "%s".',
260
$status));
261
}
262
263
if ($is_active) {
264
break;
265
} else {
266
sleep(1);
267
}
268
}
269
}
270
271
private function getBlueprintFilterMap(array $identifiers) {
272
$viewer = $this->getViewer();
273
274
$query = id(new DrydockBlueprintQuery())
275
->setViewer($viewer)
276
->withIdentifiers($identifiers);
277
278
$blueprints = $query->execute();
279
$blueprints = mpull($blueprints, null, 'getPHID');
280
281
$map = $query->getIdentifierMap();
282
283
$seen = array();
284
foreach ($identifiers as $identifier) {
285
if (!isset($map[$identifier])) {
286
throw new PhutilArgumentUsageException(
287
pht(
288
'Blueprint "%s" could not be loaded. Try a blueprint ID or '.
289
'PHID.',
290
$identifier));
291
}
292
293
$blueprint = $map[$identifier];
294
295
$blueprint_phid = $blueprint->getPHID();
296
if (isset($seen[$blueprint_phid])) {
297
throw new PhutilArgumentUsageException(
298
pht(
299
'Blueprint "%s" is specified more than once (as "%s" and "%s").',
300
$blueprint->getBlueprintName(),
301
$seen[$blueprint_phid],
302
$identifier));
303
}
304
305
$seen[$blueprint_phid] = true;
306
}
307
308
return mpull($map, null, 'getPHID');
309
}
310
311
private function newAllowedBlueprintPHIDs(
312
DrydockLease $lease,
313
array $filter_blueprints) {
314
assert_instances_of($filter_blueprints, 'DrydockBlueprint');
315
316
$viewer = $this->getViewer();
317
318
$impls = DrydockBlueprintImplementation::getAllForAllocatingLease($lease);
319
320
if (!$impls) {
321
throw new PhutilArgumentUsageException(
322
pht(
323
'No known blueprint class can ever allocate the specified '.
324
'lease. Check that the resource type is spelled correctly.'));
325
}
326
327
$classes = array_keys($impls);
328
329
$blueprints = id(new DrydockBlueprintQuery())
330
->setViewer($viewer)
331
->withBlueprintClasses($classes)
332
->withDisabled(false)
333
->execute();
334
335
if (!$blueprints) {
336
throw new PhutilArgumentUsageException(
337
pht(
338
'No enabled blueprints exist with a blueprint class that can '.
339
'plausibly allocate resources to satisfy the requested lease.'));
340
}
341
342
$phids = mpull($blueprints, 'getPHID');
343
344
if ($filter_blueprints) {
345
$allowed_map = array_fuse($phids);
346
$filter_map = mpull($filter_blueprints, null, 'getPHID');
347
348
foreach ($filter_map as $filter_phid => $blueprint) {
349
if (!isset($allowed_map[$filter_phid])) {
350
throw new PhutilArgumentUsageException(
351
pht(
352
'Specified blueprint "%s" is not capable of satisfying the '.
353
'configured lease.',
354
$blueprint->getBlueprintName()));
355
}
356
}
357
358
$phids = mpull($filter_blueprints, 'getPHID');
359
}
360
361
return $phids;
362
}
363
364
}
365
366