Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php
12256 views
1
<?php
2
3
final class DrydockAlmanacServiceHostBlueprintImplementation
4
extends DrydockBlueprintImplementation {
5
6
private $services;
7
private $freeBindings;
8
9
public function isEnabled() {
10
$almanac_app = 'PhabricatorAlmanacApplication';
11
return PhabricatorApplication::isClassInstalled($almanac_app);
12
}
13
14
public function getBlueprintName() {
15
return pht('Almanac Hosts');
16
}
17
18
public function getBlueprintIcon() {
19
return 'fa-server';
20
}
21
22
public function getDescription() {
23
return pht(
24
'Allows Drydock to lease existing hosts defined in an Almanac service '.
25
'pool.');
26
}
27
28
public function canAnyBlueprintEverAllocateResourceForLease(
29
DrydockLease $lease) {
30
return true;
31
}
32
33
public function canEverAllocateResourceForLease(
34
DrydockBlueprint $blueprint,
35
DrydockLease $lease) {
36
$services = $this->loadServices($blueprint);
37
$bindings = $this->getActiveBindings($services);
38
39
if (!$bindings) {
40
// If there are no devices bound to the services for this blueprint,
41
// we can not allocate resources.
42
return false;
43
}
44
45
return true;
46
}
47
48
public function shouldAllocateSupplementalResource(
49
DrydockBlueprint $blueprint,
50
DrydockResource $resource,
51
DrydockLease $lease) {
52
// We want to use every host in an Almanac service, since the amount of
53
// hardware is fixed and there's normally no value in packing leases onto a
54
// subset of it. Always build a new supplemental resource if we can.
55
return true;
56
}
57
58
public function canAllocateResourceForLease(
59
DrydockBlueprint $blueprint,
60
DrydockLease $lease) {
61
62
// We will only allocate one resource per unique device bound to the
63
// services for this blueprint. Make sure we have a free device somewhere.
64
$free_bindings = $this->loadFreeBindings($blueprint);
65
if (!$free_bindings) {
66
return false;
67
}
68
69
return true;
70
}
71
72
public function allocateResource(
73
DrydockBlueprint $blueprint,
74
DrydockLease $lease) {
75
76
$free_bindings = $this->loadFreeBindings($blueprint);
77
shuffle($free_bindings);
78
79
$exceptions = array();
80
foreach ($free_bindings as $binding) {
81
$device = $binding->getDevice();
82
$device_name = $device->getName();
83
84
$binding_phid = $binding->getPHID();
85
86
$resource = $this->newResourceTemplate($blueprint)
87
->setActivateWhenAllocated(true)
88
->setAttribute('almanacDeviceName', $device_name)
89
->setAttribute('almanacServicePHID', $binding->getServicePHID())
90
->setAttribute('almanacBindingPHID', $binding_phid)
91
->needSlotLock("almanac.host.binding({$binding_phid})");
92
93
try {
94
return $resource->allocateResource();
95
} catch (Exception $ex) {
96
$exceptions[] = $ex;
97
}
98
}
99
100
throw new PhutilAggregateException(
101
pht('Unable to allocate any binding as a resource.'),
102
$exceptions);
103
}
104
105
public function destroyResource(
106
DrydockBlueprint $blueprint,
107
DrydockResource $resource) {
108
// We don't create anything when allocating hosts, so we don't need to do
109
// any cleanup here.
110
return;
111
}
112
113
public function getResourceName(
114
DrydockBlueprint $blueprint,
115
DrydockResource $resource) {
116
$device_name = $resource->getAttribute(
117
'almanacDeviceName',
118
pht('<Unknown>'));
119
return pht('Host (%s)', $device_name);
120
}
121
122
public function canAcquireLeaseOnResource(
123
DrydockBlueprint $blueprint,
124
DrydockResource $resource,
125
DrydockLease $lease) {
126
127
// Require the binding to a given host be active before we'll hand out more
128
// leases on the corresponding resource.
129
$binding = $this->loadBindingForResource($resource);
130
if ($binding->getIsDisabled()) {
131
return false;
132
}
133
134
return true;
135
}
136
137
public function acquireLease(
138
DrydockBlueprint $blueprint,
139
DrydockResource $resource,
140
DrydockLease $lease) {
141
142
$lease
143
->setActivateWhenAcquired(true)
144
->acquireOnResource($resource);
145
}
146
147
public function didReleaseLease(
148
DrydockBlueprint $blueprint,
149
DrydockResource $resource,
150
DrydockLease $lease) {
151
// Almanac hosts stick around indefinitely so we don't need to recycle them
152
// if they don't have any leases.
153
return;
154
}
155
156
public function destroyLease(
157
DrydockBlueprint $blueprint,
158
DrydockResource $resource,
159
DrydockLease $lease) {
160
// We don't create anything when activating a lease, so we don't need to
161
// throw anything away.
162
return;
163
}
164
165
public function getType() {
166
return 'host';
167
}
168
169
public function getInterface(
170
DrydockBlueprint $blueprint,
171
DrydockResource $resource,
172
DrydockLease $lease,
173
$type) {
174
175
switch ($type) {
176
case DrydockCommandInterface::INTERFACE_TYPE:
177
$credential_phid = $blueprint->getFieldValue('credentialPHID');
178
$binding = $this->loadBindingForResource($resource);
179
$interface = $binding->getInterface();
180
181
return id(new DrydockSSHCommandInterface())
182
->setConfig('credentialPHID', $credential_phid)
183
->setConfig('host', $interface->getAddress())
184
->setConfig('port', $interface->getPort());
185
}
186
}
187
188
protected function getCustomFieldSpecifications() {
189
return array(
190
'almanacServicePHIDs' => array(
191
'name' => pht('Almanac Services'),
192
'type' => 'datasource',
193
'datasource.class' => 'AlmanacServiceDatasource',
194
'datasource.parameters' => array(
195
'serviceTypes' => $this->getAlmanacServiceTypes(),
196
),
197
'required' => true,
198
),
199
'credentialPHID' => array(
200
'name' => pht('Credentials'),
201
'type' => 'credential',
202
'credential.provides' =>
203
PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE,
204
'credential.type' =>
205
PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE,
206
),
207
);
208
}
209
210
private function loadServices(DrydockBlueprint $blueprint) {
211
if (!$this->services) {
212
$service_phids = $blueprint->getFieldValue('almanacServicePHIDs');
213
if (!$service_phids) {
214
throw new Exception(
215
pht(
216
'This blueprint ("%s") does not define any Almanac Service PHIDs.',
217
$blueprint->getBlueprintName()));
218
}
219
220
$viewer = $this->getViewer();
221
$services = id(new AlmanacServiceQuery())
222
->setViewer($viewer)
223
->withPHIDs($service_phids)
224
->withServiceTypes($this->getAlmanacServiceTypes())
225
->needActiveBindings(true)
226
->execute();
227
$services = mpull($services, null, 'getPHID');
228
229
if (count($services) != count($service_phids)) {
230
$missing_phids = array_diff($service_phids, array_keys($services));
231
throw new Exception(
232
pht(
233
'Some of the Almanac Services defined by this blueprint '.
234
'could not be loaded. They may be invalid, no longer exist, '.
235
'or be of the wrong type: %s.',
236
implode(', ', $missing_phids)));
237
}
238
239
$this->services = $services;
240
}
241
242
return $this->services;
243
}
244
245
private function getActiveBindings(array $services) {
246
assert_instances_of($services, 'AlmanacService');
247
$bindings = array_mergev(mpull($services, 'getActiveBindings'));
248
return mpull($bindings, null, 'getPHID');
249
}
250
251
private function loadFreeBindings(DrydockBlueprint $blueprint) {
252
if ($this->freeBindings === null) {
253
$viewer = $this->getViewer();
254
255
$pool = id(new DrydockResourceQuery())
256
->setViewer($viewer)
257
->withBlueprintPHIDs(array($blueprint->getPHID()))
258
->withStatuses(
259
array(
260
DrydockResourceStatus::STATUS_PENDING,
261
DrydockResourceStatus::STATUS_ACTIVE,
262
DrydockResourceStatus::STATUS_BROKEN,
263
DrydockResourceStatus::STATUS_RELEASED,
264
))
265
->execute();
266
267
$allocated_phids = array();
268
foreach ($pool as $resource) {
269
$allocated_phids[] = $resource->getAttribute('almanacBindingPHID');
270
}
271
$allocated_phids = array_fuse($allocated_phids);
272
273
$services = $this->loadServices($blueprint);
274
$bindings = $this->getActiveBindings($services);
275
276
$free = array();
277
foreach ($bindings as $binding) {
278
if (empty($allocated_phids[$binding->getPHID()])) {
279
$free[] = $binding;
280
}
281
}
282
283
$this->freeBindings = $free;
284
}
285
286
return $this->freeBindings;
287
}
288
289
private function getAlmanacServiceTypes() {
290
return array(
291
AlmanacDrydockPoolServiceType::SERVICETYPE,
292
);
293
}
294
295
private function loadBindingForResource(DrydockResource $resource) {
296
$binding_phid = $resource->getAttribute('almanacBindingPHID');
297
if (!$binding_phid) {
298
throw new Exception(
299
pht(
300
'Drydock resource ("%s") has no Almanac binding PHID, so its '.
301
'binding can not be loaded.',
302
$resource->getPHID()));
303
}
304
305
$viewer = $this->getViewer();
306
307
$binding = id(new AlmanacBindingQuery())
308
->setViewer($viewer)
309
->withPHIDs(array($binding_phid))
310
->executeOne();
311
if (!$binding) {
312
throw new Exception(
313
pht(
314
'Unable to load Almanac binding ("%s") for resource ("%s").',
315
$binding_phid,
316
$resource->getPHID()));
317
}
318
319
return $binding;
320
}
321
322
}
323
324