@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 325 lines 9.4 kB view raw
1<?php 2 3final class DrydockAlmanacServiceHostBlueprintImplementation 4 extends DrydockBlueprintImplementation { 5 6 private $services; 7 private $freeBindings; 8 9 public function isEnabled() { 10 return PhabricatorApplication::isClassInstalled( 11 PhabricatorAlmanacApplication::class); 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 /** 246 * @param array<AlmanacService> $services 247 */ 248 private function getActiveBindings(array $services) { 249 assert_instances_of($services, AlmanacService::class); 250 $bindings = array_mergev(mpull($services, 'getActiveBindings')); 251 return mpull($bindings, null, 'getPHID'); 252 } 253 254 private function loadFreeBindings(DrydockBlueprint $blueprint) { 255 if ($this->freeBindings === null) { 256 $viewer = $this->getViewer(); 257 258 $pool = id(new DrydockResourceQuery()) 259 ->setViewer($viewer) 260 ->withBlueprintPHIDs(array($blueprint->getPHID())) 261 ->withStatuses( 262 array( 263 DrydockResourceStatus::STATUS_PENDING, 264 DrydockResourceStatus::STATUS_ACTIVE, 265 DrydockResourceStatus::STATUS_BROKEN, 266 DrydockResourceStatus::STATUS_RELEASED, 267 )) 268 ->execute(); 269 270 $allocated_phids = array(); 271 foreach ($pool as $resource) { 272 $allocated_phids[] = $resource->getAttribute('almanacBindingPHID'); 273 } 274 $allocated_phids = array_fuse($allocated_phids); 275 276 $services = $this->loadServices($blueprint); 277 $bindings = $this->getActiveBindings($services); 278 279 $free = array(); 280 foreach ($bindings as $binding) { 281 if (empty($allocated_phids[$binding->getPHID()])) { 282 $free[] = $binding; 283 } 284 } 285 286 $this->freeBindings = $free; 287 } 288 289 return $this->freeBindings; 290 } 291 292 private function getAlmanacServiceTypes() { 293 return array( 294 AlmanacDrydockPoolServiceType::SERVICETYPE, 295 ); 296 } 297 298 private function loadBindingForResource(DrydockResource $resource) { 299 $binding_phid = $resource->getAttribute('almanacBindingPHID'); 300 if (!$binding_phid) { 301 throw new Exception( 302 pht( 303 'Drydock resource ("%s") has no Almanac binding PHID, so its '. 304 'binding can not be loaded.', 305 $resource->getPHID())); 306 } 307 308 $viewer = $this->getViewer(); 309 310 $binding = id(new AlmanacBindingQuery()) 311 ->setViewer($viewer) 312 ->withPHIDs(array($binding_phid)) 313 ->executeOne(); 314 if (!$binding) { 315 throw new Exception( 316 pht( 317 'Unable to load Almanac binding ("%s") for resource ("%s").', 318 $binding_phid, 319 $resource->getPHID())); 320 } 321 322 return $binding; 323 } 324 325}