1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-19 21:32:43 +01:00

In Harbormaster, make sure artifacts are destroyed even if a build is aborted

Summary:
Ref T9252. Currently, Harbormaster and Drydock work like this in some cases:

  # Queue a lease for activation.
  # Then, a little later, save the lease PHID somewhere.
  # When the target/resource is destroyed, destroy the lease.

However, something can happen between (1) and (2). In Drydock this window is very short and the "something" would have to be a lighting strike or something similar, but in Harbormaster we wait until the resource activates to do (2) so the window can be many minutes long. In particular, a user can use "Abort Build" during those many minutes.

If they do, the target is destroyed but it doesn't yet have a record of the artifact, so the artifact isn't cleaned up.

Make these things work like this instead:

  # Create a new lease and pre-generate a PHID for it.
  # Save that PHID as something that needs to be cleaned up.
  # Queue the lease for activation.
  # When the target/resource is destroyed, destroy the lease if it exists.

This makes sure there's no step in the process where we might lose track of a lease/resource.

Also, clean up and standardize some other stuff I hit.

Test Plan:
  - Stopped daemons.
  - Restarted a build in Harbormaster.
  - Stepped through the build one stage at a time using `bin/worker execute ...`.
  - After the lease was queued, but before it activated, aborted the build.
  - Processed the Harbormaster side of things only.
  - Saw the lease get destroyed properly.

Reviewers: chad, hach-que

Reviewed By: hach-que

Maniphest Tasks: T9252

Differential Revision: https://secure.phabricator.com/D14234
This commit is contained in:
epriestley 2015-10-05 05:58:53 -07:00
parent 0db86cce7d
commit 4cf1270ecd
11 changed files with 111 additions and 55 deletions

View file

@ -262,13 +262,14 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
array( array(
DrydockResourceStatus::STATUS_PENDING, DrydockResourceStatus::STATUS_PENDING,
DrydockResourceStatus::STATUS_ACTIVE, DrydockResourceStatus::STATUS_ACTIVE,
DrydockResourceStatus::STATUS_BROKEN,
DrydockResourceStatus::STATUS_RELEASED, DrydockResourceStatus::STATUS_RELEASED,
)) ))
->execute(); ->execute();
$allocated_phids = array(); $allocated_phids = array();
foreach ($pool as $resource) { foreach ($pool as $resource) {
$allocated_phids[] = $resource->getAttribute('almanacDevicePHID'); $allocated_phids[] = $resource->getAttribute('almanacBindingPHID');
} }
$allocated_phids = array_fuse($allocated_phids); $allocated_phids = array_fuse($allocated_phids);

View file

@ -288,7 +288,7 @@ abstract class DrydockBlueprintImplementation extends Phobject {
} }
protected function newLease(DrydockBlueprint $blueprint) { protected function newLease(DrydockBlueprint $blueprint) {
return id(new DrydockLease()); return DrydockLease::initializeNewLease();
} }
protected function requireActiveLease(DrydockLease $lease) { protected function requireActiveLease(DrydockLease $lease) {

View file

@ -104,8 +104,13 @@ final class DrydockWorkingCopyBlueprintImplementation
$host_lease = $this->newLease($blueprint) $host_lease = $this->newLease($blueprint)
->setResourceType('host') ->setResourceType('host')
->setOwnerPHID($resource_phid) ->setOwnerPHID($resource_phid)
->setAttribute('workingcopy.resourcePHID', $resource_phid) ->setAttribute('workingcopy.resourcePHID', $resource_phid);
->queueForActivation();
$resource
->setAttribute('host.leasePHID', $host_lease->getPHID())
->save();
$host_lease->queueForActivation();
// TODO: Add some limits to the number of working copies we can have at // TODO: Add some limits to the number of working copies we can have at
// once? // once?
@ -121,7 +126,6 @@ final class DrydockWorkingCopyBlueprintImplementation
return $resource return $resource
->setAttribute('repositories.map', $map) ->setAttribute('repositories.map', $map)
->setAttribute('host.leasePHID', $host_lease->getPHID())
->allocateResource(); ->allocateResource();
} }
@ -165,7 +169,13 @@ final class DrydockWorkingCopyBlueprintImplementation
DrydockBlueprint $blueprint, DrydockBlueprint $blueprint,
DrydockResource $resource) { DrydockResource $resource) {
$lease = $this->loadHostLease($resource); try {
$lease = $this->loadHostLease($resource);
} catch (Exception $ex) {
// If we can't load the lease, assume we don't need to take any actions
// to destroy it.
return;
}
// Destroy the lease on the host. // Destroy the lease on the host.
$lease->releaseOnDestruction(); $lease->releaseOnDestruction();

View file

@ -19,7 +19,7 @@ final class DrydockLeaseWaitingForResourcesLogType extends DrydockLogType {
return pht( return pht(
'Waiting for available resources from: %s.', 'Waiting for available resources from: %s.',
$viewer->renderHandleList($blueprint_phids)); $viewer->renderHandleList($blueprint_phids)->render());
} }
} }

