Path: blob/master/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php
12256 views
<?php12final class DrydockAlmanacServiceHostBlueprintImplementation3extends DrydockBlueprintImplementation {45private $services;6private $freeBindings;78public function isEnabled() {9$almanac_app = 'PhabricatorAlmanacApplication';10return PhabricatorApplication::isClassInstalled($almanac_app);11}1213public function getBlueprintName() {14return pht('Almanac Hosts');15}1617public function getBlueprintIcon() {18return 'fa-server';19}2021public function getDescription() {22return pht(23'Allows Drydock to lease existing hosts defined in an Almanac service '.24'pool.');25}2627public function canAnyBlueprintEverAllocateResourceForLease(28DrydockLease $lease) {29return true;30}3132public function canEverAllocateResourceForLease(33DrydockBlueprint $blueprint,34DrydockLease $lease) {35$services = $this->loadServices($blueprint);36$bindings = $this->getActiveBindings($services);3738if (!$bindings) {39// If there are no devices bound to the services for this blueprint,40// we can not allocate resources.41return false;42}4344return true;45}4647public function shouldAllocateSupplementalResource(48DrydockBlueprint $blueprint,49DrydockResource $resource,50DrydockLease $lease) {51// We want to use every host in an Almanac service, since the amount of52// hardware is fixed and there's normally no value in packing leases onto a53// subset of it. Always build a new supplemental resource if we can.54return true;55}5657public function canAllocateResourceForLease(58DrydockBlueprint $blueprint,59DrydockLease $lease) {6061// We will only allocate one resource per unique device bound to the62// services for this blueprint. Make sure we have a free device somewhere.63$free_bindings = $this->loadFreeBindings($blueprint);64if (!$free_bindings) {65return false;66}6768return true;69}7071public function allocateResource(72DrydockBlueprint $blueprint,73DrydockLease $lease) {7475$free_bindings = $this->loadFreeBindings($blueprint);76shuffle($free_bindings);7778$exceptions = array();79foreach ($free_bindings as $binding) {80$device = $binding->getDevice();81$device_name = $device->getName();8283$binding_phid = $binding->getPHID();8485$resource = $this->newResourceTemplate($blueprint)86->setActivateWhenAllocated(true)87->setAttribute('almanacDeviceName', $device_name)88->setAttribute('almanacServicePHID', $binding->getServicePHID())89->setAttribute('almanacBindingPHID', $binding_phid)90->needSlotLock("almanac.host.binding({$binding_phid})");9192try {93return $resource->allocateResource();94} catch (Exception $ex) {95$exceptions[] = $ex;96}97}9899throw new PhutilAggregateException(100pht('Unable to allocate any binding as a resource.'),101$exceptions);102}103104public function destroyResource(105DrydockBlueprint $blueprint,106DrydockResource $resource) {107// We don't create anything when allocating hosts, so we don't need to do108// any cleanup here.109return;110}111112public function getResourceName(113DrydockBlueprint $blueprint,114DrydockResource $resource) {115$device_name = $resource->getAttribute(116'almanacDeviceName',117pht('<Unknown>'));118return pht('Host (%s)', $device_name);119}120121public function canAcquireLeaseOnResource(122DrydockBlueprint $blueprint,123DrydockResource $resource,124DrydockLease $lease) {125126// Require the binding to a given host be active before we'll hand out more127// leases on the corresponding resource.128$binding = $this->loadBindingForResource($resource);129if ($binding->getIsDisabled()) {130return false;131}132133return true;134}135136public function acquireLease(137DrydockBlueprint $blueprint,138DrydockResource $resource,139DrydockLease $lease) {140141$lease142->setActivateWhenAcquired(true)143->acquireOnResource($resource);144}145146public function didReleaseLease(147DrydockBlueprint $blueprint,148DrydockResource $resource,149DrydockLease $lease) {150// Almanac hosts stick around indefinitely so we don't need to recycle them151// if they don't have any leases.152return;153}154155public function destroyLease(156DrydockBlueprint $blueprint,157DrydockResource $resource,158DrydockLease $lease) {159// We don't create anything when activating a lease, so we don't need to160// throw anything away.161return;162}163164public function getType() {165return 'host';166}167168public function getInterface(169DrydockBlueprint $blueprint,170DrydockResource $resource,171DrydockLease $lease,172$type) {173174switch ($type) {175case DrydockCommandInterface::INTERFACE_TYPE:176$credential_phid = $blueprint->getFieldValue('credentialPHID');177$binding = $this->loadBindingForResource($resource);178$interface = $binding->getInterface();179180return id(new DrydockSSHCommandInterface())181->setConfig('credentialPHID', $credential_phid)182->setConfig('host', $interface->getAddress())183->setConfig('port', $interface->getPort());184}185}186187protected function getCustomFieldSpecifications() {188return array(189'almanacServicePHIDs' => array(190'name' => pht('Almanac Services'),191'type' => 'datasource',192'datasource.class' => 'AlmanacServiceDatasource',193'datasource.parameters' => array(194'serviceTypes' => $this->getAlmanacServiceTypes(),195),196'required' => true,197),198'credentialPHID' => array(199'name' => pht('Credentials'),200'type' => 'credential',201'credential.provides' =>202PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE,203'credential.type' =>204PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE,205),206);207}208209private function loadServices(DrydockBlueprint $blueprint) {210if (!$this->services) {211$service_phids = $blueprint->getFieldValue('almanacServicePHIDs');212if (!$service_phids) {213throw new Exception(214pht(215'This blueprint ("%s") does not define any Almanac Service PHIDs.',216$blueprint->getBlueprintName()));217}218219$viewer = $this->getViewer();220$services = id(new AlmanacServiceQuery())221->setViewer($viewer)222->withPHIDs($service_phids)223->withServiceTypes($this->getAlmanacServiceTypes())224->needActiveBindings(true)225->execute();226$services = mpull($services, null, 'getPHID');227228if (count($services) != count($service_phids)) {229$missing_phids = array_diff($service_phids, array_keys($services));230throw new Exception(231pht(232'Some of the Almanac Services defined by this blueprint '.233'could not be loaded. They may be invalid, no longer exist, '.234'or be of the wrong type: %s.',235implode(', ', $missing_phids)));236}237238$this->services = $services;239}240241return $this->services;242}243244private function getActiveBindings(array $services) {245assert_instances_of($services, 'AlmanacService');246$bindings = array_mergev(mpull($services, 'getActiveBindings'));247return mpull($bindings, null, 'getPHID');248}249250private function loadFreeBindings(DrydockBlueprint $blueprint) {251if ($this->freeBindings === null) {252$viewer = $this->getViewer();253254$pool = id(new DrydockResourceQuery())255->setViewer($viewer)256->withBlueprintPHIDs(array($blueprint->getPHID()))257->withStatuses(258array(259DrydockResourceStatus::STATUS_PENDING,260DrydockResourceStatus::STATUS_ACTIVE,261DrydockResourceStatus::STATUS_BROKEN,262DrydockResourceStatus::STATUS_RELEASED,263))264->execute();265266$allocated_phids = array();267foreach ($pool as $resource) {268$allocated_phids[] = $resource->getAttribute('almanacBindingPHID');269}270$allocated_phids = array_fuse($allocated_phids);271272$services = $this->loadServices($blueprint);273$bindings = $this->getActiveBindings($services);274275$free = array();276foreach ($bindings as $binding) {277if (empty($allocated_phids[$binding->getPHID()])) {278$free[] = $binding;279}280}281282$this->freeBindings = $free;283}284285return $this->freeBindings;286}287288private function getAlmanacServiceTypes() {289return array(290AlmanacDrydockPoolServiceType::SERVICETYPE,291);292}293294private function loadBindingForResource(DrydockResource $resource) {295$binding_phid = $resource->getAttribute('almanacBindingPHID');296if (!$binding_phid) {297throw new Exception(298pht(299'Drydock resource ("%s") has no Almanac binding PHID, so its '.300'binding can not be loaded.',301$resource->getPHID()));302}303304$viewer = $this->getViewer();305306$binding = id(new AlmanacBindingQuery())307->setViewer($viewer)308->withPHIDs(array($binding_phid))309->executeOne();310if (!$binding) {311throw new Exception(312pht(313'Unable to load Almanac binding ("%s") for resource ("%s").',314$binding_phid,315$resource->getPHID()));316}317318return $binding;319}320321}322323324