Remove product literal strings in "pht()", part 6
[phabricator.git] / src / applications / drydock / blueprint / DrydockAlmanacServiceHostBlueprintImplementation.php
blob290fae8c63b61dca783866cc7e14ed8fc5873a12
1 <?php
3 final class DrydockAlmanacServiceHostBlueprintImplementation
4 extends DrydockBlueprintImplementation {
6 private $services;
7 private $freeBindings;
9 public function isEnabled() {
10 $almanac_app = 'PhabricatorAlmanacApplication';
11 return PhabricatorApplication::isClassInstalled($almanac_app);
14 public function getBlueprintName() {
15 return pht('Almanac Hosts');
18 public function getBlueprintIcon() {
19 return 'fa-server';
22 public function getDescription() {
23 return pht(
24 'Allows Drydock to lease existing hosts defined in an Almanac service '.
25 'pool.');
28 public function canAnyBlueprintEverAllocateResourceForLease(
29 DrydockLease $lease) {
30 return true;
33 public function canEverAllocateResourceForLease(
34 DrydockBlueprint $blueprint,
35 DrydockLease $lease) {
36 $services = $this->loadServices($blueprint);
37 $bindings = $this->getActiveBindings($services);
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;
45 return true;
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;
58 public function canAllocateResourceForLease(
59 DrydockBlueprint $blueprint,
60 DrydockLease $lease) {
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;
69 return true;
72 public function allocateResource(
73 DrydockBlueprint $blueprint,
74 DrydockLease $lease) {
76 $free_bindings = $this->loadFreeBindings($blueprint);
77 shuffle($free_bindings);
79 $exceptions = array();
80 foreach ($free_bindings as $binding) {
81 $device = $binding->getDevice();
82 $device_name = $device->getName();
84 $binding_phid = $binding->getPHID();
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})");
93 try {
94 return $resource->allocateResource();
95 } catch (Exception $ex) {
96 $exceptions[] = $ex;
100 throw new PhutilAggregateException(
101 pht('Unable to allocate any binding as a resource.'),
102 $exceptions);
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;
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);
122 public function canAcquireLeaseOnResource(
123 DrydockBlueprint $blueprint,
124 DrydockResource $resource,
125 DrydockLease $lease) {
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;
134 return true;
137 public function acquireLease(
138 DrydockBlueprint $blueprint,
139 DrydockResource $resource,
140 DrydockLease $lease) {
142 $lease
143 ->setActivateWhenAcquired(true)
144 ->acquireOnResource($resource);
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;
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;
165 public function getType() {
166 return 'host';
169 public function getInterface(
170 DrydockBlueprint $blueprint,
171 DrydockResource $resource,
172 DrydockLease $lease,
173 $type) {
175 switch ($type) {
176 case DrydockCommandInterface::INTERFACE_TYPE:
177 $credential_phid = $blueprint->getFieldValue('credentialPHID');
178 $binding = $this->loadBindingForResource($resource);
179 $interface = $binding->getInterface();
181 return id(new DrydockSSHCommandInterface())
182 ->setConfig('credentialPHID', $credential_phid)
183 ->setConfig('host', $interface->getAddress())
184 ->setConfig('port', $interface->getPort());
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(),
197 'required' => true,
199 'credentialPHID' => array(
200 'name' => pht('Credentials'),
201 'type' => 'credential',
202 'credential.provides' =>
203 PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE,
204 'credential.type' =>
205 PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE,
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()));
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');
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)));
239 $this->services = $services;
242 return $this->services;
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');
251 private function loadFreeBindings(DrydockBlueprint $blueprint) {
252 if ($this->freeBindings === null) {
253 $viewer = $this->getViewer();
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,
265 ->execute();
267 $allocated_phids = array();
268 foreach ($pool as $resource) {
269 $allocated_phids[] = $resource->getAttribute('almanacBindingPHID');
271 $allocated_phids = array_fuse($allocated_phids);
273 $services = $this->loadServices($blueprint);
274 $bindings = $this->getActiveBindings($services);
276 $free = array();
277 foreach ($bindings as $binding) {
278 if (empty($allocated_phids[$binding->getPHID()])) {
279 $free[] = $binding;
283 $this->freeBindings = $free;
286 return $this->freeBindings;
289 private function getAlmanacServiceTypes() {
290 return array(
291 AlmanacDrydockPoolServiceType::SERVICETYPE,
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()));
305 $viewer = $this->getViewer();
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()));
319 return $binding;