View file

@ -19,6 +19,16 @@ final class DrydockLease extends DrydockDAO
private $activateWhenAcquired = false; private $activateWhenAcquired = false;
private $slotLocks = array(); private $slotLocks = array();
public static function initializeNewLease() {
$lease = new DrydockLease();
// Pregenerate a PHID so that the caller can set something up to release
// this lease before queueing it for activation.
$lease->setPHID($lease->generatePHID());
return $lease;
}
/** /**
* Flag this lease to be released when its destructor is called. This is * Flag this lease to be released when its destructor is called. This is
* mostly useful if you have a script which acquires, uses, and then releases * mostly useful if you have a script which acquires, uses, and then releases
@ -232,6 +242,7 @@ final class DrydockLease extends DrydockDAO
} }
$this->openTransaction(); $this->openTransaction();
try { try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array(); $this->slotLocks = array();
@ -247,16 +258,12 @@ final class DrydockLease extends DrydockDAO
throw $ex; throw $ex;
} }
try { $this
$this ->setResourcePHID($resource->getPHID())
->setResourcePHID($resource->getPHID()) ->attachResource($resource)
->attachResource($resource) ->setStatus($new_status)
->setStatus($new_status) ->save();
->save();
} catch (Exception $ex) {
$this->killTransaction();
throw $ex;
}
$this->saveTransaction(); $this->saveTransaction();
$this->isAcquired = true; $this->isAcquired = true;
@ -295,12 +302,24 @@ final class DrydockLease extends DrydockDAO
$this->openTransaction(); $this->openTransaction();
$this try {
->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)
->save();
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array(); $this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)
->save();
$this->saveTransaction(); $this->saveTransaction();

View file

@ -113,13 +113,6 @@ final class DrydockResource extends DrydockDAO
} }
public function allocateResource() { public function allocateResource() {
if ($this->getID()) {
throw new Exception(
pht(
'Trying to allocate a resource which has already been persisted. '.
'Only new resources may be allocated.'));
}
// We expect resources to have a pregenerated PHID, as they should have // We expect resources to have a pregenerated PHID, as they should have
// been created by a call to DrydockBlueprint->newResourceTemplate(). // been created by a call to DrydockBlueprint->newResourceTemplate().
if (!$this->getPHID()) { if (!$this->getPHID()) {
@ -155,9 +148,14 @@ final class DrydockResource extends DrydockDAO
} catch (DrydockSlotLockException $ex) { } catch (DrydockSlotLockException $ex) {
$this->killTransaction(); $this->killTransaction();
// NOTE: We have to log this on the blueprint, as the resource is not if ($this->getID()) {
// going to be saved so the PHID will vanish. $log_target = $this;
$this->getBlueprint()->logEvent( } else {
// If we don't have an ID, we have to log this on the blueprint, as the
// resource is not going to be saved so the PHID will vanish.
$log_target = $this->getBlueprint();
}
$log_target->logEvent(
DrydockSlotLockFailureLogType::LOGCONST, DrydockSlotLockFailureLogType::LOGCONST,
array( array(
'locks' => $ex->getLockMap(), 'locks' => $ex->getLockMap(),
@ -166,14 +164,9 @@ final class DrydockResource extends DrydockDAO
throw $ex; throw $ex;
} }
try { $this
$this ->setStatus($new_status)
->setStatus($new_status) ->save();
->save();
} catch (Exception $ex) {
$this->killTransaction();
throw $ex;
}
$this->saveTransaction(); $this->saveTransaction();
@ -210,12 +203,24 @@ final class DrydockResource extends DrydockDAO
$this->openTransaction(); $this->openTransaction();
$this try {
->setStatus(DrydockResourceStatus::STATUS_ACTIVE)
->save();
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array(); $this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
$this
->setStatus(DrydockResourceStatus::STATUS_ACTIVE)
->save();
$this->saveTransaction(); $this->saveTransaction();

View file

@ -149,6 +149,7 @@ final class DrydockSlotLock extends DrydockDAO {
// time we should be able to figure out which locks are already held. // time we should be able to figure out which locks are already held.
$held = self::loadHeldLocks($locks); $held = self::loadHeldLocks($locks);
$held = mpull($held, 'getOwnerPHID', 'getLockKey'); $held = mpull($held, 'getOwnerPHID', 'getLockKey');
throw new DrydockSlotLockException($held); throw new DrydockSlotLockException($held);
} }
} }

View file

@ -29,7 +29,9 @@ abstract class HarbormasterDrydockLeaseArtifact
} }
public function willCreateArtifact(PhabricatorUser $actor) { public function willCreateArtifact(PhabricatorUser $actor) {
$this->loadArtifactLease($actor); // We don't load the lease here because it's expected that artifacts are
// created before leases actually exist. This guarantees that the leases
// will be cleaned up.
} }
public function loadArtifactLease(PhabricatorUser $viewer) { public function loadArtifactLease(PhabricatorUser $viewer) {
@ -51,7 +53,15 @@ abstract class HarbormasterDrydockLeaseArtifact
} }
public function releaseArtifact(PhabricatorUser $actor) { public function releaseArtifact(PhabricatorUser $actor) {
$lease = $this->loadArtifactLease($actor); try {
$lease = $this->loadArtifactLease($actor);
} catch (Exception $ex) {
// If we can't load the lease, treat it as already released. Artifacts
// are generated before leases are queued, so it's possible to arrive
// here under normal conditions.
return;
}
if (!$lease->canRelease()) { if (!$lease->canRelease()) {
return; return;
} }

View file

@ -41,7 +41,7 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
$working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation()) $working_copy_type = id(new DrydockWorkingCopyBlueprintImplementation())
->getType(); ->getType();
$lease = id(new DrydockLease()) $lease = DrydockLease::initializeNewLease()
->setResourceType($working_copy_type) ->setResourceType($working_copy_type)
->setOwnerPHID($build_target->getPHID()); ->setOwnerPHID($build_target->getPHID());
@ -54,6 +54,18 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
$lease->setAwakenTaskIDs(array($task_id)); $lease->setAwakenTaskIDs(array($task_id));
} }
// TODO: Maybe add a method to mark artifacts like this as pending?
// Create the artifact now so that the lease is always disposed of, even
// if this target is aborted.
$build_target->createArtifact(
$viewer,
$settings['name'],
HarbormasterWorkingCopyArtifact::ARTIFACTCONST,
array(
'drydockLeasePHID' => $lease->getPHID(),
));
$lease->queueForActivation(); $lease->queueForActivation();
$build_target $build_target
@ -73,14 +85,6 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
'Lease "%s" never activated.', 'Lease "%s" never activated.',
$lease->getPHID())); $lease->getPHID()));
} }
$artifact = $build_target->createArtifact(
$viewer,
$settings['name'],
HarbormasterWorkingCopyArtifact::ARTIFACTCONST,
array(
'drydockLeasePHID' => $lease->getPHID(),
));
} }
public function getArtifactOutputs() { public function getArtifactOutputs() {

View file

@ -42,7 +42,8 @@ final class PhabricatorWorkerManagementExecuteWorkflow
$task->getDataID()); $task->getDataID());
$task->setData($task_data->getData()); $task->setData($task_data->getData());
$console->writeOut( echo tsprintf(
"%s\n",
pht( pht(
'Executing task %d (%s)...', 'Executing task %d (%s)...',
$task->getID(), $task->getID(),

View file

@ -1398,6 +1398,11 @@ final class PhabricatorUSEnglishTranslation
'Setting retention policy for "%s" to %s days.', 'Setting retention policy for "%s" to %s days.',
), ),
'Waiting %s second(s) for lease to activate.' => array(
'Waiting a second for lease to activate.',
'Waiting %s seconds for lease to activate.',
),
); );
} }