1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-02 03:32:42 +01:00

(stable) Promote 2015 Week 39

This commit is contained in:
epriestley 2015-09-26 07:01:28 -07:00
commit 256fd47f2a
140 changed files with 4846 additions and 1962 deletions

View file

@ -7,7 +7,7 @@
*/
return array(
'names' => array(
'core.pkg.css' => '5eabac59',
'core.pkg.css' => 'a11c3643',
'core.pkg.js' => '47dc9ebb',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
@ -25,7 +25,7 @@ return array(
'rsrc/css/aphront/notification.css' => '9c279160',
'rsrc/css/aphront/panel-view.css' => '8427b78d',
'rsrc/css/aphront/phabricator-nav-view.css' => 'a24cb589',
'rsrc/css/aphront/table-view.css' => '34ee903e',
'rsrc/css/aphront/table-view.css' => '63985f5b',
'rsrc/css/aphront/tokenizer.css' => '04875312',
'rsrc/css/aphront/tooltip.css' => '7672b60f',
'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c',
@ -104,7 +104,7 @@ return array(
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de',
'rsrc/css/core/core.css' => 'a76cefc9',
'rsrc/css/core/remarkup.css' => 'e27a26b2',
'rsrc/css/core/remarkup.css' => 'fa3a8225',
'rsrc/css/core/syntax.css' => '9fd11da8',
'rsrc/css/core/z-index.css' => '57ddcaa2',
'rsrc/css/diviner/diviner-shared.css' => '5a337049',
@ -492,7 +492,7 @@ return array(
'aphront-list-filter-view-css' => '5d6f0526',
'aphront-multi-column-view-css' => 'fd18389d',
'aphront-panel-view-css' => '8427b78d',
'aphront-table-view-css' => '34ee903e',
'aphront-table-view-css' => '63985f5b',
'aphront-tokenizer-control-css' => '04875312',
'aphront-tooltip-css' => '7672b60f',
'aphront-typeahead-control-css' => '0e403212',
@ -733,7 +733,7 @@ return array(
'phabricator-object-selector-css' => '85ee8ce6',
'phabricator-phtize' => 'd254d646',
'phabricator-prefab' => '6920d200',
'phabricator-remarkup-css' => 'e27a26b2',
'phabricator-remarkup-css' => 'fa3a8225',
'phabricator-search-results-css' => '7dea472c',
'phabricator-shaped-request' => '7cbe244b',
'phabricator-side-menu-view-css' => 'bec2458e',

View file

@ -9,41 +9,53 @@ $metadata = array(
'edge:type' => PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
);
foreach (new LiskMigrationIterator($table) as $txn) {
// ManiphestTransaction::TYPE_PROJECTS
if ($txn->getTransactionType() == 'projects') {
$old_value = mig20141222_build_edge_data(
$txn->getOldValue(),
$txn->getObjectPHID());
$new_value = mig20141222_build_edge_data(
$txn->getNewvalue(),
$txn->getObjectPHID());
queryfx(
$conn_w,
'UPDATE %T SET '.
'transactionType = %s, oldValue = %s, newValue = %s, metaData = %s '.
'WHERE id = %d',
$table->getTableName(),
PhabricatorTransactions::TYPE_EDGE,
json_encode($old_value),
json_encode($new_value),
json_encode($metadata),
$txn->getID());
if ($txn->getTransactionType() != 'projects') {
continue;
}
$old_value = mig20141222_build_edge_data(
$txn->getOldValue(),
$txn->getObjectPHID());
$new_value = mig20141222_build_edge_data(
$txn->getNewValue(),
$txn->getObjectPHID());
queryfx(
$conn_w,
'UPDATE %T SET '.
'transactionType = %s, oldValue = %s, newValue = %s, metaData = %s '.
'WHERE id = %d',
$table->getTableName(),
PhabricatorTransactions::TYPE_EDGE,
json_encode($old_value),
json_encode($new_value),
json_encode($metadata),
$txn->getID());
}
echo pht('Done.')."\n";
function mig20141222_build_edge_data(array $project_phids, $task_phid) {
function mig20141222_build_edge_data($project_phids, $task_phid) {
$edge_data = array();
// See T9464. If we didn't get a proper array value out of the transaction,
// just return an empty value so we can move forward.
if (!is_array($project_phids)) {
return $edge_data;
}
foreach ($project_phids as $project_phid) {
if (!is_scalar($project_phid)) {
continue;
}
$edge_data[$project_phid] = array(
'src' => $task_phid,
'type' => PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
'dst' => $project_phid,
);
}
return $edge_data;
}

View file

@ -0,0 +1,8 @@
CREATE TABLE {$NAMESPACE}_drydock.drydock_slotlock (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
ownerPHID VARBINARY(64) NOT NULL,
lockIndex BINARY(12) NOT NULL,
lockKey LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
UNIQUE KEY `key_lock` (lockIndex),
KEY `key_owner` (ownerPHID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,10 @@
CREATE TABLE {$NAMESPACE}_drydock.drydock_command (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
authorPHID VARBINARY(64) NOT NULL,
targetPHID VARBINARY(64) NOT NULL,
command VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
isConsumed BOOL NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
KEY `key_target` (targetPHID, isConsumed)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
ADD resourcePHID VARBINARY(64);

View file

@ -0,0 +1,5 @@
UPDATE
{$NAMESPACE}_drydock.drydock_lease l,
{$NAMESPACE}_drydock.drydock_resource r
SET l.resourcePHID = r.phid
WHERE l.resourceID = r.id;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
DROP resourceID;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
DROP taskID;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_blueprint
ADD isDisabled BOOL NOT NULL;

View file

@ -0,0 +1,39 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
CHANGE status status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
UPDATE {$NAMESPACE}_drydock.drydock_lease
SET status = 'pending' WHERE status = '0';
UPDATE {$NAMESPACE}_drydock.drydock_lease
SET status = 'acquired' WHERE status = '5';
UPDATE {$NAMESPACE}_drydock.drydock_lease
SET status = 'active' WHERE status = '1';
UPDATE {$NAMESPACE}_drydock.drydock_lease
SET status = 'released' WHERE status = '2';
UPDATE {$NAMESPACE}_drydock.drydock_lease
SET status = 'broken' WHERE status = '3';
UPDATE {$NAMESPACE}_drydock.drydock_lease
SET status = 'destroyed' WHERE status = '4';
ALTER TABLE {$NAMESPACE}_drydock.drydock_resource
CHANGE status status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
UPDATE {$NAMESPACE}_drydock.drydock_resource
SET status = 'pending' WHERE status = '0';
UPDATE {$NAMESPACE}_drydock.drydock_resource
SET status = 'active' WHERE status = '1';
UPDATE {$NAMESPACE}_drydock.drydock_resource
SET status = 'released' WHERE status = '2';
UPDATE {$NAMESPACE}_drydock.drydock_resource
SET status = 'broken' WHERE status = '3';
UPDATE {$NAMESPACE}_drydock.drydock_resource
SET status = 'destroyed' WHERE status = '4';

View file

@ -44,6 +44,7 @@ phutil_register_library_map(array(
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php',
'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php',
'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php',
'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php',
'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php',
'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php',
@ -796,6 +797,7 @@ phutil_register_library_map(array(
'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php',
'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php',
'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php',
'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php',
'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php',
@ -803,6 +805,7 @@ phutil_register_library_map(array(
'DrydockBlueprintCreateController' => 'applications/drydock/controller/DrydockBlueprintCreateController.php',
'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php',
'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php',
'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php',
'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php',
'DrydockBlueprintEditor' => 'applications/drydock/editor/DrydockBlueprintEditor.php',
'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php',
@ -810,12 +813,13 @@ phutil_register_library_map(array(
'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php',
'DrydockBlueprintPHIDType' => 'applications/drydock/phid/DrydockBlueprintPHIDType.php',
'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php',
'DrydockBlueprintScopeGuard' => 'applications/drydock/util/DrydockBlueprintScopeGuard.php',
'DrydockBlueprintSearchEngine' => 'applications/drydock/query/DrydockBlueprintSearchEngine.php',
'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php',
'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php',
'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php',
'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php',
'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php',
'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php',
'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php',
'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php',
'DrydockController' => 'applications/drydock/controller/DrydockController.php',
@ -828,6 +832,7 @@ phutil_register_library_map(array(
'DrydockLease' => 'applications/drydock/storage/DrydockLease.php',
'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php',
'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php',
'DrydockLeaseDestroyWorker' => 'applications/drydock/worker/DrydockLeaseDestroyWorker.php',
'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php',
'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php',
'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php',
@ -835,35 +840,43 @@ phutil_register_library_map(array(
'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php',
'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php',
'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php',
'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php',
'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php',
'DrydockLocalCommandInterface' => 'applications/drydock/interface/command/DrydockLocalCommandInterface.php',
'DrydockLeaseWorker' => 'applications/drydock/worker/DrydockLeaseWorker.php',
'DrydockLog' => 'applications/drydock/storage/DrydockLog.php',
'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php',
'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php',
'DrydockLogListView' => 'applications/drydock/view/DrydockLogListView.php',
'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php',
'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php',
'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php',
'DrydockManagementCreateResourceWorkflow' => 'applications/drydock/management/DrydockManagementCreateResourceWorkflow.php',
'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php',
'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php',
'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php',
'DrydockManagementReleaseLeaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php',
'DrydockManagementReleaseResourceWorkflow' => 'applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php',
'DrydockManagementUpdateLeaseWorkflow' => 'applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php',
'DrydockManagementUpdateResourceWorkflow' => 'applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php',
'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
'DrydockPreallocatedHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php',
'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php',
'DrydockResource' => 'applications/drydock/storage/DrydockResource.php',
'DrydockResourceCloseController' => 'applications/drydock/controller/DrydockResourceCloseController.php',
'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php',
'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php',
'DrydockResourceDestroyWorker' => 'applications/drydock/worker/DrydockResourceDestroyWorker.php',
'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php',
'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php',
'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php',
'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php',
'DrydockResourceReleaseController' => 'applications/drydock/controller/DrydockResourceReleaseController.php',
'DrydockResourceSearchEngine' => 'applications/drydock/query/DrydockResourceSearchEngine.php',
'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php',
'DrydockResourceUpdateWorker' => 'applications/drydock/worker/DrydockResourceUpdateWorker.php',
'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php',
'DrydockResourceWorker' => 'applications/drydock/worker/DrydockResourceWorker.php',
'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php',
'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php',
'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php',
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php',
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php',
'FeedPublishConduitAPIMethod' => 'applications/feed/conduit/FeedPublishConduitAPIMethod.php',
@ -989,11 +1002,15 @@ phutil_register_library_map(array(
'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php',
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php',
'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php',
'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php',
'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php',
'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php',
'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php',
'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php',
'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php',
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php',
'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php',
'HarbormasterManagePlansCapability' => 'applications/harbormaster/capability/HarbormasterManagePlansCapability.php',
@ -1034,6 +1051,7 @@ phutil_register_library_map(array(
'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php',
'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php',
'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php',
'HarbormasterWorkingCopyArtifact' => 'applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php',
'HeraldAction' => 'applications/herald/action/HeraldAction.php',
'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php',
'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php',
@ -2492,6 +2510,7 @@ phutil_register_library_map(array(
'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php',
'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php',
'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php',
'PhabricatorPasteRawController' => 'applications/paste/controller/PhabricatorPasteRawController.php',
'PhabricatorPasteRemarkupRule' => 'applications/paste/remarkup/PhabricatorPasteRemarkupRule.php',
'PhabricatorPasteSchemaSpec' => 'applications/paste/storage/PhabricatorPasteSchemaSpec.php',
'PhabricatorPasteSearchEngine' => 'applications/paste/query/PhabricatorPasteSearchEngine.php',
@ -2915,6 +2934,7 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php',
'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php',
'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php',
'PhabricatorStandardCustomFieldDatasource' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php',
'PhabricatorStandardCustomFieldDate' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php',
'PhabricatorStandardCustomFieldHeader' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php',
'PhabricatorStandardCustomFieldInt' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php',
@ -2924,6 +2944,7 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomFieldRemarkup' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php',
'PhabricatorStandardCustomFieldSelect' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php',
'PhabricatorStandardCustomFieldText' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php',
'PhabricatorStandardCustomFieldTokenizer' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php',
'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php',
'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php',
'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php',
@ -3666,6 +3687,7 @@ phutil_register_library_map(array(
'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction',
'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacDeviceViewController' => 'AlmanacDeviceController',
'AlmanacDrydockPoolServiceType' => 'AlmanacServiceType',
'AlmanacInterface' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
@ -4498,7 +4520,8 @@ phutil_register_library_map(array(
'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'DoorkeeperTagView' => 'AphrontView',
'DoorkeeperTagsController' => 'PhabricatorController',
'DrydockAllocatorWorker' => 'PhabricatorWorker',
'DrydockAllocatorWorker' => 'DrydockWorker',
'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
'DrydockBlueprint' => array(
'DrydockDAO',
@ -4514,6 +4537,7 @@ phutil_register_library_map(array(
'DrydockBlueprintCreateController' => 'DrydockBlueprintController',
'DrydockBlueprintCustomField' => 'PhabricatorCustomField',
'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockBlueprintDisableController' => 'DrydockBlueprintController',
'DrydockBlueprintEditController' => 'DrydockBlueprintController',
'DrydockBlueprintEditor' => 'PhabricatorApplicationTransactionEditor',
'DrydockBlueprintImplementation' => 'Phobject',
@ -4521,12 +4545,16 @@ phutil_register_library_map(array(
'DrydockBlueprintListController' => 'DrydockBlueprintController',
'DrydockBlueprintPHIDType' => 'PhabricatorPHIDType',
'DrydockBlueprintQuery' => 'DrydockQuery',
'DrydockBlueprintScopeGuard' => 'Phobject',
'DrydockBlueprintSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockBlueprintTransaction' => 'PhabricatorApplicationTransaction',
'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DrydockBlueprintViewController' => 'DrydockBlueprintController',
'DrydockCommand' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
),
'DrydockCommandInterface' => 'DrydockInterface',
'DrydockCommandQuery' => 'DrydockQuery',
'DrydockConsoleController' => 'DrydockController',
'DrydockConstants' => 'Phobject',
'DrydockController' => 'PhabricatorController',
@ -4542,6 +4570,7 @@ phutil_register_library_map(array(
),
'DrydockLeaseController' => 'DrydockController',
'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockLeaseDestroyWorker' => 'DrydockWorker',
'DrydockLeaseListController' => 'DrydockLeaseController',
'DrydockLeaseListView' => 'AphrontView',
'DrydockLeasePHIDType' => 'PhabricatorPHIDType',
@ -4549,8 +4578,9 @@ phutil_register_library_map(array(
'DrydockLeaseReleaseController' => 'DrydockLeaseController',
'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockLeaseStatus' => 'DrydockConstants',
'DrydockLeaseUpdateWorker' => 'DrydockWorker',
'DrydockLeaseViewController' => 'DrydockLeaseController',
'DrydockLocalCommandInterface' => 'DrydockCommandInterface',
'DrydockLeaseWorker' => 'DrydockWorker',
'DrydockLog' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
@ -4560,30 +4590,37 @@ phutil_register_library_map(array(
'DrydockLogListView' => 'AphrontView',
'DrydockLogQuery' => 'DrydockQuery',
'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementCreateResourceWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementReleaseLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementReleaseResourceWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementUpdateLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementUpdateResourceWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',
'DrydockPreallocatedHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DrydockResource' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
),
'DrydockResourceCloseController' => 'DrydockResourceController',
'DrydockResourceController' => 'DrydockController',
'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockResourceDestroyWorker' => 'DrydockWorker',
'DrydockResourceListController' => 'DrydockResourceController',
'DrydockResourceListView' => 'AphrontView',
'DrydockResourcePHIDType' => 'PhabricatorPHIDType',
'DrydockResourceQuery' => 'DrydockQuery',
'DrydockResourceReleaseController' => 'DrydockResourceController',
'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockResourceStatus' => 'DrydockConstants',
'DrydockResourceUpdateWorker' => 'DrydockWorker',
'DrydockResourceViewController' => 'DrydockResourceController',
'DrydockResourceWorker' => 'DrydockWorker',
'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface',
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
'DrydockSlotLock' => 'DrydockDAO',
'DrydockSlotLockException' => 'Exception',
'DrydockWebrootInterface' => 'DrydockInterface',
'DrydockWorker' => 'PhabricatorWorker',
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
'FeedConduitAPIMethod' => 'ConduitAPIMethod',
'FeedPublishConduitAPIMethod' => 'FeedConduitAPIMethod',
@ -4755,11 +4792,15 @@ phutil_register_library_map(array(
'HarbormasterController' => 'PhabricatorController',
'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterDAO' => 'PhabricatorLiskDAO',
'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact',
'HarbormasterExecFuture' => 'Future',
'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterFileArtifact' => 'HarbormasterArtifact',
'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterHostArtifact' => 'HarbormasterArtifact',
'HarbormasterHostArtifact' => 'HarbormasterDrydockLeaseArtifact',
'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterLintMessagesController' => 'HarbormasterController',
'HarbormasterLintPropertyView' => 'AphrontView',
'HarbormasterManagePlansCapability' => 'PhabricatorPolicyCapability',
@ -4800,6 +4841,7 @@ phutil_register_library_map(array(
'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterWorker' => 'PhabricatorWorker',
'HarbormasterWorkingCopyArtifact' => 'HarbormasterDrydockLeaseArtifact',
'HeraldAction' => 'Phobject',
'HeraldActionGroup' => 'HeraldGroup',
'HeraldActionRecord' => 'HeraldDAO',
@ -6504,6 +6546,7 @@ phutil_register_library_map(array(
'PhabricatorPasteListController' => 'PhabricatorPasteController',
'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType',
'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPasteRawController' => 'PhabricatorPasteController',
'PhabricatorPasteRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'PhabricatorPasteSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorPasteSearchEngine' => 'PhabricatorApplicationSearchEngine',
@ -7022,6 +7065,7 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomField' => 'PhabricatorCustomField',
'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldDatasource' => 'PhabricatorStandardCustomFieldTokenizer',
'PhabricatorStandardCustomFieldDate' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldHeader' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldInt' => 'PhabricatorStandardCustomField',
@ -7030,7 +7074,8 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomFieldRemarkup' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldSelect' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldText' => 'PhabricatorStandardCustomField',
'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldPHIDs',
'PhabricatorStandardCustomFieldTokenizer' => 'PhabricatorStandardCustomFieldPHIDs',
'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldTokenizer',
'PhabricatorStandardPageView' => 'PhabricatorBarePageView',
'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorStatusController' => 'PhabricatorController',

View file

@ -0,0 +1,18 @@
<?php
final class AlmanacDrydockPoolServiceType extends AlmanacServiceType {
public function getServiceTypeShortName() {
return pht('Drydock Pool');
}
public function getServiceTypeName() {
return pht('Drydock: Resource Pool');
}
public function getServiceTypeDescription() {
return pht(
'Defines a pool of hosts which Drydock can allocate.');
}
}

View file

@ -23,6 +23,16 @@ final class AlmanacServiceDatasource
->withNamePrefix($raw_query)
->setOrder('name');
// TODO: When service classes are restricted, it might be nice to customize
// the title and placeholder text to show which service types can be
// selected, or show all services but mark the invalid ones disabled and
// prevent their selection.
$service_classes = $this->getParameter('serviceClasses');
if ($service_classes) {
$services->withServiceClasses($service_classes);
}
$services = $this->executeQuery($services);
if ($services) {

View file

@ -37,7 +37,8 @@ final class AuditQueryConduitAPIMethod extends AuditConduitAPIMethod {
protected function execute(ConduitAPIRequest $request) {
$query = id(new DiffusionCommitQuery())
->setViewer($request->getUser());
->setViewer($request->getUser())
->needAuditRequests(true);
$auditor_phids = $request->getValue('auditorPHIDs', array());
if ($auditor_phids) {

View file

@ -8,11 +8,11 @@ final class PhabricatorAuditMailReceiver extends PhabricatorObjectMailReceiver {
}
protected function getObjectPattern() {
return 'C[1-9]\d*';
return 'COMMIT[1-9]\d*';
}
protected function loadObject($pattern, PhabricatorUser $viewer) {
$id = (int)trim($pattern, 'C');
$id = (int)preg_replace('/^COMMIT/', '', $pattern);
return id(new DiffusionCommitQuery())
->setViewer($viewer)

View file

@ -13,9 +13,7 @@ final class PhabricatorAuditReplyHandler
}
public function getObjectPrefix() {
// TODO: This conflicts with Countdown and will probably need to be
// changed eventually.
return 'C';
return 'COMMIT';
}
}

View file

@ -22,6 +22,9 @@ final class PhabricatorBadgesEditController
}
$is_new = false;
} else {
$this->requireApplicationCapability(
PhabricatorBadgesCreateCapability::CAPABILITY);
$badge = PhabricatorBadgesBadge::initializeNewBadge($viewer);
$is_new = true;
}

View file

@ -109,7 +109,8 @@ final class PhabricatorBadgesViewController
'default',
$viewer);
$view->addSectionHeader(pht('Description'));
$view->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent($description);
}

View file

@ -201,7 +201,8 @@ final class PhabricatorConduitConsoleController
id(new PhabricatorMarkupOneOff())->setContent($description),
'default',
$viewer);
$view->addSectionHeader(pht('Description'));
$view->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent($description);
return $view;

View file

@ -443,6 +443,7 @@ final class DifferentialDiff
if ($repo) {
$results['repository.callsign'] = $repo->getCallsign();
$results['repository.phid'] = $repo->getPHID();
$results['repository.vcs'] = $repo->getVersionControlSystem();
$results['repository.uri'] = $repo->getPublicCloneURI();
}
@ -459,6 +460,8 @@ final class DifferentialDiff
pht('The differential revision ID, if applicable.'),
'repository.callsign' =>
pht('The callsign of the repository in Phabricator.'),
'repository.phid' =>
pht('The PHID of the repository in Phabricator.'),
'repository.vcs' =>
pht('The version control system, either "svn", "hg" or "git".'),
'repository.uri' =>

View file

@ -156,7 +156,8 @@ abstract class DiffusionBrowseController extends DiffusionController {
$tag = idx($tags, $symbolic);
if ($tag && strlen($tag->getMessage())) {
$view->addSectionHeader(pht('Tag Content'));
$view->addSectionHeader(
pht('Tag Content'), 'fa-tag');
$view->addTextContent($this->markupText($tag->getMessage()));
}
}

View file

@ -337,7 +337,8 @@ final class DiffusionBrowseFileController extends DiffusionBrowseController {
$corpus = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($corpus);
->appendChild($corpus)
->setCollapsed(true);
return $corpus;
}

View file

@ -289,7 +289,8 @@ final class DiffusionRepositoryController extends DiffusionController {
$repository,
'description',
$user);
$view->addSectionHeader(pht('Description'));
$view->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent($description);
}

View file

@ -294,7 +294,8 @@ final class DiffusionRepositoryEditMainController
$this->buildRepositoryUpdateInterval($repository));
$description = $repository->getDetail('description');
$view->addSectionHeader(pht('Description'));
$view->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
if (!strlen($description)) {
$description = phutil_tag('em', array(), pht('No description provided.'));
} else {

View file

@ -49,19 +49,31 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
'' => 'DrydockConsoleController',
'blueprint/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockBlueprintListController',
'(?P<id>[1-9]\d*)/' => 'DrydockBlueprintViewController',
'(?P<id>[1-9]\d*)/' => array(
'' => 'DrydockBlueprintViewController',
'(?P<action>disable|enable)/' =>
'DrydockBlueprintDisableController',
'resources/(?:query/(?P<queryKey>[^/]+)/)?' =>
'DrydockResourceListController',
),
'create/' => 'DrydockBlueprintCreateController',
'edit/(?:(?P<id>[1-9]\d*)/)?' => 'DrydockBlueprintEditController',
),
'resource/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockResourceListController',
'(?P<id>[1-9]\d*)/' => 'DrydockResourceViewController',
'(?P<id>[1-9]\d*)/close/' => 'DrydockResourceCloseController',
'(?P<id>[1-9]\d*)/' => array(
'' => 'DrydockResourceViewController',
'release/' => 'DrydockResourceReleaseController',
'leases/(?:query/(?P<queryKey>[^/]+)/)?' =>
'DrydockLeaseListController',
),
),
'lease/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockLeaseListController',
'(?P<id>[1-9]\d*)/' => 'DrydockLeaseViewController',
'(?P<id>[1-9]\d*)/release/' => 'DrydockLeaseReleaseController',
'(?P<id>[1-9]\d*)/' => array(
'' => 'DrydockLeaseViewController',
'release/' => 'DrydockLeaseReleaseController',
),
),
'log/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockLogListController',

View file

@ -0,0 +1,289 @@
<?php
final class DrydockAlmanacServiceHostBlueprintImplementation
extends DrydockBlueprintImplementation {
private $services;
private $freeBindings;
public function isEnabled() {
$almanac_app = 'PhabricatorAlmanacApplication';
return PhabricatorApplication::isClassInstalled($almanac_app);
}
public function getBlueprintName() {
return pht('Almanac Hosts');
}
public function getDescription() {
return pht(
'Allows Drydock to lease existing hosts defined in an Almanac service '.
'pool.');
}
public function canAnyBlueprintEverAllocateResourceForLease(
DrydockLease $lease) {
return true;
}
public function canEverAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$services = $this->loadServices($blueprint);
$bindings = $this->loadAllBindings($services);
if (!$bindings) {
// If there are no devices bound to the services for this blueprint,
// we can not allocate resources.
return false;
}
return true;
}
public function canAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
// We will only allocate one resource per unique device bound to the
// services for this blueprint. Make sure we have a free device somewhere.
$free_bindings = $this->loadFreeBindings($blueprint);
if (!$free_bindings) {
return false;
}
return true;
}
public function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$free_bindings = $this->loadFreeBindings($blueprint);
shuffle($free_bindings);
$exceptions = array();
foreach ($free_bindings as $binding) {
$device = $binding->getDevice();
$device_name = $device->getName();
$binding_phid = $binding->getPHID();
$resource = $this->newResourceTemplate($blueprint, $device_name)
->setActivateWhenAllocated(true)
->setAttribute('almanacServicePHID', $binding->getServicePHID())
->setAttribute('almanacBindingPHID', $binding_phid)
->needSlotLock("almanac.host.binding({$binding_phid})");
try {
return $resource->allocateResource();
} catch (Exception $ex) {
$exceptions[] = $ex;
}
}
throw new PhutilAggregateException(
pht('Unable to allocate any binding as a resource.'),
$exceptions);
}
public function destroyResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
// We don't create anything when allocating hosts, so we don't need to do
// any cleanup here.
return;
}
public function canAcquireLeaseOnResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
if (!DrydockSlotLock::isLockFree($this->getLeaseSlotLock($resource))) {
return false;
}
return true;
}
public function acquireLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
$lease
->setActivateWhenAcquired(true)
->needSlotLock($this->getLeaseSlotLock($resource))
->acquireOnResource($resource);
}
public function didReleaseLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// Almanac hosts stick around indefinitely so we don't need to recycle them
// if they don't have any leases.
return;
}
public function destroyLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// We don't create anything when activating a lease, so we don't need to
// throw anything away.
return;
}
private function getLeaseSlotLock(DrydockResource $resource) {
$resource_phid = $resource->getPHID();
return "almanac.host.lease({$resource_phid})";
}
public function getType() {
return 'host';
}
public function getInterface(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease,
$type) {
$viewer = PhabricatorUser::getOmnipotentUser();
switch ($type) {
case DrydockCommandInterface::INTERFACE_TYPE:
$credential_phid = $blueprint->getFieldValue('credentialPHID');
$binding_phid = $resource->getAttribute('almanacBindingPHID');
$binding = id(new AlmanacBindingQuery())
->setViewer($viewer)
->withPHIDs(array($binding_phid))
->executeOne();
if (!$binding) {
// TODO: This is probably a permanent failure, destroy this resource?
throw new Exception(
pht(
'Unable to load binding "%s" to create command interface.',
$binding_phid));
}
$interface = $binding->getInterface();
return id(new DrydockSSHCommandInterface())
->setConfig('credentialPHID', $credential_phid)
->setConfig('host', $interface->getAddress())
->setConfig('port', $interface->getPort());
}
}
public function getFieldSpecifications() {
return array(
'almanacServicePHIDs' => array(
'name' => pht('Almanac Services'),
'type' => 'datasource',
'datasource.class' => 'AlmanacServiceDatasource',
'datasource.parameters' => array(
'serviceClasses' => $this->getAlmanacServiceClasses(),
),
'required' => true,
),
'credentialPHID' => array(
'name' => pht('Credentials'),
'type' => 'credential',
'credential.provides' =>
PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE,
'credential.type' =>
PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE,
),
) + parent::getFieldSpecifications();
}
private function loadServices(DrydockBlueprint $blueprint) {
if (!$this->services) {
$service_phids = $blueprint->getFieldValue('almanacServicePHIDs');
if (!$service_phids) {
throw new Exception(
pht(
'This blueprint ("%s") does not define any Almanac Service PHIDs.',
$blueprint->getBlueprintName()));
}
$viewer = PhabricatorUser::getOmnipotentUser();
$services = id(new AlmanacServiceQuery())
->setViewer($viewer)
->withPHIDs($service_phids)
->withServiceClasses($this->getAlmanacServiceClasses())
->needBindings(true)
->execute();
$services = mpull($services, null, 'getPHID');
if (count($services) != count($service_phids)) {
$missing_phids = array_diff($service_phids, array_keys($services));
throw new Exception(
pht(
'Some of the Almanac Services defined by this blueprint '.
'could not be loaded. They may be invalid, no longer exist, '.
'or be of the wrong type: %s.',
implode(', ', $missing_phids)));
}
$this->services = $services;
}
return $this->services;
}
private function loadAllBindings(array $services) {
assert_instances_of($services, 'AlmanacService');
$bindings = array_mergev(mpull($services, 'getBindings'));
return mpull($bindings, null, 'getPHID');
}
private function loadFreeBindings(DrydockBlueprint $blueprint) {
if ($this->freeBindings === null) {
$viewer = PhabricatorUser::getOmnipotentUser();
$pool = id(new DrydockResourceQuery())
->setViewer($viewer)
->withBlueprintPHIDs(array($blueprint->getPHID()))
->withStatuses(
array(
DrydockResourceStatus::STATUS_PENDING,
DrydockResourceStatus::STATUS_ACTIVE,
DrydockResourceStatus::STATUS_RELEASED,
))
->execute();
$allocated_phids = array();
foreach ($pool as $resource) {
$allocated_phids[] = $resource->getAttribute('almanacDevicePHID');
}
$allocated_phids = array_fuse($allocated_phids);
$services = $this->loadServices($blueprint);
$bindings = $this->loadAllBindings($services);
$free = array();
foreach ($bindings as $binding) {
if (empty($allocated_phids[$binding->getPHID()])) {
$free[] = $binding;
}
}
$this->freeBindings = $free;
}
return $this->freeBindings;
}
private function getAlmanacServiceClasses() {
return array(
'AlmanacDrydockPoolServiceType',
);
}
}

View file

@ -1,328 +1,250 @@
<?php
/**
* @task lease Lease Acquisition
* @task resource Resource Allocation
* @task log Logging
* @task lease Lease Acquisition
* @task resource Resource Allocation
* @task interface Resource Interfaces
* @task log Logging
*/
abstract class DrydockBlueprintImplementation extends Phobject {
private $activeResource;
private $activeLease;
private $instance;
abstract public function getType();
abstract public function getInterface(
DrydockResource $resource,
DrydockLease $lease,
$type);
abstract public function isEnabled();
abstract public function getBlueprintName();
abstract public function getDescription();
public function getBlueprintClass() {
return get_class($this);
}
protected function loadLease($lease_id) {
// TODO: Get rid of this?
$query = id(new DrydockLeaseQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($lease_id))
->execute();
$lease = idx($query, $lease_id);
if (!$lease) {
throw new Exception(pht("No such lease '%d'!", $lease_id));
}
return $lease;
}
protected function getInstance() {
if (!$this->instance) {
throw new Exception(
pht('Attach the blueprint instance to the implementation.'));
}
return $this->instance;
}
public function attachInstance(DrydockBlueprint $instance) {
$this->instance = $instance;
return $this;
}
public function getFieldSpecifications() {
return array();
}
public function getDetail($key, $default = null) {
return $this->getInstance()->getDetail($key, $default);
}
/* -( Lease Acquisition )-------------------------------------------------- */
/**
* @task lease
*/
final public function filterResource(
DrydockResource $resource,
DrydockLease $lease) {
$scope = $this->pushActiveScope($resource, $lease);
return $this->canAllocateLease($resource, $lease);
}
/**
* Enforce basic checks on lease/resource compatibility. Allows resources to
* reject leases if they are incompatible, even if the resource types match.
*
* For example, if a resource represents a 32-bit host, this method might
* reject leases that need a 64-bit host. If a resource represents a working
* copy of repository "X", this method might reject leases which need a
* working copy of repository "Y". Generally, although the main types of
* a lease and resource may match (e.g., both "host"), it may not actually be
* possible to satisfy the lease with a specific resource.
* reject leases that need a 64-bit host. The blueprint might also reject
* a resource if the lease needs 8GB of RAM and the resource only has 6GB
* free.
*
* This method generally should not enforce limits or perform capacity
* checks. Perform those in @{method:shouldAllocateLease} instead. It also
* should not perform actual acquisition of the lease; perform that in
* @{method:executeAcquireLease} instead.
* This method should not acquire locks or expect anything to be locked. This
* is a coarse compatibility check between a lease and a resource.
*
* @param DrydockResource Candidiate resource to allocate the lease on.
* @param DrydockLease Pending lease that wants to allocate here.
* @return bool True if the resource and lease are compatible.
* @param DrydockBlueprint Concrete blueprint to allocate for.
* @param DrydockResource Candidiate resource to allocate the lease on.
* @param DrydockLease Pending lease that wants to allocate here.
* @return bool True if the resource and lease are compatible.
* @task lease
*/
abstract protected function canAllocateLease(
abstract public function canAcquireLeaseOnResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
/**
* @task lease
*/
final public function allocateLease(
DrydockResource $resource,
DrydockLease $lease) {
$scope = $this->pushActiveScope($resource, $lease);
$this->log(pht('Trying to Allocate Lease'));
$lease->setStatus(DrydockLeaseStatus::STATUS_ACQUIRING);
$lease->setResourceID($resource->getID());
$lease->attachResource($resource);
$ephemeral_lease = id(clone $lease)->makeEphemeral();
$allocated = false;
$allocation_exception = null;
$resource->openTransaction();
$resource->beginReadLocking();
$resource->reload();
// TODO: Policy stuff.
$other_leases = id(new DrydockLease())->loadAllWhere(
'status IN (%Ld) AND resourceID = %d',
array(
DrydockLeaseStatus::STATUS_ACQUIRING,
DrydockLeaseStatus::STATUS_ACTIVE,
),
$resource->getID());
try {
$allocated = $this->shouldAllocateLease(
$resource,
$ephemeral_lease,
$other_leases);
} catch (Exception $ex) {
$allocation_exception = $ex;
}
if ($allocated) {
$lease->save();
}
$resource->endReadLocking();
if ($allocated) {
$resource->saveTransaction();
$this->log(pht('Allocated Lease'));
} else {
$resource->killTransaction();
$this->log(pht('Failed to Allocate Lease'));
}
if ($allocation_exception) {
$this->logException($allocation_exception);
}
return $allocated;
}
/**
* Enforce lease limits on resources. Allows resources to reject leases if
* they would become over-allocated by accepting them.
*
* For example, if a resource represents disk space, this method might check
* how much space the lease is asking for (say, 200MB) and how much space is
* left unallocated on the resource. It could grant the lease (return true)
* if it has enough remaining space (more than 200MB), and reject the lease
* (return false) if it does not (less than 200MB).
*
* A resource might also allow only exclusive leases. In this case it could
* accept a new lease (return true) if there are no active leases, or reject
* the new lease (return false) if there any other leases.
*
* A lock is held on the resource while this method executes to prevent
* multiple processes from allocating leases on the resource simultaneously.
* However, this means you should implement the method as cheaply as possible.
* In particular, do not perform any actual acquisition or setup in this
* method.
*
* If allocation is permitted, the lease will be moved to `ACQUIRING` status
* and @{method:executeAcquireLease} will be called to actually perform
* acquisition.
*
* General compatibility checks unrelated to resource limits and capacity are
* better implemented in @{method:canAllocateLease}, which serves as a
* cheap filter before lock acquisition.
*
* @param DrydockResource Candidate resource to allocate the lease on.
* @param DrydockLease Pending lease that wants to allocate here.
* @param list<DrydockLease> Other allocated and acquired leases on the
* resource. The implementation can inspect them
* to verify it can safely add the new lease.
* @return bool True to allocate the lease on the resource;
* false to reject it.
* @task lease
*/
abstract protected function shouldAllocateLease(
DrydockResource $resource,
DrydockLease $lease,
array $other_leases);
/**
* @task lease
*/
final public function acquireLease(
DrydockResource $resource,
DrydockLease $lease) {
$scope = $this->pushActiveScope($resource, $lease);
$this->log(pht('Acquiring Lease'));
$lease->setStatus(DrydockLeaseStatus::STATUS_ACTIVE);
$lease->setResourceID($resource->getID());
$lease->attachResource($resource);
$ephemeral_lease = id(clone $lease)->makeEphemeral();
try {
$this->executeAcquireLease($resource, $ephemeral_lease);
} catch (Exception $ex) {
$this->logException($ex);
throw $ex;
}
$lease->setAttributes($ephemeral_lease->getAttributes());
$lease->save();
$this->log(pht('Acquired Lease'));
}
/**
* Acquire and activate an allocated lease. Allows resources to peform setup
* as leases are brought online.
*
* Following a successful call to @{method:canAllocateLease}, a lease is moved
* to `ACQUIRING` status and this method is called after resource locks are
* released. Nothing is locked while this method executes; the implementation
* is free to perform expensive operations like writing files and directories,
* executing commands, etc.
*
* After this method executes, the lease status is moved to `ACTIVE` and the
* original leasee may access it.
* Acquire a lease. Allows resources to peform setup as leases are brought
* online.
*
* If acquisition fails, throw an exception.
*
* @param DrydockResource Resource to acquire a lease on.
* @param DrydockLease Lease to acquire.
* @return void
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource to acquire a lease on.
* @param DrydockLease Requested lease.
* @return void
* @task lease
*/
abstract protected function executeAcquireLease(
abstract public function acquireLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
final public function releaseLease(
/**
* @return void
* @task lease
*/
public function activateLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
$scope = $this->pushActiveScope(null, $lease);
$released = false;
$lease->openTransaction();
$lease->beginReadLocking();
$lease->reload();
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) {
$lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
$lease->save();
$released = true;
}
$lease->endReadLocking();
$lease->saveTransaction();
if (!$released) {
throw new Exception(pht('Unable to release lease: lease not active!'));
}
throw new PhutilMethodNotImplementedException();
}
/**
* React to a lease being released.
*
* This callback is primarily useful for automatically releasing resources
* once all leases are released.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource a lease was released on.
* @param DrydockLease Recently released lease.
* @return void
* @task lease
*/
abstract public function didReleaseLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
/**
* Destroy any temporary data associated with a lease.
*
* If a lease creates temporary state while held, destroy it here.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource the lease is acquired on.
* @param DrydockLease The lease being destroyed.
* @return void
* @task lease
*/
abstract public function destroyLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease);
/* -( Resource Allocation )------------------------------------------------ */
public function canAllocateMoreResources(array $pool) {
return true;
/**
* Enforce fundamental implementation/lease checks. Allows implementations to
* reject a lease which no concrete blueprint can ever satisfy.
*
* For example, if a lease only builds ARM hosts and the lease needs a
* PowerPC host, it may be rejected here.
*
* This is the earliest rejection phase, and followed by
* @{method:canEverAllocateResourceForLease}.
*
* This method should not actually check if a resource can be allocated
* right now, or even if a blueprint which can allocate a suitable resource
* really exists, only if some blueprint may conceivably exist which could
* plausibly be able to build a suitable resource.
*
* @param DrydockLease Requested lease.
* @return bool True if some concrete blueprint of this implementation's
* type might ever be able to build a resource for the lease.
* @task resource
*/
abstract public function canAnyBlueprintEverAllocateResourceForLease(
DrydockLease $lease);
/**
* Enforce basic blueprint/lease checks. Allows blueprints to reject a lease
* which they can not build a resource for.
*
* This is the second rejection phase. It follows
* @{method:canAnyBlueprintEverAllocateResourceForLease} and is followed by
* @{method:canAllocateResourceForLease}.
*
* This method should not check if a resource can be built right now, only
* if the blueprint as configured may, at some time, be able to build a
* suitable resource.
*
* @param DrydockBlueprint Blueprint which may be asked to allocate a
* resource.
* @param DrydockLease Requested lease.
* @return bool True if this blueprint can eventually build a suitable
* resource for the lease, as currently configured.
* @task resource
*/
abstract public function canEverAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease);
/**
* Enforce basic availability limits. Allows blueprints to reject resource
* allocation if they are currently overallocated.
*
* This method should perform basic capacity/limit checks. For example, if
* it has a limit of 6 resources and currently has 6 resources allocated,
* it might reject new leases.
*
* This method should not acquire locks or expect locks to be acquired. This
* is a coarse check to determine if the operation is likely to succeed
* right now without needing to acquire locks.
*
* It is expected that this method will sometimes return `true` (indicating
* that a resource can be allocated) but find that another allocator has
* eaten up free capacity by the time it actually tries to build a resource.
* This is normal and the allocator will recover from it.
*
* @param DrydockBlueprint The blueprint which may be asked to allocate a
* resource.
* @param DrydockLease Requested lease.
* @return bool True if this blueprint appears likely to be able to allocate
* a suitable resource.
* @task resource
*/
abstract public function canAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease);
/**
* Allocate a suitable resource for a lease.
*
* This method MUST acquire, hold, and manage locks to prevent multiple
* allocations from racing. World state is not locked before this method is
* called. Blueprints are entirely responsible for any lock handling they
* need to perform.
*
* @param DrydockBlueprint The blueprint which should allocate a resource.
* @param DrydockLease Requested lease.
* @return DrydockResource Allocated resource.
* @task resource
*/
abstract public function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease);
/**
* @task resource
*/
public function activateResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
throw new PhutilMethodNotImplementedException();
}
abstract protected function executeAllocateResource(DrydockLease $lease);
/**
* Destroy any temporary data associated with a resource.
*
* If a resource creates temporary state when allocated, destroy that state
* here. For example, you might shut down a virtual host or destroy a working
* copy on disk.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource being destroyed.
* @return void
* @task resource
*/
abstract public function destroyResource(
DrydockBlueprint $blueprint,
DrydockResource $resource);
final public function allocateResource(DrydockLease $lease) {
$scope = $this->pushActiveScope(null, $lease);
/* -( Resource Interfaces )------------------------------------------------ */
$this->log(
pht(
"Blueprint '%s': Allocating Resource for '%s'",
$this->getBlueprintClass(),
$lease->getLeaseName()));
try {
$resource = $this->executeAllocateResource($lease);
$this->validateAllocatedResource($resource);
} catch (Exception $ex) {
$this->logException($ex);
throw $ex;
}
return $resource;
}
abstract public function getInterface(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease,
$type);
/* -( Logging )------------------------------------------------------------ */
@ -340,10 +262,7 @@ abstract class DrydockBlueprintImplementation extends Phobject {
* @task log
*/
protected function log($message) {
self::writeLog(
$this->activeResource,
$this->activeLease,
$message);
self::writeLog(null, null, $message);
}
@ -377,88 +296,44 @@ abstract class DrydockBlueprintImplementation extends Phobject {
->execute();
}
public static function getAllBlueprintImplementationsForResource($type) {
static $groups = null;
if ($groups === null) {
$groups = mgroup(self::getAllBlueprintImplementations(), 'getType');
}
return idx($groups, $type, array());
}
public static function getNamedImplementation($class) {
return idx(self::getAllBlueprintImplementations(), $class);
}
protected function newResourceTemplate($name) {
protected function newResourceTemplate(
DrydockBlueprint $blueprint,
$name) {
$resource = id(new DrydockResource())
->setBlueprintPHID($this->getInstance()->getPHID())
->setBlueprintClass($this->getBlueprintClass())
->setBlueprintPHID($blueprint->getPHID())
->attachBlueprint($blueprint)
->setType($this->getType())
->setStatus(DrydockResourceStatus::STATUS_PENDING)
->setName($name)
->save();
->setName($name);
$this->activeResource = $resource;
$this->log(
pht(
"Blueprint '%s': Created New Template",
$this->getBlueprintClass()));
// Pre-allocate the resource PHID.
$resource->setPHID($resource->generatePHID());
return $resource;
}
/**
* Sanity checks that the blueprint is implemented properly.
*/
private function validateAllocatedResource($resource) {
$blueprint = $this->getBlueprintClass();
if (!($resource instanceof DrydockResource)) {
throw new Exception(
pht(
"Blueprint '%s' is not properly implemented: %s must return an ".
"object of type %s or throw, but returned something else.",
$blueprint,
'executeAllocateResource()',
'DrydockResource'));
}
$current_status = $resource->getStatus();
$req_status = DrydockResourceStatus::STATUS_OPEN;
if ($current_status != $req_status) {
$current_name = DrydockResourceStatus::getNameForStatus($current_status);
$req_name = DrydockResourceStatus::getNameForStatus($req_status);
throw new Exception(
pht(
"Blueprint '%s' is not properly implemented: %s must return a %s ".
"with status '%s', but returned one with status '%s'.",
$blueprint,
'executeAllocateResource()',
'DrydockResource',
$req_name,
$current_name));
}
protected function newLease(DrydockBlueprint $blueprint) {
return id(new DrydockLease());
}
private function pushActiveScope(
DrydockResource $resource = null,
DrydockLease $lease = null) {
protected function requireActiveLease(DrydockLease $lease) {
$lease_status = $lease->getStatus();
if (($this->activeResource !== null) ||
($this->activeLease !== null)) {
throw new Exception(pht('There is already an active resource or lease!'));
switch ($lease_status) {
case DrydockLeaseStatus::STATUS_ACQUIRED:
// TODO: Temporary failure.
throw new Exception(pht('Lease still activating.'));
case DrydockLeaseStatus::STATUS_ACTIVE:
return;
default:
// TODO: Permanent failure.
throw new Exception(pht('Lease in bad state.'));
}
$this->activeResource = $resource;
$this->activeLease = $lease;
return new DrydockBlueprintScopeGuard($this);
}
public function popActiveScope() {
$this->activeResource = null;
$this->activeLease = null;
}
}

View file

@ -1,116 +0,0 @@
<?php
final class DrydockPreallocatedHostBlueprintImplementation
extends DrydockBlueprintImplementation {
public function isEnabled() {
return true;
}
public function getBlueprintName() {
return pht('Preallocated Remote Hosts');
}
public function getDescription() {
return pht('Allows Drydock to run on specific remote hosts you configure.');
}
public function canAllocateMoreResources(array $pool) {
return false;
}
protected function executeAllocateResource(DrydockLease $lease) {
throw new Exception(
pht("Preallocated hosts can't be dynamically allocated."));
}
protected function canAllocateLease(
DrydockResource $resource,
DrydockLease $lease) {
return
$lease->getAttribute('platform') === $resource->getAttribute('platform');
}
protected function shouldAllocateLease(
DrydockResource $resource,
DrydockLease $lease,
array $other_leases) {
return true;
}
protected function executeAcquireLease(
DrydockResource $resource,
DrydockLease $lease) {
// Because preallocated resources are manually created, we should verify
// we have all the information we need.
PhutilTypeSpec::checkMap(
$resource->getAttributesForTypeSpec(
array('platform', 'host', 'port', 'credential', 'path')),
array(
'platform' => 'string',
'host' => 'string',
'port' => 'string', // Value is a string from the command line
'credential' => 'string',
'path' => 'string',
));
$v_platform = $resource->getAttribute('platform');
$v_path = $resource->getAttribute('path');
// Similar to DrydockLocalHostBlueprint, we create a folder
// on the remote host that the lease can use.
$lease_id = $lease->getID();
// Can't use DIRECTORY_SEPERATOR here because that is relevant to
// the platform we're currently running on, not the platform we are
// remoting to.
$separator = '/';
if ($v_platform === 'windows') {
$separator = '\\';
}
// Clean up the directory path a little.
$base_path = rtrim($v_path, '/');
$base_path = rtrim($base_path, '\\');
$full_path = $base_path.$separator.$lease_id;
$cmd = $lease->getInterface('command');
$cmd->execx('mkdir %s', $full_path);
$lease->setAttribute('path', $full_path);
}
public function getType() {
return 'host';
}
public function getInterface(
DrydockResource $resource,
DrydockLease $lease,
$type) {
switch ($type) {
case 'command':
return id(new DrydockSSHCommandInterface())
->setConfiguration(array(
'host' => $resource->getAttribute('host'),
'port' => $resource->getAttribute('port'),
'credential' => $resource->getAttribute('credential'),
'platform' => $resource->getAttribute('platform'),
))
->setWorkingDirectory($lease->getAttribute('path'));
case 'filesystem':
return id(new DrydockSFTPFilesystemInterface())
->setConfiguration(array(
'host' => $resource->getAttribute('host'),
'port' => $resource->getAttribute('port'),
'credential' => $resource->getAttribute('credential'),
));
}
throw new Exception(pht("No interface of type '%s'.", $type));
}
}

View file

@ -15,86 +15,190 @@ final class DrydockWorkingCopyBlueprintImplementation
return pht('Allows Drydock to check out working copies of repositories.');
}
protected function canAllocateLease(
public function canAnyBlueprintEverAllocateResourceForLease(
DrydockLease $lease) {
return true;
}
public function canEverAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
return true;
}
public function canAllocateResourceForLease(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
return true;
}
public function canAcquireLeaseOnResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
$resource_repo = $resource->getAttribute('repositoryID');
$lease_repo = $lease->getAttribute('repositoryID');
$have_phid = $resource->getAttribute('repositoryPHID');
$need_phid = $lease->getAttribute('repositoryPHID');
return ($resource_repo && $lease_repo && ($resource_repo == $lease_repo));
if ($need_phid !== $have_phid) {
return false;
}
if (!DrydockSlotLock::isLockFree($this->getLeaseSlotLock($resource))) {
return false;
}
return true;
}
protected function shouldAllocateLease(
public function acquireLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease,
array $other_leases) {
DrydockLease $lease) {
return !$other_leases;
$lease
->needSlotLock($this->getLeaseSlotLock($resource))
->acquireOnResource($resource);
}
protected function executeAllocateResource(DrydockLease $lease) {
$repository_id = $lease->getAttribute('repositoryID');
if (!$repository_id) {
throw new Exception(
pht(
"Lease is missing required '%s' attribute.",
'repositoryID'));
}
private function getLeaseSlotLock(DrydockResource $resource) {
$resource_phid = $resource->getPHID();
return "workingcopy.lease({$resource_phid})";
}
$repository = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($repository_id))
->executeOne();
public function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
if (!$repository) {
throw new Exception(
pht(
"Repository '%s' does not exist!",
$repository_id));
}
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
default:
throw new Exception(pht('Unsupported VCS!'));
}
// TODO: Policy stuff here too.
$host_lease = id(new DrydockLease())
->setResourceType('host')
->waitUntilActive();
$path = $host_lease->getAttribute('path').$repository->getCallsign();
$this->log(
pht('Cloning %s into %s....', $repository->getCallsign(), $path));
$cmd = $host_lease->getInterface('command');
$cmd->execx(
'git clone --origin origin %P %s',
$repository->getRemoteURIEnvelope(),
$path);
$this->log(pht('Complete.'));
$repository_phid = $lease->getAttribute('repositoryPHID');
$repository = $this->loadRepository($repository_phid);
$resource = $this->newResourceTemplate(
$blueprint,
pht(
'Working Copy (%s)',
$repository->getCallsign()));
$resource->setStatus(DrydockResourceStatus::STATUS_OPEN);
$resource->setAttribute('lease.host', $host_lease->getID());
$resource->setAttribute('path', $path);
$resource->setAttribute('repositoryID', $repository->getID());
$resource->save();
return $resource;
$resource_phid = $resource->getPHID();
$host_lease = $this->newLease($blueprint)
->setResourceType('host')
->setOwnerPHID($resource_phid)
->setAttribute('workingcopy.resourcePHID', $resource_phid)
->queueForActivation();
// TODO: Add some limits to the number of working copies we can have at
// once?
return $resource
->setAttribute('repositoryPHID', $repository->getPHID())
->setAttribute('host.leasePHID', $host_lease->getPHID())
->allocateResource();
}
protected function executeAcquireLease(
public function activateResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
$lease = $this->loadHostLease($resource);
$this->requireActiveLease($lease);
$repository_phid = $resource->getAttribute('repositoryPHID');
$repository = $this->loadRepository($repository_phid);
$repository_id = $repository->getID();
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $lease->getInterface($command_type);
// TODO: Make this configurable.
$resource_id = $resource->getID();
$root = "/var/drydock/workingcopy-{$resource_id}";
$path = "{$root}/repo/{$repository_id}/";
$interface->execx(
'git clone -- %s %s',
(string)$repository->getCloneURIObject(),
$path);
$resource
->setAttribute('workingcopy.root', $root)
->setAttribute('workingcopy.path', $path)
->activateResource();
}
public function destroyResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
$lease = $this->loadHostLease($resource);
// Destroy the lease on the host.
$lease->releaseOnDestruction();
// Destroy the working copy on disk.
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $lease->getInterface($command_type);
$root_key = 'workingcopy.root';
$root = $resource->getAttribute($root_key);
if (strlen($root)) {
$interface->execx('rm -rf -- %s', $root);
}
}
public function activateLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $lease->getInterface($command_type);
$cmd = array();
$arg = array();
$cmd[] = 'git clean -d --force';
$cmd[] = 'git reset --hard HEAD';
$cmd[] = 'git fetch';
$commit = $lease->getAttribute('commit');
$branch = $lease->getAttribute('branch');
if ($commit !== null) {
$cmd[] = 'git reset --hard %s';
$arg[] = $commit;
} else if ($branch !== null) {
$cmd[] = 'git reset --hard %s';
$arg[] = $branch;
}
$cmd = implode(' && ', $cmd);
$argv = array_merge(array($cmd), $arg);
$result = call_user_func_array(
array($interface, 'execx'),
$argv);
$lease->activateOnResource($resource);
}
public function didReleaseLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// We leave working copies around even if there are no leases on them,
// since the cost to maintain them is nearly zero but rebuilding them is
// moderately expensive and it's likely that they'll be reused.
return;
}
public function destroyLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
// When we activate a lease we just reset the working copy state and do
// not create any new state, so we don't need to do anything special when
// destroying a lease.
return;
}
@ -103,18 +207,63 @@ final class DrydockWorkingCopyBlueprintImplementation
}
public function getInterface(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease,
$type) {
switch ($type) {
case 'command':
return $this
->loadLease($resource->getAttribute('lease.host'))
->getInterface($type);
}
case DrydockCommandInterface::INTERFACE_TYPE:
$host_lease = $this->loadHostLease($resource);
$command_interface = $host_lease->getInterface($type);
throw new Exception(pht("No interface of type '%s'.", $type));
$path = $resource->getAttribute('workingcopy.path');
$command_interface->setWorkingDirectory($path);
return $command_interface;
}
}
private function loadRepository($repository_phid) {
$repository = id(new PhabricatorRepositoryQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($repository_phid))
->executeOne();
if (!$repository) {
// TODO: Permanent failure.
throw new Exception(
pht(
'Repository PHID "%s" does not exist.',
$repository_phid));
}
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
default:
// TODO: Permanent failure.
throw new Exception(pht('Unsupported VCS!'));
}
return $repository;
}
private function loadHostLease(DrydockResource $resource) {
$viewer = PhabricatorUser::getOmnipotentUser();
$lease_phid = $resource->getAttribute('host.leasePHID');
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
// TODO: Permanent failure.
throw new Exception(pht('Unable to load lease "%s".', $lease_phid));
}
return $lease;
}
}

View file

@ -2,35 +2,31 @@
final class DrydockLeaseStatus extends DrydockConstants {
const STATUS_PENDING = 0;
const STATUS_ACQUIRING = 5;
const STATUS_ACTIVE = 1;
const STATUS_RELEASED = 2;
const STATUS_BROKEN = 3;
const STATUS_EXPIRED = 4;
const STATUS_PENDING = 'pending';
const STATUS_ACQUIRED = 'acquired';
const STATUS_ACTIVE = 'active';
const STATUS_RELEASED = 'released';
const STATUS_BROKEN = 'broken';
const STATUS_DESTROYED = 'destroyed';
public static function getStatusMap() {
return array(
self::STATUS_PENDING => pht('Pending'),
self::STATUS_ACQUIRED => pht('Acquired'),
self::STATUS_ACTIVE => pht('Active'),
self::STATUS_RELEASED => pht('Released'),
self::STATUS_BROKEN => pht('Broken'),
self::STATUS_DESTROYED => pht('Destroyed'),
);
}
public static function getNameForStatus($status) {
$map = array(
self::STATUS_PENDING => pht('Pending'),
self::STATUS_ACQUIRING => pht('Acquiring'),
self::STATUS_ACTIVE => pht('Active'),
self::STATUS_RELEASED => pht('Released'),
self::STATUS_BROKEN => pht('Broken'),
self::STATUS_EXPIRED => pht('Expired'),
);
$map = self::getStatusMap();
return idx($map, $status, pht('Unknown'));
}
public static function getAllStatuses() {
return array(
self::STATUS_PENDING,
self::STATUS_ACQUIRING,
self::STATUS_ACTIVE,
self::STATUS_RELEASED,
self::STATUS_BROKEN,
self::STATUS_EXPIRED,
);
return array_keys(self::getStatusMap());
}
}

View file

@ -2,32 +2,29 @@
final class DrydockResourceStatus extends DrydockConstants {
const STATUS_PENDING = 0;
const STATUS_OPEN = 1;
const STATUS_CLOSED = 2;
const STATUS_BROKEN = 3;
const STATUS_DESTROYED = 4;
const STATUS_PENDING = 'pending';
const STATUS_ACTIVE = 'active';
const STATUS_RELEASED = 'released';
const STATUS_BROKEN = 'broken';
const STATUS_DESTROYED = 'destroyed';
public static function getStatusMap() {
return array(
self::STATUS_PENDING => pht('Pending'),
self::STATUS_ACTIVE => pht('Active'),
self::STATUS_RELEASED => pht('Released'),
self::STATUS_BROKEN => pht('Broken'),
self::STATUS_DESTROYED => pht('Destroyed'),
);
}
public static function getNameForStatus($status) {
$map = array(
self::STATUS_PENDING => pht('Pending'),
self::STATUS_OPEN => pht('Open'),
self::STATUS_CLOSED => pht('Closed'),
self::STATUS_BROKEN => pht('Broken'),
self::STATUS_DESTROYED => pht('Destroyed'),
);
$map = self::getStatusMap();
return idx($map, $status, pht('Unknown'));
}
public static function getAllStatuses() {
return array(
self::STATUS_PENDING,
self::STATUS_OPEN,
self::STATUS_CLOSED,
self::STATUS_BROKEN,
self::STATUS_DESTROYED,
);
return array_keys(self::getStatusMap());
}
}

View file

@ -0,0 +1,64 @@
<?php
final class DrydockBlueprintDisableController
extends DrydockBlueprintController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$blueprint = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$blueprint) {
return new Aphront404Response();
}
$is_disable = ($request->getURIData('action') == 'disable');
$id = $blueprint->getID();
$cancel_uri = $this->getApplicationURI("blueprint/{$id}/");
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new DrydockBlueprintTransaction())
->setTransactionType(DrydockBlueprintTransaction::TYPE_DISABLED)
->setNewValue($is_disable ? 1 : 0);
$editor = id(new DrydockBlueprintEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($blueprint, $xactions);
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
}
if ($is_disable) {
$title = pht('Disable Blueprint');
$body = pht(
'If you disable this blueprint, Drydock will no longer use it to '.
'allocate new resources. Existing resources will not be affected.');
$button = pht('Disable Blueprint');
} else {
$title = pht('Enable Blueprint');
$body = pht(
'If you enable this blueprint, Drydock will start using it to '.
'allocate new resources.');
$button = pht('Enable Blueprint');
}
return $this->newDialog()
->setTitle($title)
->appendParagraph($body)
->addCancelButton($cancel_uri)
->addSubmitButton($button);
}
}

View file

@ -33,8 +33,10 @@ final class DrydockBlueprintEditController extends DrydockBlueprintController {
return new Aphront400Response();
}
$blueprint = DrydockBlueprint::initializeNewBlueprint($viewer);
$blueprint->setClassName($class);
$blueprint = DrydockBlueprint::initializeNewBlueprint($viewer)
->setClassName($class)
->attachImplementation($impl);
$cancel_uri = $this->getApplicationURI('blueprint/');
}

View file

@ -21,27 +21,15 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
->setUser($viewer)
->setPolicyObject($blueprint);
if ($blueprint->getIsDisabled()) {
$header->setStatus('fa-ban', 'red', pht('Disabled'));
} else {
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
}
$actions = $this->buildActionListView($blueprint);
$properties = $this->buildPropertyListView($blueprint, $actions);
$blueprint_uri = 'blueprint/'.$blueprint->getID().'/';
$blueprint_uri = $this->getApplicationURI($blueprint_uri);
$resources = id(new DrydockResourceQuery())
->withBlueprintPHIDs(array($blueprint->getPHID()))
->setViewer($viewer)
->execute();
$resource_list = id(new DrydockResourceListView())
->setUser($viewer)
->setResources($resources)
->render();
$resource_list->setNoDataString(pht('This blueprint has no resources.'));
$pager = new PHUIPagerView();
$pager->setURI(new PhutilURI($blueprint_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID()));
@ -61,6 +49,8 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
$viewer,
$properties);
$resource_box = $this->buildResourceBox($blueprint);
$timeline = $this->buildTransactionTimeline(
$blueprint,
new DrydockBlueprintTransactionQuery());
@ -70,7 +60,7 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
array(
$crumbs,
$object_box,
$resource_list,
$resource_box,
$timeline,
),
array(
@ -80,15 +70,15 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
}
private function buildActionListView(DrydockBlueprint $blueprint) {
$viewer = $this->getRequest()->getUser();
$viewer = $this->getViewer();
$id = $blueprint->getID();
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObjectURI($this->getRequest()->getRequestURI())
->setObject($blueprint);
$uri = '/blueprint/edit/'.$blueprint->getID().'/';
$uri = $this->getApplicationURI($uri);
$edit_uri = $this->getApplicationURI("blueprint/edit/{$id}/");
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
@ -97,12 +87,30 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
$view->addAction(
id(new PhabricatorActionView())
->setHref($uri)
->setHref($edit_uri)
->setName(pht('Edit Blueprint'))
->setIcon('fa-pencil')
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit));
if (!$blueprint->getIsDisabled()) {
$disable_name = pht('Disable Blueprint');
$disable_icon = 'fa-ban';
$disable_uri = $this->getApplicationURI("blueprint/{$id}/disable/");
} else {
$disable_name = pht('Enable Blueprint');
$disable_icon = 'fa-check';
$disable_uri = $this->getApplicationURI("blueprint/{$id}/enable/");
}
$view->addAction(
id(new PhabricatorActionView())
->setHref($disable_uri)
->setName($disable_name)
->setIcon($disable_icon)
->setWorkflow(true)
->setDisabled(!$can_edit));
return $view;
}
@ -120,4 +128,43 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
return $view;
}
private function buildResourceBox(DrydockBlueprint $blueprint) {
$viewer = $this->getViewer();
$resources = id(new DrydockResourceQuery())
->setViewer($viewer)
->withBlueprintPHIDs(array($blueprint->getPHID()))
->withStatuses(
array(
DrydockResourceStatus::STATUS_PENDING,
DrydockResourceStatus::STATUS_ACTIVE,
))
->setLimit(100)
->execute();
$resource_list = id(new DrydockResourceListView())
->setUser($viewer)
->setResources($resources)
->render()
->setNoDataString(pht('This blueprint has no active resources.'));
$id = $blueprint->getID();
$resources_uri = "blueprint/{$id}/resources/query/all/";
$resources_uri = $this->getApplicationURI($resources_uri);
$resource_header = id(new PHUIHeaderView())
->setHeader(pht('Active Resources'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setHref($resources_uri)
->setIconFont('fa-search')
->setText(pht('View All Resources')));
return id(new PHUIObjectBoxView())
->setHeader($resource_header)
->setObjectList($resource_list);
}
}

View file

@ -8,4 +8,81 @@ abstract class DrydockController extends PhabricatorController {
return $this->buildSideNavView()->getMenu();
}
protected function buildLocksTab($owner_phid) {
$locks = DrydockSlotLock::loadLocks($owner_phid);
$rows = array();
foreach ($locks as $lock) {
$rows[] = array(
$lock->getID(),
$lock->getLockKey(),
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(pht('No slot locks held.'))
->setHeaders(
array(
pht('ID'),
pht('Lock Key'),
))
->setColumnClasses(
array(
null,
'wide',
));
return id(new PHUIPropertyListView())
->addRawContent($table);
}
protected function buildCommandsTab($target_phid) {
$viewer = $this->getViewer();
$commands = id(new DrydockCommandQuery())
->setViewer($viewer)
->withTargetPHIDs(array($target_phid))
->execute();
$consumed_yes = id(new PHUIIconView())
->setIconFont('fa-check green');
$consumed_no = id(new PHUIIconView())
->setIconFont('fa-clock-o grey');
$rows = array();
foreach ($commands as $command) {
$rows[] = array(
$command->getID(),
$viewer->renderHandle($command->getAuthorPHID()),
$command->getCommand(),
($command->getIsConsumed()
? $consumed_yes
: $consumed_no),
phabricator_datetime($command->getDateCreated(), $viewer),
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(pht('No commands issued.'))
->setHeaders(
array(
pht('ID'),
pht('From'),
pht('Command'),
null,
pht('Date'),
))
->setColumnClasses(
array(
null,
null,
'wide',
null,
null,
));
return id(new PHUIPropertyListView())
->addRawContent($table);
}
}

View file

@ -3,13 +3,29 @@
abstract class DrydockLeaseController
extends DrydockController {
private $resource;
public function setResource($resource) {
$this->resource = $resource;
return $this;
}
public function getResource() {
return $this->resource;
}
public function buildSideNavView() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new DrydockLeaseSearchEngine())
->setViewer($this->getRequest()->getUser())
->addNavigationItems($nav->getMenu());
$engine = id(new DrydockLeaseSearchEngine())
->setViewer($this->getRequest()->getUser());
if ($this->getResource()) {
$engine->setResource($this->getResource());
}
$engine->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
@ -18,9 +34,28 @@ abstract class DrydockLeaseController
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Leases'),
$this->getApplicationURI('lease/'));
$resource = $this->getResource();
if ($resource) {
$id = $resource->getID();
$crumbs->addTextCrumb(
pht('Resources'),
$this->getApplicationURI('resource/'));
$crumbs->addTextCrumb(
$resource->getName(),
$this->getApplicationURI("resource/{$id}/"));
$crumbs->addTextCrumb(
pht('Leases'),
$this->getApplicationURI("resource/{$id}/leases/"));
} else {
$crumbs->addTextCrumb(
pht('Leases'),
$this->getApplicationURI('lease/'));
}
return $crumbs;
}

View file

@ -8,11 +8,26 @@ final class DrydockLeaseListController extends DrydockLeaseController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$querykey = $request->getURIData('queryKey');
$query_key = $request->getURIData('queryKey');
$engine = new DrydockLeaseSearchEngine();
$id = $request->getURIData('id');
if ($id) {
$resource = id(new DrydockResourceQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$resource) {
return new Aphront404Response();
}
$this->setResource($resource);
$engine->setResource($resource);
}
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($querykey)
->setSearchEngine(new DrydockLeaseSearchEngine())
->setQueryKey($query_key)
->setSearchEngine($engine)
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);

View file

@ -9,6 +9,11 @@ final class DrydockLeaseReleaseController extends DrydockLeaseController {
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$lease) {
return new Aphront404Response();
@ -17,43 +22,35 @@ final class DrydockLeaseReleaseController extends DrydockLeaseController {
$lease_uri = '/lease/'.$lease->getID().'/';
$lease_uri = $this->getApplicationURI($lease_uri);
if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) {
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Lease Not Active'))
->appendChild(
phutil_tag(
'p',
array(),
pht('You can only release "active" leases.')))
if (!$lease->canRelease()) {
return $this->newDialog()
->setTitle(pht('Lease Not Releasable'))
->appendParagraph(
pht(
'Leases can not be released after they are destroyed.'))
->addCancelButton($lease_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if (!$request->isDialogFormPost()) {
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Really release lease?'))
->appendChild(
phutil_tag(
'p',
array(),
pht(
'Releasing a lease may cause trouble for the lease holder and '.
'trigger cleanup of the underlying resource. It can not be '.
'undone. Continue?')))
->addSubmitButton(pht('Release Lease'))
->addCancelButton($lease_uri);
if ($request->isFormPost()) {
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($lease->getPHID())
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
return id(new AphrontDialogResponse())->setDialog($dialog);
$lease->scheduleUpdate();
return id(new AphrontRedirectResponse())->setURI($lease_uri);
}
$resource = $lease->getResource();
$blueprint = $resource->getBlueprint();
$blueprint->releaseLease($resource, $lease);
return id(new AphrontReloadResponse())->setURI($lease_uri);
return $this->newDialog()
->setTitle(pht('Release Lease?'))
->appendParagraph(
pht(
'Forcefully releasing a lease may interfere with the operation '.
'of the lease holder and trigger destruction of the underlying '.
'resource. It can not be undone.'))
->addSubmitButton(pht('Release Lease'))
->addCancelButton($lease_uri);
}
}

View file

@ -42,31 +42,46 @@ final class DrydockLeaseViewController extends DrydockLeaseController {
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title, $lease_uri);
$locks = $this->buildLocksTab($lease->getPHID());
$commands = $this->buildCommandsTab($lease->getPHID());
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
->addPropertyList($properties, pht('Properties'))
->addPropertyList($locks, pht('Slot Locks'))
->addPropertyList($commands, pht('Commands'));
$log_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Lease Logs'))
->setTable($log_table);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$log_table,
$log_box,
),
array(
'title' => $title,
'title' => $title,
));
}
private function buildActionListView(DrydockLease $lease) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->setUser($viewer)
->setObjectURI($this->getRequest()->getRequestURI())
->setObject($lease);
$id = $lease->getID();
$can_release = ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE);
$can_release = $lease->canRelease();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$lease,
PhabricatorPolicyCapability::CAN_EDIT);
$view->addAction(
id(new PhabricatorActionView())
@ -74,7 +89,7 @@ final class DrydockLeaseViewController extends DrydockLeaseController {
->setIcon('fa-times')
->setHref($this->getApplicationURI("/lease/{$id}/release/"))
->setWorkflow(true)
->setDisabled(!$can_release));
->setDisabled(!$can_release || !$can_edit));
return $view;
}
@ -82,46 +97,39 @@ final class DrydockLeaseViewController extends DrydockLeaseController {
private function buildPropertyListView(
DrydockLease $lease,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = new PHUIPropertyListView();
$view->setActionList($actions);
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_ACTIVE:
$status = pht('Active');
break;
case DrydockLeaseStatus::STATUS_RELEASED:
$status = pht('Released');
break;
case DrydockLeaseStatus::STATUS_EXPIRED:
$status = pht('Expired');
break;
case DrydockLeaseStatus::STATUS_PENDING:
$status = pht('Pending');
break;
case DrydockLeaseStatus::STATUS_BROKEN:
$status = pht('Broken');
break;
default:
$status = pht('Unknown');
break;
}
$view->addProperty(
pht('Status'),
$status);
DrydockLeaseStatus::getNameForStatus($lease->getStatus()));
$view->addProperty(
pht('Resource Type'),
$lease->getResourceType());
$view->addProperty(
pht('Resource'),
$lease->getResourceID());
$resource_phid = $lease->getResourcePHID();
if ($resource_phid) {
$resource_display = $viewer->renderHandle($resource_phid);
} else {
$resource_display = phutil_tag('em', array(), pht('No Resource'));
}
$view->addProperty(pht('Resource'), $resource_display);
$until = $lease->getUntil();
if ($until) {
$until_display = phabricator_datetime($until, $viewer);
} else {
$until_display = phutil_tag('em', array(), pht('Never'));
}
$view->addProperty(pht('Expires'), $until_display);
$attributes = $lease->getAttributes();
if ($attributes) {
$view->addSectionHeader(pht('Attributes'));
$view->addSectionHeader(
pht('Attributes'), 'fa-list-ul');
foreach ($attributes as $key => $value) {
$view->addProperty($key, $value);
}

View file

@ -1,49 +0,0 @@
<?php
final class DrydockResourceCloseController extends DrydockResourceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$resource = id(new DrydockResourceQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$resource) {
return new Aphront404Response();
}
$resource_uri = '/resource/'.$resource->getID().'/';
$resource_uri = $this->getApplicationURI($resource_uri);
if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) {
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Resource Not Open'))
->appendChild(phutil_tag('p', array(), pht(
'You can only close "open" resources.')))
->addCancelButton($resource_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if ($request->isFormPost()) {
$resource->closeResource();
return id(new AphrontReloadResponse())->setURI($resource_uri);
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Really close resource?'))
->appendChild(
pht(
'Closing a resource releases all leases and destroys the '.
'resource. It can not be undone. Continue?'))
->addSubmitButton(pht('Close Resource'))
->addCancelButton($resource_uri);
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -3,13 +3,29 @@
abstract class DrydockResourceController
extends DrydockController {
private $blueprint;
public function setBlueprint($blueprint) {
$this->blueprint = $blueprint;
return $this;
}
public function getBlueprint() {
return $this->blueprint;
}
public function buildSideNavView() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new DrydockResourceSearchEngine())
->setViewer($this->getRequest()->getUser())
->addNavigationItems($nav->getMenu());
$engine = id(new DrydockResourceSearchEngine())
->setViewer($this->getViewer());
if ($this->getBlueprint()) {
$engine->setBlueprint($this->getBlueprint());
}
$engine->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
@ -18,9 +34,26 @@ abstract class DrydockResourceController
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Resources'),
$this->getApplicationURI('resource/'));
$blueprint = $this->getBlueprint();
if ($blueprint) {
$id = $blueprint->getID();
$crumbs->addTextCrumb(
pht('Blueprints'),
$this->getApplicationURI('blueprint/'));
$crumbs->addTextCrumb(
$blueprint->getBlueprintName(),
$this->getApplicationURI("blueprint/{$id}/"));
$crumbs->addTextCrumb(
pht('Resources'),
$this->getApplicationURI("blueprint/{$id}/resources/"));
} else {
$crumbs->addTextCrumb(
pht('Resources'),
$this->getApplicationURI('resource/'));
}
return $crumbs;
}

View file

@ -7,12 +7,28 @@ final class DrydockResourceListController extends DrydockResourceController {
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$viewer = $this->getViewer();
$engine = new DrydockResourceSearchEngine();
$id = $request->getURIData('id');
if ($id) {
$blueprint = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$blueprint) {
return new Aphront404Response();
}
$this->setBlueprint($blueprint);
$engine->setBlueprint($blueprint);
}
$querykey = $request->getURIData('queryKey');
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($querykey)
->setSearchEngine(new DrydockResourceSearchEngine())
->setSearchEngine($engine)
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);

View file

@ -0,0 +1,56 @@
<?php
final class DrydockResourceReleaseController extends DrydockResourceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$resource = id(new DrydockResourceQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$resource) {
return new Aphront404Response();
}
$resource_uri = '/resource/'.$resource->getID().'/';
$resource_uri = $this->getApplicationURI($resource_uri);
if (!$resource->canRelease()) {
return $this->newDialog()
->setTitle(pht('Resource Not Releasable'))
->appendParagraph(
pht(
'Resources can not be released after they are destroyed.'))
->addCancelButton($resource_uri);
}
if ($request->isFormPost()) {
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($resource->getPHID())
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$resource->scheduleUpdate();
return id(new AphrontRedirectResponse())->setURI($resource_uri);
}
return $this->newDialog()
->setTitle(pht('Really release resource?'))
->appendChild(
pht(
'Releasing a resource releases all leases and destroys the '.
'resource. It can not be undone.'))
->addSubmitButton(pht('Release Resource'))
->addCancelButton($resource_uri);
}
}

View file

@ -17,6 +17,8 @@ final class DrydockResourceViewController extends DrydockResourceController {
$title = pht('Resource %s %s', $resource->getID(), $resource->getName());
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setPolicyObject($resource)
->setHeader($title);
$actions = $this->buildActionListView($resource);
@ -25,17 +27,6 @@ final class DrydockResourceViewController extends DrydockResourceController {
$resource_uri = 'resource/'.$resource->getID().'/';
$resource_uri = $this->getApplicationURI($resource_uri);
$leases = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withResourceIDs(array($resource->getID()))
->execute();
$lease_list = id(new DrydockLeaseListView())
->setUser($viewer)
->setLeases($leases)
->render();
$lease_list->setNoDataString(pht('This resource has no leases.'));
$pager = new PHUIPagerView();
$pager->setURI(new PhutilURI($resource_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
@ -54,16 +45,27 @@ final class DrydockResourceViewController extends DrydockResourceController {
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Resource %d', $resource->getID()));
$locks = $this->buildLocksTab($resource->getPHID());
$commands = $this->buildCommandsTab($resource->getPHID());
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
->addPropertyList($properties, pht('Properties'))
->addPropertyList($locks, pht('Slot Locks'))
->addPropertyList($commands, pht('Commands'));
$lease_box = $this->buildLeaseBox($resource);
$log_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Resource Logs'))
->setTable($log_table);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$lease_list,
$log_table,
$lease_box,
$log_box,
),
array(
'title' => $title,
@ -72,22 +74,29 @@ final class DrydockResourceViewController extends DrydockResourceController {
}
private function buildActionListView(DrydockResource $resource) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
->setUser($viewer)
->setObjectURI($this->getRequest()->getRequestURI())
->setObject($resource);
$can_close = ($resource->getStatus() == DrydockResourceStatus::STATUS_OPEN);
$uri = '/resource/'.$resource->getID().'/close/';
$can_release = $resource->canRelease();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$resource,
PhabricatorPolicyCapability::CAN_EDIT);
$uri = '/resource/'.$resource->getID().'/release/';
$uri = $this->getApplicationURI($uri);
$view->addAction(
id(new PhabricatorActionView())
->setHref($uri)
->setName(pht('Close Resource'))
->setName(pht('Release Resource'))
->setIcon('fa-times')
->setWorkflow(true)
->setDisabled(!$can_close));
->setDisabled(!$can_release || !$can_edit));
return $view;
}
@ -95,9 +104,10 @@ final class DrydockResourceViewController extends DrydockResourceController {
private function buildPropertyListView(
DrydockResource $resource,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = new PHUIPropertyListView();
$view->setActionList($actions);
$view = id(new PHUIPropertyListView())
->setActionList($actions);
$status = $resource->getStatus();
$status = DrydockResourceStatus::getNameForStatus($status);
@ -110,14 +120,14 @@ final class DrydockResourceViewController extends DrydockResourceController {
pht('Resource Type'),
$resource->getType());
// TODO: Load handle.
$view->addProperty(
pht('Blueprint'),
$resource->getBlueprintPHID());
$viewer->renderHandle($resource->getBlueprintPHID()));
$attributes = $resource->getAttributes();
if ($attributes) {
$view->addSectionHeader(pht('Attributes'));
$view->addSectionHeader(
pht('Attributes'), 'fa-list-ul');
foreach ($attributes as $key => $value) {
$view->addProperty($key, $value);
}
@ -126,4 +136,43 @@ final class DrydockResourceViewController extends DrydockResourceController {
return $view;
}
private function buildLeaseBox(DrydockResource $resource) {
$viewer = $this->getViewer();
$leases = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withResourcePHIDs(array($resource->getPHID()))
->withStatuses(
array(
DrydockLeaseStatus::STATUS_PENDING,
DrydockLeaseStatus::STATUS_ACQUIRED,
DrydockLeaseStatus::STATUS_ACTIVE,
))
->setLimit(100)
->execute();
$id = $resource->getID();
$leases_uri = "resource/{$id}/leases/query/all/";
$leases_uri = $this->getApplicationURI($leases_uri);
$lease_header = id(new PHUIHeaderView())
->setHeader(pht('Active Leases'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setHref($leases_uri)
->setIconFont('fa-search')
->setText(pht('View All Leases')));
$lease_list = id(new DrydockLeaseListView())
->setUser($viewer)
->setLeases($leases)
->render()
->setNoDataString(pht('This resource has no active leases.'));
return id(new PHUIObjectBoxView())
->setHeader($lease_header)
->setObjectList($lease_list);
}
}

View file

@ -9,6 +9,12 @@ final class DrydockBlueprintCoreCustomField
}
public function createFields($object) {
// If this is a generic object without an attached implementation (for
// example, via ApplicationSearch), just don't build any custom fields.
if (!$object->hasImplementation()) {
return array();
}
$impl = $object->getImplementation();
$specs = $impl->getFieldSpecifications();
@ -40,4 +46,8 @@ final class DrydockBlueprintCoreCustomField
return;
}
public function getBlueprintFieldValue() {
return $this->getProxy()->getFieldValue();
}
}

View file

@ -1,4 +1,8 @@
<?php
abstract class DrydockBlueprintCustomField
extends PhabricatorCustomField {}
extends PhabricatorCustomField {
abstract public function getBlueprintFieldValue();
}

View file

@ -16,7 +16,9 @@ final class DrydockBlueprintEditor
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
$types[] = DrydockBlueprintTransaction::TYPE_NAME;
$types[] = DrydockBlueprintTransaction::TYPE_DISABLED;
return $types;
}
@ -28,7 +30,11 @@ final class DrydockBlueprintEditor
switch ($xaction->getTransactionType()) {
case DrydockBlueprintTransaction::TYPE_NAME:
return $object->getBlueprintName();
case DrydockBlueprintTransaction::TYPE_DISABLED:
return (int)$object->getIsDisabled();
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
@ -38,7 +44,11 @@ final class DrydockBlueprintEditor
switch ($xaction->getTransactionType()) {
case DrydockBlueprintTransaction::TYPE_NAME:
return $xaction->getNewValue();
case DrydockBlueprintTransaction::TYPE_DISABLED:
return (int)$xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
@ -48,26 +58,26 @@ final class DrydockBlueprintEditor
switch ($xaction->getTransactionType()) {
case DrydockBlueprintTransaction::TYPE_NAME:
$object->setBlueprintName($xaction->getNewValue());
break;
return;
case DrydockBlueprintTransaction::TYPE_DISABLED:
$object->setIsDisabled((int)$xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return;
}
protected function extractFilePHIDsFromCustomTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
return array();
}
switch ($xaction->getTransactionType()) {
case DrydockBlueprintTransaction::TYPE_NAME:
case DrydockBlueprintTransaction::TYPE_DISABLED:
return;
}
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
return false;
return parent::applyCustomExternalTransaction($object, $xaction);
}
}

View file

@ -0,0 +1,25 @@
<?php
final class DrydockSlotLockException extends Exception {
private $lockMap;
public function __construct(array $locks) {
$this->lockMap = $locks;
if ($locks) {
$lock_list = array();
foreach ($locks as $lock => $owner_phid) {
$lock_list[] = pht('"%s" (owned by "%s")', $lock, $owner_phid);
}
$message = pht(
'Unable to acquire slot locks: %s.',
implode(', ', $lock_list));
} else {
$message = pht('Unable to acquire slot locks.');
}
parent::__construct($message);
}
}

View file

@ -2,12 +2,12 @@
abstract class DrydockInterface extends Phobject {
private $config;
private $config = array();
abstract public function getInterfaceType();
final public function setConfiguration(array $config) {
$this->config = $config;
final public function setConfig($key, $value) {
$this->config[$key] = $value;
return $this;
}

View file

@ -2,6 +2,8 @@
abstract class DrydockCommandInterface extends DrydockInterface {
const INTERFACE_TYPE = 'command';
private $workingDirectory;
public function setWorkingDirectory($working_directory) {
@ -14,7 +16,7 @@ abstract class DrydockCommandInterface extends DrydockInterface {
}
final public function getInterfaceType() {
return 'command';
return self::INTERFACE_TYPE;
}
final public function exec($command) {
@ -38,7 +40,7 @@ abstract class DrydockCommandInterface extends DrydockInterface {
protected function applyWorkingDirectoryToArgv(array $argv) {
if ($this->getWorkingDirectory() !== null) {
$cmd = $argv[0];
$cmd = "(cd %s; {$cmd})";
$cmd = "(cd %s && {$cmd})";
$argv = array_merge(
array($cmd),
array($this->getWorkingDirectory()),

View file

@ -1,12 +0,0 @@
<?php
final class DrydockLocalCommandInterface extends DrydockCommandInterface {
public function getExecFuture($command) {
$argv = func_get_args();
$argv = $this->applyWorkingDirectoryToArgv($argv);
return newv('ExecFuture', $argv);
}
}

View file

@ -2,35 +2,19 @@
final class DrydockSSHCommandInterface extends DrydockCommandInterface {
private $passphraseSSHKey;
private $credential;
private $connectTimeout;
private function openCredentialsIfNotOpen() {
if ($this->passphraseSSHKey !== null) {
return;
private function loadCredential() {
if ($this->credential === null) {
$credential_phid = $this->getConfig('credentialPHID');
$this->credential = PassphraseSSHKey::loadFromPHID(
$credential_phid,
PhabricatorUser::getOmnipotentUser());
}
$credential = id(new PassphraseCredentialQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($this->getConfig('credential')))
->needSecrets(true)
->executeOne();
if ($credential === null) {
throw new Exception(
pht(
'There is no credential with ID %d.',
$this->getConfig('credential')));
}
if ($credential->getProvidesType() !==
PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE) {
throw new Exception(pht('Only private key credentials are supported.'));
}
$this->passphraseSSHKey = PassphraseSSHKey::loadFromPHID(
$credential->getPHID(),
PhabricatorUser::getOmnipotentUser());
return $this->credential;
}
public function setConnectTimeout($timeout) {
@ -39,30 +23,36 @@ final class DrydockSSHCommandInterface extends DrydockCommandInterface {
}
public function getExecFuture($command) {
$this->openCredentialsIfNotOpen();
$credential = $this->loadCredential();
$argv = func_get_args();
$argv = $this->applyWorkingDirectoryToArgv($argv);
$full_command = call_user_func_array('csprintf', $argv);
$command_timeout = '';
if ($this->connectTimeout !== null) {
$command_timeout = csprintf(
'-o %s',
'ConnectTimeout='.$this->connectTimeout);
$flags = array();
$flags[] = '-o';
$flags[] = 'LogLevel=quiet';
$flags[] = '-o';
$flags[] = 'StrictHostKeyChecking=no';
$flags[] = '-o';
$flags[] = 'UserKnownHostsFile=/dev/null';
$flags[] = '-o';
$flags[] = 'BatchMode=yes';
if ($this->connectTimeout) {
$flags[] = '-o';
$flags[] = 'ConnectTimeout='.$this->connectTimeout;
}
return new ExecFuture(
'ssh '.
'-o LogLevel=quiet '.
'-o StrictHostKeyChecking=no '.
'-o UserKnownHostsFile=/dev/null '.
'-o BatchMode=yes '.
'%C -p %s -i %P %P@%s -- %s',
$command_timeout,
'ssh %Ls -l %P -p %s -i %P %s -- %s',
$flags,
$credential->getUsernameEnvelope(),
$this->getConfig('port'),
$this->passphraseSSHKey->getKeyfileEnvelope(),
$this->passphraseSSHKey->getUsernameEnvelope(),
$credential->getKeyfileEnvelope(),
$this->getConfig('host'),
$full_command);
}

View file

@ -1,49 +0,0 @@
<?php
final class DrydockManagementCloseWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('close')
->setSynopsis(pht('Close a resource.'))
->setArguments(
array(
array(
'name' => 'ids',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$ids = $args->getArg('ids');
if (!$ids) {
throw new PhutilArgumentUsageException(
pht('Specify one or more resource IDs to close.'));
}
$viewer = $this->getViewer();
$resources = id(new DrydockResourceQuery())
->setViewer($viewer)
->withIDs($ids)
->execute();
foreach ($ids as $id) {
$resource = idx($resources, $id);
if (!$resource) {
$console->writeErr("%s\n", pht('Resource %d does not exist!', $id));
} else if ($resource->getStatus() != DrydockResourceStatus::STATUS_OPEN) {
$console->writeErr("%s\n", pht("Resource %d is not 'open'!", $id));
} else {
$resource->closeResource();
$console->writeErr("%s\n", pht('Closed resource %d.', $id));
}
}
}
}

View file

@ -0,0 +1,66 @@
<?php
final class DrydockManagementCommandWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('command')
->setSynopsis(pht('Run a command on a leased resource.'))
->setArguments(
array(
array(
'name' => 'lease',
'param' => 'id',
'help' => pht('Lease ID.'),
),
array(
'name' => 'argv',
'wildcard' => true,
'help' => pht('Command to execute.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$lease_id = $args->getArg('lease');
if (!$lease_id) {
throw new PhutilArgumentUsageException(
pht(
'Use %s to specify a lease.',
'--lease'));
}
$argv = $args->getArg('argv');
if (!$argv) {
throw new PhutilArgumentUsageException(
pht(
'Specify a command to run.'));
}
$lease = id(new DrydockLeaseQuery())
->setViewer($this->getViewer())
->withIDs(array($lease_id))
->executeOne();
if (!$lease) {
throw new Exception(
pht(
'Unable to load lease with ID "%s"!',
$lease_id));
}
// TODO: Check lease state, etc.
$interface = $lease->getInterface(DrydockCommandInterface::INTERFACE_TYPE);
list($stdout, $stderr) = call_user_func_array(
array($interface, 'execx'),
array('%Ls', $argv));
fprintf(STDOUT, $stdout);
fprintf(STDERR, $stderr);
return 0;
}
}

View file

@ -1,81 +0,0 @@
<?php
final class DrydockManagementCreateResourceWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('create-resource')
->setSynopsis(pht('Create a resource manually.'))
->setArguments(
array(
array(
'name' => 'name',
'param' => 'resource_name',
'help' => pht('Resource name.'),
),
array(
'name' => 'blueprint',
'param' => 'blueprint_id',
'help' => pht('Blueprint ID.'),
),
array(
'name' => 'attributes',
'param' => 'name=value,...',
'help' => pht('Resource attributes.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$resource_name = $args->getArg('name');
if (!$resource_name) {
throw new PhutilArgumentUsageException(
pht(
'Specify a resource name with `%s`.',
'--name'));
}
$blueprint_id = $args->getArg('blueprint');
if (!$blueprint_id) {
throw new PhutilArgumentUsageException(
pht(
'Specify a blueprint ID with `%s`.',
'--blueprint'));
}
$attributes = $args->getArg('attributes');
if ($attributes) {
$options = new PhutilSimpleOptions();
$options->setCaseSensitive(true);
$attributes = $options->parse($attributes);
}
$viewer = $this->getViewer();
$blueprint = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withIDs(array($blueprint_id))
->executeOne();
if (!$blueprint) {
throw new PhutilArgumentUsageException(
pht('Specified blueprint does not exist.'));
}
$resource = id(new DrydockResource())
->setBlueprintPHID($blueprint->getPHID())
->setType($blueprint->getImplementation()->getType())
->setName($resource_name)
->setStatus(DrydockResourceStatus::STATUS_OPEN);
if ($attributes) {
$resource->setAttributes($attributes);
}
$resource->save();
$console->writeOut("%s\n", pht('Created Resource %s', $resource->getID()));
return 0;
}
}

View file

@ -14,6 +14,11 @@ final class DrydockManagementLeaseWorkflow
'param' => 'resource_type',
'help' => pht('Resource type.'),
),
array(
'name' => 'until',
'param' => 'time',
'help' => pht('Set lease expiration time.'),
),
array(
'name' => 'attributes',
'param' => 'name=value,...',
@ -33,6 +38,17 @@ final class DrydockManagementLeaseWorkflow
'--type'));
}
$until = $args->getArg('until');
if (strlen($until)) {
$until = strtotime($until);
if ($until <= 0) {
throw new PhutilArgumentUsageException(
pht(
'Unable to parse argument to "%s".',
'--until'));
}
}
$attributes = $args->getArg('attributes');
if ($attributes) {
$options = new PhutilSimpleOptions();
@ -40,18 +56,29 @@ final class DrydockManagementLeaseWorkflow
$attributes = $options->parse($attributes);
}
PhabricatorWorker::setRunAllTasksInProcess(true);
$lease = id(new DrydockLease())
->setResourceType($resource_type);
if ($attributes) {
$lease->setAttributes($attributes);
}
$lease
->queueForActivation()
->waitUntilActive();
$console->writeOut("%s\n", pht('Acquired Lease %s', $lease->getID()));
if ($until) {
$lease->setUntil($until);
}
$lease->queueForActivation();
echo tsprintf(
"%s\n",
pht('Waiting for daemons to activate lease...'));
$lease->waitUntilActive();
echo tsprintf(
"%s\n",
pht('Activated lease "%s".', $lease->getID()));
return 0;
}

View file

@ -0,0 +1,70 @@
<?php
final class DrydockManagementReleaseLeaseWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('release-lease')
->setSynopsis(pht('Release a lease.'))
->setArguments(
array(
array(
'name' => 'id',
'param' => 'id',
'repeat' => true,
'help' => pht('Lease ID to release.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$ids = $args->getArg('id');
if (!$ids) {
throw new PhutilArgumentUsageException(
pht(
'Specify one or more lease IDs to release with "%s".',
'--id'));
}
$viewer = $this->getViewer();
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
$leases = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withIDs($ids)
->execute();
PhabricatorWorker::setRunAllTasksInProcess(true);
foreach ($ids as $id) {
$lease = idx($leases, $id);
if (!$lease) {
echo tsprintf(
"%s\n",
pht('Lease "%s" does not exist.', $id));
continue;
}
if (!$lease->canRelease()) {
echo tsprintf(
"%s\n",
pht('Lease "%s" is not releasable.', $id));
continue;
}
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($lease->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$lease->scheduleUpdate();
echo tsprintf(
"%s\n",
pht('Scheduled release of lease "%s".', $id));
}
}
}

View file

@ -0,0 +1,71 @@
<?php
final class DrydockManagementReleaseResourceWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('release-resource')
->setSynopsis(pht('Release a resource.'))
->setArguments(
array(
array(
'name' => 'id',
'param' => 'id',
'repeat' => true,
'help' => pht('Resource ID to release.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$ids = $args->getArg('id');
if (!$ids) {
throw new PhutilArgumentUsageException(
pht(
'Specify one or more resource IDs to release with "%s".',
'--id'));
}
$viewer = $this->getViewer();
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
$resources = id(new DrydockResourceQuery())
->setViewer($viewer)
->withIDs($ids)
->execute();
PhabricatorWorker::setRunAllTasksInProcess(true);
foreach ($ids as $id) {
$resource = idx($resources, $id);
if (!$resource) {
echo tsprintf(
"%s\n",
pht('Resource "%s" does not exist.', $id));
continue;
}
if (!$resource->canRelease()) {
echo tsprintf(
"%s\n",
pht('Resource "%s" is not releasable.', $id));
continue;
}
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($resource->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$resource->scheduleUpdate();
echo tsprintf(
"%s\n",
pht('Scheduled release of resource "%s".', $id));
}
}
}

View file

@ -1,52 +0,0 @@
<?php
final class DrydockManagementReleaseWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('release')
->setSynopsis(pht('Release a lease.'))
->setArguments(
array(
array(
'name' => 'ids',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$ids = $args->getArg('ids');
if (!$ids) {
throw new PhutilArgumentUsageException(
pht('Specify one or more lease IDs to release.'));
}
$viewer = $this->getViewer();
$leases = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withIDs($ids)
->execute();
foreach ($ids as $id) {
$lease = idx($leases, $id);
if (!$lease) {
$console->writeErr("%s\n", pht('Lease %d does not exist!', $id));
} else if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) {
$console->writeErr("%s\n", pht("Lease %d is not 'active'!", $id));
} else {
$resource = $lease->getResource();
$blueprint = $resource->getBlueprint();
$blueprint->releaseLease($resource, $lease);
$console->writeErr("%s\n", pht('Released lease %d.', $id));
}
}
}
}

View file

@ -0,0 +1,57 @@
<?php
final class DrydockManagementUpdateLeaseWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('update-lease')
->setSynopsis(pht('Update a lease.'))
->setArguments(
array(
array(
'name' => 'id',
'param' => 'id',
'repeat' => true,
'help' => pht('Lease ID to update.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$ids = $args->getArg('id');
if (!$ids) {
throw new PhutilArgumentUsageException(
pht(
'Specify one or more lease IDs to update with "%s".',
'--id'));
}
$leases = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withIDs($ids)
->execute();
PhabricatorWorker::setRunAllTasksInProcess(true);
foreach ($ids as $id) {
$lease = idx($leases, $id);
if (!$lease) {
echo tsprintf(
"%s\n",
pht('Lease "%s" does not exist.', $id));
continue;
}
echo tsprintf(
"%s\n",
pht('Updating lease "%s".', $id));
$lease->scheduleUpdate();
}
}
}

View file

@ -0,0 +1,58 @@
<?php
final class DrydockManagementUpdateResourceWorkflow
extends DrydockManagementWorkflow {
protected function didConstruct() {
$this
->setName('update-resource')
->setSynopsis(pht('Update a resource.'))
->setArguments(
array(
array(
'name' => 'id',
'param' => 'id',
'repeat' => true,
'help' => pht('Resource ID to update.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$ids = $args->getArg('id');
if (!$ids) {
throw new PhutilArgumentUsageException(
pht(
'Specify one or more resource IDs to update with "%s".',
'--id'));
}
$resources = id(new DrydockResourceQuery())
->setViewer($viewer)
->withIDs($ids)
->execute();
PhabricatorWorker::setRunAllTasksInProcess(true);
foreach ($ids as $id) {
$resource = idx($resources, $id);
if (!$resource) {
echo tsprintf(
"%s\n",
pht('Resource "%s" does not exist.', $id));
continue;
}
echo tsprintf(
"%s\n",
pht('Updating resource "%s".', $id));
$resource->scheduleUpdate();
}
}
}

View file

@ -29,6 +29,7 @@ final class DrydockBlueprintPHIDType extends PhabricatorPHIDType {
$blueprint = $objects[$phid];
$id = $blueprint->getID();
$handle->setName($blueprint->getBlueprintName());
$handle->setURI("/drydock/blueprint/{$id}/");
}
}

View file

@ -29,7 +29,12 @@ final class DrydockResourcePHIDType extends PhabricatorPHIDType {
$resource = $objects[$phid];
$id = $resource->getID();
$handle->setName($resource->getName());
$handle->setName(
pht(
'Resource %d: %s',
$id,
$resource->getName()));
$handle->setURI("/drydock/resource/{$id}/");
}
}

View file

@ -4,7 +4,9 @@ final class DrydockBlueprintQuery extends DrydockQuery {
private $ids;
private $phids;
private $blueprintClasses;
private $datasourceQuery;
private $disabled;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -16,63 +18,84 @@ final class DrydockBlueprintQuery extends DrydockQuery {
return $this;
}
public function withBlueprintClasses(array $classes) {
$this->blueprintClasses = $classes;
return $this;
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
public function withDisabled($disabled) {
$this->disabled = $disabled;
return $this;
}
public function newResultObject() {
return new DrydockBlueprint();
}
protected function loadPage() {
$table = new DrydockBlueprint();
$conn_r = $table->establishConnection('r');
return $this->loadStandardPage($this->newResultObject());
}
$data = queryfx_all(
$conn_r,
'SELECT blueprint.* FROM %T blueprint %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
$blueprints = $table->loadAllFromArray($data);
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
foreach ($blueprints as $blueprint) {
if (array_key_exists($blueprint->getClassName(), $implementations)) {
$blueprint->attachImplementation(
$implementations[$blueprint->getClassName()]);
protected function willFilterPage(array $blueprints) {
$impls = DrydockBlueprintImplementation::getAllBlueprintImplementations();
foreach ($blueprints as $key => $blueprint) {
$impl = idx($impls, $blueprint->getClassName());
if (!$impl) {
$this->didRejectResult($blueprint);
unset($blueprints[$key]);
continue;
}
$impl = clone $impl;
$blueprint->attachImplementation($impl);
}
return $blueprints;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->datasourceQuery !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'blueprintName LIKE %>',
$this->datasourceQuery);
}
return $this->formatWhereClause($where);
if ($this->blueprintClasses !== null) {
$where[] = qsprintf(
$conn,
'className IN (%Ls)',
$this->blueprintClasses);
}
if ($this->disabled !== null) {
$where[] = qsprintf(
$conn,
'isDisabled = %d',
(int)$this->disabled);
}
return $where;
}
}

View file

@ -11,17 +11,31 @@ final class DrydockBlueprintSearchEngine
return 'PhabricatorDrydockApplication';
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
return new PhabricatorSavedQuery();
public function newQuery() {
return id(new DrydockBlueprintQuery());
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
return new DrydockBlueprintQuery();
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['isDisabled'] !== null) {
$query->withDisabled($map['isDisabled']);
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Disabled'))
->setKey('isDisabled')
->setOptions(
pht('(Show All)'),
pht('Show Only Disabled Blueprints'),
pht('Hide Disabled Blueprints')),
);
}
protected function getURI($path) {
return '/drydock/blueprint/'.$path;
@ -29,6 +43,7 @@ final class DrydockBlueprintSearchEngine
protected function getBuiltinQueryNames() {
return array(
'active' => pht('Active Blueprints'),
'all' => pht('All Blueprints'),
);
}
@ -38,6 +53,8 @@ final class DrydockBlueprintSearchEngine
$query->setQueryKey($query_key);
switch ($query_key) {
case 'active':
return $query->setParameter('isDisabled', false);
case 'all':
return $query;
}
@ -62,6 +79,12 @@ final class DrydockBlueprintSearchEngine
if (!$blueprint->getImplementation()->isEnabled()) {
$item->setDisabled(true);
$item->addIcon('fa-chain-broken grey', pht('Implementation'));
}
if ($blueprint->getIsDisabled()) {
$item->setDisabled(true);
$item->addIcon('fa-ban grey', pht('Disabled'));
}
$item->addAttribute($blueprint->getImplementation()->getBlueprintName());

View file

@ -0,0 +1,82 @@
<?php
final class DrydockCommandQuery extends DrydockQuery {
private $ids;
private $targetPHIDs;
private $consumed;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withTargetPHIDs(array $phids) {
$this->targetPHIDs = $phids;
return $this;
}
public function withConsumed($consumed) {
$this->consumed = $consumed;
return $this;
}
public function newResultObject() {
return new DrydockCommand();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $commands) {
$target_phids = mpull($commands, 'getTargetPHID');
$targets = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs($target_phids)
->execute();
$targets = mpull($targets, null, 'getPHID');
foreach ($commands as $key => $command) {
$target = idx($targets, $command->getTargetPHID());
if (!$target) {
$this->didRejectResult($command);
unset($commands[$key]);
continue;
}
$command->attachCommandTarget($target);
}
return $commands;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->targetPHIDs !== null) {
$where[] = qsprintf(
$conn,
'targetPHID IN (%Ls)',
$this->targetPHIDs);
}
if ($this->consumed !== null) {
$where[] = qsprintf(
$conn,
'isConsumed = %d',
(int)$this->consumed);
}
return $where;
}
}

View file

@ -4,9 +4,10 @@ final class DrydockLeaseQuery extends DrydockQuery {
private $ids;
private $phids;
private $resourceIDs;
private $resourcePHIDs;
private $statuses;
private $datasourceQuery;
private $needCommands;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -18,8 +19,8 @@ final class DrydockLeaseQuery extends DrydockQuery {
return $this;
}
public function withResourceIDs(array $ids) {
$this->resourceIDs = $ids;
public function withResourcePHIDs(array $phids) {
$this->resourcePHIDs = $phids;
return $this;
}
@ -28,36 +29,38 @@ final class DrydockLeaseQuery extends DrydockQuery {
return $this;
}
public function newResultObject() {
return new DrydockLease();
}
public function withDatasourceQuery($query) {
$this->datasourceQuery = $query;
return $this;
}
public function newResultObject() {
return new DrydockLease();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $leases) {
$resource_ids = array_filter(mpull($leases, 'getResourceID'));
if ($resource_ids) {
$resource_phids = array_filter(mpull($leases, 'getResourcePHID'));
if ($resource_phids) {
$resources = id(new DrydockResourceQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withIDs(array_unique($resource_ids))
->withPHIDs(array_unique($resource_phids))
->execute();
$resources = mpull($resources, null, 'getPHID');
} else {
$resources = array();
}
foreach ($leases as $key => $lease) {
$resource = null;
if ($lease->getResourceID()) {
$resource = idx($resources, $lease->getResourceID());
if ($lease->getResourcePHID()) {
$resource = idx($resources, $lease->getResourcePHID());
if (!$resource) {
$this->didRejectResult($lease);
unset($leases[$key]);
continue;
}
@ -71,11 +74,11 @@ final class DrydockLeaseQuery extends DrydockQuery {
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->resourceIDs !== null) {
if ($this->resourcePHIDs !== null) {
$where[] = qsprintf(
$conn,
'resourceID IN (%Ld)',
$this->resourceIDs);
'resourcePHID IN (%Ls)',
$this->resourcePHIDs);
}
if ($this->ids !== null) {
@ -95,7 +98,7 @@ final class DrydockLeaseQuery extends DrydockQuery {
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'status IN (%Ld)',
'status IN (%Ls)',
$this->statuses);
}

View file

@ -3,6 +3,17 @@
final class DrydockLeaseSearchEngine
extends PhabricatorApplicationSearchEngine {
private $resource;
public function setResource($resource) {
$this->resource = $resource;
return $this;
}
public function getResource() {
return $this->resource;
}
public function getResultTypeDescription() {
return pht('Drydock Leases');
}
@ -11,50 +22,44 @@ final class DrydockLeaseSearchEngine
return 'PhabricatorDrydockApplication';
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
public function newQuery() {
$query = new DrydockLeaseQuery();
$saved->setParameter(
'statuses',
$this->readListFromRequest($request, 'statuses'));
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new DrydockLeaseQuery());
$statuses = $saved->getParameter('statuses', array());
if ($statuses) {
$query->withStatuses($statuses);
$resource = $this->getResource();
if ($resource) {
$query->withResourcePHIDs(array($resource->getPHID()));
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
$statuses = $saved->getParameter('statuses', array());
$status_control = id(new AphrontFormCheckboxControl())
->setLabel(pht('Status'));
foreach (DrydockLeaseStatus::getAllStatuses() as $status) {
$status_control->addCheckbox(
'statuses[]',
$status,
DrydockLeaseStatus::getNameForStatus($status),
in_array($status, $statuses));
if ($map['statuses']) {
$query->withStatuses($map['statuses']);
}
$form
->appendChild($status_control);
return $query;
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchCheckboxesField())
->setLabel(pht('Statuses'))
->setKey('statuses')
->setOptions(DrydockLeaseStatus::getStatusMap()),
);
}
protected function getURI($path) {
return '/drydock/lease/'.$path;
$resource = $this->getResource();
if ($resource) {
$id = $resource->getID();
return "/drydock/resource/{$id}/leases/".$path;
} else {
return '/drydock/lease/'.$path;
}
}
protected function getBuiltinQueryNames() {
@ -74,7 +79,7 @@ final class DrydockLeaseSearchEngine
'statuses',
array(
DrydockLeaseStatus::STATUS_PENDING,
DrydockLeaseStatus::STATUS_ACQUIRING,
DrydockLeaseStatus::STATUS_ACQUIRED,
DrydockLeaseStatus::STATUS_ACTIVE,
));
case 'all':

View file

@ -39,71 +39,82 @@ final class DrydockResourceQuery extends DrydockQuery {
return $this;
}
public function newResultObject() {
return new DrydockResource();
}
protected function loadPage() {
$table = new DrydockResource();
$conn_r = $table->establishConnection('r');
return $this->loadStandardPage($this->newResultObject());
}
$data = queryfx_all(
$conn_r,
'SELECT resource.* FROM %T resource %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
protected function willFilterPage(array $resources) {
$blueprint_phids = mpull($resources, 'getBlueprintPHID');
$resources = $table->loadAllFromArray($data);
$blueprints = id(new DrydockBlueprintQuery())
->setViewer($this->getViewer())
->withPHIDs($blueprint_phids)
->execute();
$blueprints = mpull($blueprints, null, 'getPHID');
foreach ($resources as $key => $resource) {
$blueprint = idx($blueprints, $resource->getBlueprintPHID());
if (!$blueprint) {
$this->didRejectResult($resource);
unset($resources[$key]);
continue;
}
$resource->attachBlueprint($blueprint);
}
return $resources;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->types !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'type IN (%Ls)',
$this->types);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'status IN (%Ls)',
$this->statuses);
}
if ($this->blueprintPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'blueprintPHID IN (%Ls)',
$this->blueprintPHIDs);
}
if ($this->datasourceQuery !== null) {
$where[] = qsprintf(
$conn_r,
$conn,
'name LIKE %>',
$this->datasourceQuery);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
return $where;
}
}

View file

@ -3,6 +3,17 @@
final class DrydockResourceSearchEngine
extends PhabricatorApplicationSearchEngine {
private $blueprint;
public function setBlueprint(DrydockBlueprint $blueprint) {
$this->blueprint = $blueprint;
return $this;
}
public function getBlueprint() {
return $this->blueprint;
}
public function getResultTypeDescription() {
return pht('Drydock Resources');
}
@ -11,49 +22,44 @@ final class DrydockResourceSearchEngine
return 'PhabricatorDrydockApplication';
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
public function newQuery() {
$query = new DrydockResourceQuery();
$saved->setParameter(
'statuses',
$this->readListFromRequest($request, 'statuses'));
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new DrydockResourceQuery());
$statuses = $saved->getParameter('statuses', array());
if ($statuses) {
$query->withStatuses($statuses);
$blueprint = $this->getBlueprint();
if ($blueprint) {
$query->withBlueprintPHIDs(array($blueprint->getPHID()));
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
$statuses = $saved->getParameter('statuses', array());
$status_control = id(new AphrontFormCheckboxControl())
->setLabel(pht('Status'));
foreach (DrydockResourceStatus::getAllStatuses() as $status) {
$status_control->addCheckbox(
'statuses[]',
$status,
DrydockResourceStatus::getNameForStatus($status),
in_array($status, $statuses));
if ($map['statuses']) {
$query->withStatuses($map['statuses']);
}
$form
->appendChild($status_control);
return $query;
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchCheckboxesField())
->setLabel(pht('Statuses'))
->setKey('statuses')
->setOptions(DrydockResourceStatus::getStatusMap()),
);
}
protected function getURI($path) {
return '/drydock/resource/'.$path;
$blueprint = $this->getBlueprint();
if ($blueprint) {
$id = $blueprint->getID();
return "/drydock/blueprint/{$id}/resources/".$path;
} else {
return '/drydock/resource/'.$path;
}
}
protected function getBuiltinQueryNames() {
@ -73,7 +79,7 @@ final class DrydockResourceSearchEngine
'statuses',
array(
DrydockResourceStatus::STATUS_PENDING,
DrydockResourceStatus::STATUS_OPEN,
DrydockResourceStatus::STATUS_ACTIVE,
));
case 'all':
return $query;

View file

@ -1,5 +1,9 @@
<?php
/**
* @task resource Allocating Resources
* @task lease Acquiring Leases
*/
final class DrydockBlueprint extends DrydockDAO
implements
PhabricatorApplicationTransactionInterface,
@ -11,9 +15,11 @@ final class DrydockBlueprint extends DrydockDAO
protected $viewPolicy;
protected $editPolicy;
protected $details = array();
protected $isDisabled;
private $implementation = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
private $fields = null;
public static function initializeNewBlueprint(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
@ -29,7 +35,8 @@ final class DrydockBlueprint extends DrydockDAO
return id(new DrydockBlueprint())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setBlueprintName('');
->setBlueprintName('')
->setIsDisabled(0);
}
protected function getConfiguration() {
@ -41,6 +48,7 @@ final class DrydockBlueprint extends DrydockDAO
self::CONFIG_COLUMN_SCHEMA => array(
'className' => 'text255',
'blueprintName' => 'sort255',
'isDisabled' => 'bool',
),
) + parent::getConfiguration();
}
@ -51,16 +59,7 @@ final class DrydockBlueprint extends DrydockDAO
}
public function getImplementation() {
$class = $this->className;
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
if (!isset($implementations[$class])) {
throw new Exception(
pht(
"Invalid class name for blueprint (got '%s')",
$class));
}
return id(new $class())->attachInstance($this);
return $this->assertAttached($this->implementation);
}
public function attachImplementation(DrydockBlueprintImplementation $impl) {
@ -68,6 +67,10 @@ final class DrydockBlueprint extends DrydockDAO
return $this;
}
public function hasImplementation() {
return ($this->implementation !== self::ATTACHABLE);
}
public function getDetail($key, $default = null) {
return idx($this->details, $key, $default);
}
@ -77,6 +80,175 @@ final class DrydockBlueprint extends DrydockDAO
return $this;
}
public function getFieldValue($key) {
$key = "std:drydock:core:{$key}";
$fields = $this->loadCustomFields();
$field = idx($fields, $key);
if (!$field) {
throw new Exception(
pht(
'Unknown blueprint field "%s"!',
$key));
}
return $field->getBlueprintFieldValue();
}
private function loadCustomFields() {
if ($this->fields === null) {
$field_list = PhabricatorCustomField::getObjectFields(
$this,
PhabricatorCustomField::ROLE_VIEW);
$field_list->readFieldsFromStorage($this);
$this->fields = $field_list->getFields();
}
return $this->fields;
}
/* -( Allocating Resources )----------------------------------------------- */
/**
* @task resource
*/
public function canEverAllocateResourceForLease(DrydockLease $lease) {
return $this->getImplementation()->canEverAllocateResourceForLease(
$this,
$lease);
}
/**
* @task resource
*/
public function canAllocateResourceForLease(DrydockLease $lease) {
return $this->getImplementation()->canAllocateResourceForLease(
$this,
$lease);
}
/**
* @task resource
*/
public function allocateResource(DrydockLease $lease) {
return $this->getImplementation()->allocateResource(
$this,
$lease);
}
/**
* @task resource
*/
public function activateResource(DrydockResource $resource) {
return $this->getImplementation()->activateResource(
$this,
$resource);
}
/**
* @task resource
*/
public function destroyResource(DrydockResource $resource) {
$this->getImplementation()->destroyResource(
$this,
$resource);
return $this;
}
/* -( Acquiring Leases )--------------------------------------------------- */
/**
* @task lease
*/
public function canAcquireLeaseOnResource(
DrydockResource $resource,
DrydockLease $lease) {
return $this->getImplementation()->canAcquireLeaseOnResource(
$this,
$resource,
$lease);
}
/**
* @task lease
*/
public function acquireLease(
DrydockResource $resource,
DrydockLease $lease) {
return $this->getImplementation()->acquireLease(
$this,
$resource,
$lease);
}
/**
* @task lease
*/
public function activateLease(
DrydockResource $resource,
DrydockLease $lease) {
return $this->getImplementation()->activateLease(
$this,
$resource,
$lease);
}
/**
* @task lease
*/
public function didReleaseLease(
DrydockResource $resource,
DrydockLease $lease) {
$this->getImplementation()->didReleaseLease(
$this,
$resource,
$lease);
return $this;
}
/**
* @task lease
*/
public function destroyLease(
DrydockResource $resource,
DrydockLease $lease) {
$this->getImplementation()->destroyLease(
$this,
$resource,
$lease);
return $this;
}
public function getInterface(
DrydockResource $resource,
DrydockLease $lease,
$type) {
$interface = $this->getImplementation()
->getInterface($this, $resource, $lease, $type);
if (!$interface) {
throw new Exception(
pht(
'Unable to build resource interface of type "%s".',
$type));
}
return $interface;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */

View file

@ -3,7 +3,8 @@
final class DrydockBlueprintTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'drydock:blueprint:name';
const TYPE_NAME = 'drydock:blueprint:name';
const TYPE_DISABLED = 'drydock:blueprint:disabled';
public function getApplicationName() {
return 'drydock';
@ -31,6 +32,16 @@ final class DrydockBlueprintTransaction
$old,
$new);
}
case self::TYPE_DISABLED:
if ($new) {
return pht(
'%s disabled this blueprint.',
$author_handle);
} else {
return pht(
'%s enabled this blueprint.',
$author_handle);
}
}
return parent::getTitle();

View file

@ -0,0 +1,69 @@
<?php
final class DrydockCommand
extends DrydockDAO
implements PhabricatorPolicyInterface {
const COMMAND_RELEASE = 'release';
protected $authorPHID;
protected $targetPHID;
protected $command;
protected $isConsumed;
private $commandTarget = self::ATTACHABLE;
public static function initializeNewCommand(PhabricatorUser $author) {
return id(new DrydockCommand())
->setAuthorPHID($author->getPHID())
->setIsConsumed(0);
}
protected function getConfiguration() {
return array(
self::CONFIG_COLUMN_SCHEMA => array(
'command' => 'text32',
'isConsumed' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_target' => array(
'columns' => array('targetPHID', 'isConsumed'),
),
),
) + parent::getConfiguration();
}
public function attachCommandTarget($target) {
$this->commandTarget = $target;
return $this;
}
public function getCommandTarget() {
return $this->assertAttached($this->commandTarget);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return $this->getCommandTarget()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getCommandTarget()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht('Drydock commands have the same policies as their targets.');
}
}

View file

@ -3,16 +3,19 @@
final class DrydockLease extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $resourceID;
protected $resourcePHID;
protected $resourceType;
protected $until;
protected $ownerPHID;
protected $attributes = array();
protected $status = DrydockLeaseStatus::STATUS_PENDING;
protected $taskID;
private $resource = self::ATTACHABLE;
private $releaseOnDestruction;
private $isAcquired = false;
private $isActivated = false;
private $activateWhenAcquired = false;
private $slotLocks = array();
/**
* Flag this lease to be released when its destructor is called. This is
@ -26,11 +29,24 @@ final class DrydockLease extends DrydockDAO
}
public function __destruct() {
if ($this->releaseOnDestruction) {
if ($this->isActive()) {
$this->release();
}
if (!$this->releaseOnDestruction) {
return;
}
if (!$this->canRelease()) {
return;
}
$actor = PhabricatorUser::getOmnipotentUser();
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
$command = DrydockCommand::initializeNewCommand($actor)
->setTargetPHID($this->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$this->scheduleUpdate();
}
public function getLeaseName() {
@ -44,18 +60,15 @@ final class DrydockLease extends DrydockDAO
'attributes' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'status' => 'uint32',
'status' => 'text32',
'until' => 'epoch?',
'resourceType' => 'text128',
'taskID' => 'id?',
'ownerPHID' => 'phid?',
'resourceID' => 'id?',
'resourcePHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
'key_resource' => array(
'columns' => array('resourcePHID', 'status'),
),
),
) + parent::getConfiguration();
@ -91,114 +104,233 @@ final class DrydockLease extends DrydockDAO
return ($this->resource !== null);
}
public function loadResource() {
return id(new DrydockResource())->loadOneWhere(
'id = %d',
$this->getResourceID());
}
public function queueForActivation() {
if ($this->getID()) {
throw new Exception(
pht('Only new leases may be queued for activation!'));
}
$this->setStatus(DrydockLeaseStatus::STATUS_PENDING);
$this->save();
$this
->setStatus(DrydockLeaseStatus::STATUS_PENDING)
->save();
$task = PhabricatorWorker::scheduleTask(
'DrydockAllocatorWorker',
$this->getID());
// NOTE: Scheduling the task might execute it in-process, if we're running
// from a CLI script. Reload the lease to make sure we have the most
// up-to-date information. Normally, this has no effect.
$this->reload();
$this->setTaskID($task->getID());
$this->save();
array(
'leasePHID' => $this->getPHID(),
),
array(
'objectPHID' => $this->getPHID(),
));
return $this;
}
public function release() {
$this->assertActive();
$this->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
$this->save();
$this->resource = null;
return $this;
}
public function isActive() {
switch ($this->status) {
case DrydockLeaseStatus::STATUS_ACTIVE:
case DrydockLeaseStatus::STATUS_ACQUIRING:
public function isActivating() {
switch ($this->getStatus()) {
case DrydockLeaseStatus::STATUS_PENDING:
case DrydockLeaseStatus::STATUS_ACQUIRED:
return true;
}
return false;
}
private function assertActive() {
if (!$this->isActive()) {
throw new Exception(
pht(
'Lease is not active! You can not interact with resources through '.
'an inactive lease.'));
}
}
public static function waitForLeases(array $leases) {
assert_instances_of($leases, __CLASS__);
$task_ids = array_filter(mpull($leases, 'getTaskID'));
PhabricatorWorker::waitForTasks($task_ids);
$unresolved = $leases;
while (true) {
foreach ($unresolved as $key => $lease) {
$lease->reload();
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_ACTIVE:
unset($unresolved[$key]);
break;
case DrydockLeaseStatus::STATUS_RELEASED:
throw new Exception(pht('Lease has already been released!'));
case DrydockLeaseStatus::STATUS_EXPIRED:
throw new Exception(pht('Lease has already expired!'));
case DrydockLeaseStatus::STATUS_BROKEN:
throw new Exception(pht('Lease has been broken!'));
case DrydockLeaseStatus::STATUS_PENDING:
case DrydockLeaseStatus::STATUS_ACQUIRING:
break;
default:
throw new Exception(pht('Unknown status??'));
}
}
if ($unresolved) {
sleep(1);
} else {
break;
}
public function isActive() {
switch ($this->getStatus()) {
case DrydockLeaseStatus::STATUS_ACTIVE:
return true;
}
foreach ($leases as $lease) {
$lease->attachResource($lease->loadResource());
}
return false;
}
public function waitUntilActive() {
if (!$this->getID()) {
$this->queueForActivation();
while (true) {
$lease = $this->reload();
if (!$lease) {
throw new Exception(pht('Failed to reload lease.'));
}
$status = $lease->getStatus();
switch ($status) {
case DrydockLeaseStatus::STATUS_ACTIVE:
return;
case DrydockLeaseStatus::STATUS_RELEASED:
throw new Exception(pht('Lease has already been released!'));
case DrydockLeaseStatus::STATUS_DESTROYED:
throw new Exception(pht('Lease has already been destroyed!'));
case DrydockLeaseStatus::STATUS_BROKEN:
throw new Exception(pht('Lease has been broken!'));
case DrydockLeaseStatus::STATUS_PENDING:
case DrydockLeaseStatus::STATUS_ACQUIRED:
break;
default:
throw new Exception(
pht(
'Lease has unknown status "%s".',
$status));
}
sleep(1);
}
}
public function setActivateWhenAcquired($activate) {
$this->activateWhenAcquired = true;
return $this;
}
public function needSlotLock($key) {
$this->slotLocks[] = $key;
return $this;
}
public function acquireOnResource(DrydockResource $resource) {
$expect_status = DrydockLeaseStatus::STATUS_PENDING;
$actual_status = $this->getStatus();
if ($actual_status != $expect_status) {
throw new Exception(
pht(
'Trying to acquire a lease on a resource which is in the wrong '.
'state: status must be "%s", actually "%s".',
$expect_status,
$actual_status));
}
if ($this->activateWhenAcquired) {
$new_status = DrydockLeaseStatus::STATUS_ACTIVE;
} else {
$new_status = DrydockLeaseStatus::STATUS_ACQUIRED;
}
if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
throw new Exception(
pht(
'Trying to acquire an active lease on a pending resource. '.
'You can not immediately activate leases on resources which '.
'need time to start up.'));
}
}
$this->openTransaction();
$this
->setResourcePHID($resource->getPHID())
->setStatus($new_status)
->save();
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
$this->saveTransaction();
$this->isAcquired = true;
if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {
$this->didActivate();
}
self::waitForLeases(array($this));
return $this;
}
public function isAcquiredLease() {
return $this->isAcquired;
}
public function activateOnResource(DrydockResource $resource) {
$expect_status = DrydockLeaseStatus::STATUS_ACQUIRED;
$actual_status = $this->getStatus();
if ($actual_status != $expect_status) {
throw new Exception(
pht(
'Trying to activate a lease which has the wrong status: status '.
'must be "%s", actually "%s".',
$expect_status,
$actual_status));
}
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
// TODO: Be stricter about this?
throw new Exception(
pht(
'Trying to activate a lease on a pending resource.'));
}
$this->openTransaction();
$this
->setStatus(DrydockLeaseStatus::STATUS_ACTIVE)
->save();
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
$this->saveTransaction();
$this->isActivated = true;
$this->didActivate();
return $this;
}
public function isActivatedLease() {
return $this->isActivated;
}
public function canRelease() {
if (!$this->getID()) {
return false;
}
switch ($this->getStatus()) {
case DrydockLeaseStatus::STATUS_RELEASED:
case DrydockLeaseStatus::STATUS_DESTROYED:
return false;
default:
return true;
}
}
public function scheduleUpdate($epoch = null) {
PhabricatorWorker::scheduleTask(
'DrydockLeaseUpdateWorker',
array(
'leasePHID' => $this->getPHID(),
'isExpireTask' => ($epoch !== null),
),
array(
'objectPHID' => $this->getPHID(),
'delayUntil' => $epoch,
));
}
private function didActivate() {
$viewer = PhabricatorUser::getOmnipotentUser();
$need_update = false;
$commands = id(new DrydockCommandQuery())
->setViewer($viewer)
->withTargetPHIDs(array($this->getPHID()))
->withConsumed(false)
->execute();
if ($commands) {
$need_update = true;
}
if ($need_update) {
$this->scheduleUpdate();
}
$expires = $this->getUntil();
if ($expires) {
$this->scheduleUpdate($expires);
}
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -206,6 +338,7 @@ final class DrydockLease extends DrydockDAO
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
@ -213,6 +346,9 @@ final class DrydockLease extends DrydockDAO
if ($this->getResource()) {
return $this->getResource()->getPolicy($capability);
}
// TODO: Implement reasonable policies.
return PhabricatorPolicies::getMostOpenPolicy();
}

View file

@ -14,7 +14,11 @@ final class DrydockResource extends DrydockDAO
protected $capabilities = array();
protected $ownerPHID;
private $blueprint;
private $blueprint = self::ATTACHABLE;
private $isAllocated = false;
private $isActivated = false;
private $activateWhenAllocated = false;
private $slotLocks = array();
protected function getConfiguration() {
return array(
@ -26,14 +30,15 @@ final class DrydockResource extends DrydockDAO
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'ownerPHID' => 'phid?',
'status' => 'uint32',
'status' => 'text32',
'type' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
'key_type' => array(
'columns' => array('type', 'status'),
),
'key_blueprint' => array(
'columns' => array('blueprintPHID', 'status'),
),
),
) + parent::getConfiguration();
@ -65,46 +70,145 @@ final class DrydockResource extends DrydockDAO
}
public function getBlueprint() {
// TODO: Policy stuff.
if (empty($this->blueprint)) {
$blueprint = id(new DrydockBlueprint())
->loadOneWhere('phid = %s', $this->blueprintPHID);
$this->blueprint = $blueprint->getImplementation();
}
return $this->blueprint;
return $this->assertAttached($this->blueprint);
}
public function closeResource() {
public function attachBlueprint(DrydockBlueprint $blueprint) {
$this->blueprint = $blueprint;
return $this;
}
public function setActivateWhenAllocated($activate) {
$this->activateWhenAllocated = $activate;
return $this;
}
public function needSlotLock($key) {
$this->slotLocks[] = $key;
return $this;
}
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.'));
}
$expect_status = DrydockResourceStatus::STATUS_PENDING;
$actual_status = $this->getStatus();
if ($actual_status != $expect_status) {
throw new Exception(
pht(
'Trying to allocate a resource from the wrong status. Status must '.
'be "%s", actually "%s".',
$expect_status,
$actual_status));
}
if ($this->activateWhenAllocated) {
$new_status = DrydockResourceStatus::STATUS_ACTIVE;
} else {
$new_status = DrydockResourceStatus::STATUS_PENDING;
}
$this->openTransaction();
$statuses = array(
DrydockLeaseStatus::STATUS_PENDING,
DrydockLeaseStatus::STATUS_ACTIVE,
);
$leases = id(new DrydockLeaseQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withResourceIDs(array($this->getID()))
->withStatuses($statuses)
->execute();
$this
->setStatus($new_status)
->save();
foreach ($leases as $lease) {
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_PENDING:
$message = pht('Breaking pending lease (resource closing).');
$lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN);
break;
case DrydockLeaseStatus::STATUS_ACTIVE:
$message = pht('Releasing active lease (resource closing).');
$lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
break;
}
DrydockBlueprintImplementation::writeLog($this, $lease, $message);
$lease->save();
}
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
$this->setStatus(DrydockResourceStatus::STATUS_CLOSED);
$this->save();
$this->saveTransaction();
$this->isAllocated = true;
return $this;
}
public function isAllocatedResource() {
return $this->isAllocated;
}
public function activateResource() {
if (!$this->getID()) {
throw new Exception(
pht(
'Trying to activate a resource which has not yet been persisted.'));
}
$expect_status = DrydockResourceStatus::STATUS_PENDING;
$actual_status = $this->getStatus();
if ($actual_status != $expect_status) {
throw new Exception(
pht(
'Trying to activate a resource from the wrong status. Status must '.
'be "%s", actually "%s".',
$expect_status,
$actual_status));
}
$this->openTransaction();
$this
->setStatus(DrydockResourceStatus::STATUS_ACTIVE)
->save();
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
$this->saveTransaction();
$this->isActivated = true;
return $this;
}
public function isActivatedResource() {
return $this->isActivated;
}
public function canRelease() {
switch ($this->getStatus()) {
case DrydockResourceStatus::STATUS_RELEASED:
case DrydockResourceStatus::STATUS_DESTROYED:
return false;
default:
return true;
}
}
public function scheduleUpdate() {
PhabricatorWorker::scheduleTask(
'DrydockResourceUpdateWorker',
array(
'resourcePHID' => $this->getPHID(),
),
array(
'objectPHID' => $this->getPHID(),
));
}
private function didActivate() {
$viewer = PhabricatorUser::getOmnipotentUser();
$need_update = false;
$commands = id(new DrydockCommandQuery())
->setViewer($viewer)
->withTargetPHIDs(array($this->getPHID()))
->withConsumed(false)
->execute();
if ($commands) {
$need_update = true;
}
if ($need_update) {
$this->scheduleUpdate();
}
}
@ -114,21 +218,21 @@ final class DrydockResource extends DrydockDAO
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return PhabricatorPolicies::getMostOpenPolicy();
}
return $this->getBlueprint()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
return $this->getBlueprint()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return null;
return pht('Resources inherit the policies of their blueprints.');
}
}

View file

@ -0,0 +1,175 @@
<?php
/**
* Simple optimistic locks for Drydock resources and leases.
*
* Most blueprints only need very simple locks: for example, a host blueprint
* might not want to create multiple resources representing the same physical
* machine. These optimistic "slot locks" provide a flexible way to do this
* sort of simple locking.
*
* @task info Getting Lock Information
* @task lock Acquiring and Releasing Locks
*/
final class DrydockSlotLock extends DrydockDAO {
protected $ownerPHID;
protected $lockIndex;
protected $lockKey;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'lockIndex' => 'bytes12',
'lockKey' => 'text',
),
self::CONFIG_KEY_SCHEMA => array(
'key_lock' => array(
'columns' => array('lockIndex'),
'unique' => true,
),
'key_owner' => array(
'columns' => array('ownerPHID'),
),
),
) + parent::getConfiguration();
}
/* -( Getting Lock Information )------------------------------------------- */
/**
* Load all locks held by a particular owner.
*
* @param phid Owner PHID.
* @return list<DrydockSlotLock> All held locks.
* @task info
*/
public static function loadLocks($owner_phid) {
return id(new DrydockSlotLock())->loadAllWhere(
'ownerPHID = %s',
$owner_phid);
}
/**
* Test if a lock is currently free.
*
* @param string Lock key to test.
* @return bool True if the lock is currently free.
* @task info
*/
public static function isLockFree($lock) {
return self::areLocksFree(array($lock));
}
/**
* Test if a list of locks are all currently free.
*
* @param list<string> List of lock keys to test.
* @return bool True if all locks are currently free.
* @task info
*/
public static function areLocksFree(array $locks) {
$lock_map = self::loadHeldLocks($locks);
return !$lock_map;
}
/**
* Load named locks.
*
* @param list<string> List of lock keys to load.
* @return list<DrydockSlotLock> List of held locks.
* @task info
*/
public static function loadHeldLocks(array $locks) {
if (!$locks) {
return array();
}
$table = new DrydockSlotLock();
$conn_r = $table->establishConnection('r');
$indexes = array();
foreach ($locks as $lock) {
$indexes[] = PhabricatorHash::digestForIndex($lock);
}
return id(new DrydockSlotLock())->loadAllWhere(
'lockIndex IN (%Ls)',
$indexes);
}
/* -( Acquiring and Releasing Locks )-------------------------------------- */
/**
* Acquire a set of slot locks.
*
* This method either acquires all the locks or throws an exception (usually
* because one or more locks are held).
*
* @param phid Lock owner PHID.
* @param list<string> List of locks to acquire.
* @return void
* @task locks
*/
public static function acquireLocks($owner_phid, array $locks) {
if (!$locks) {
return;
}
$table = new DrydockSlotLock();
$conn_w = $table->establishConnection('w');
$sql = array();
foreach ($locks as $lock) {
$sql[] = qsprintf(
$conn_w,
'(%s, %s, %s)',
$owner_phid,
PhabricatorHash::digestForIndex($lock),
$lock);
}
try {
queryfx(
$conn_w,
'INSERT INTO %T (ownerPHID, lockIndex, lockKey) VALUES %Q',
$table->getTableName(),
implode(', ', $sql));
} catch (AphrontDuplicateKeyQueryException $ex) {
// Try to improve the readability of the exception. We might miss on
// this query if the lock has already been released, but most of the
// time we should be able to figure out which locks are already held.
$held = self::loadHeldLocks($locks);
$held = mpull($held, 'getOwnerPHID', 'getLockKey');
throw new DrydockSlotLockException($held);
}
}
/**
* Release all locks held by an owner.
*
* @param phid Lock owner PHID.
* @return void
* @task locks
*/
public static function releaseLocks($owner_phid) {
$table = new DrydockSlotLock();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE ownerPHID = %s',
$table->getTableName(),
$owner_phid);
}
}

View file

@ -1,15 +0,0 @@
<?php
final class DrydockBlueprintScopeGuard extends Phobject {
private $blueprint;
public function __construct(DrydockBlueprintImplementation $blueprint) {
$this->blueprint = $blueprint;
}
public function __destruct() {
$this->blueprint->popActiveScope();
}
}

View file

@ -41,7 +41,10 @@ final class DrydockLeaseListView extends AphrontView {
$item->addAttribute($status);
$item->setEpoch($lease->getDateCreated());
if ($lease->isActive()) {
// TODO: Tailor this for clarity.
if ($lease->isActivating()) {
$item->setStatusIcon('fa-dot-circle-o yellow');
} else if ($lease->isActive()) {
$item->setStatusIcon('fa-dot-circle-o green');
} else {
$item->setStatusIcon('fa-dot-circle-o red');

View file

@ -29,7 +29,7 @@ final class DrydockResourceListView extends AphrontView {
case DrydockResourceStatus::STATUS_PENDING:
$item->setStatusIcon('fa-dot-circle-o yellow');
break;
case DrydockResourceStatus::STATUS_OPEN:
case DrydockResourceStatus::STATUS_ACTIVE:
$item->setStatusIcon('fa-dot-circle-o green');
break;
case DrydockResourceStatus::STATUS_DESTROYED:

View file

@ -1,187 +1,479 @@
<?php
final class DrydockAllocatorWorker extends PhabricatorWorker {
private $lease;
public function getRequiredLeaseTime() {
return 3600 * 24;
}
public function getMaximumRetryCount() {
// TODO: Allow Drydock allocations to retry. For now, every failure is
// permanent and most of them are because I am bad at programming, so fail
// fast rather than ending up in limbo.
return 0;
}
private function loadLease() {
if (empty($this->lease)) {
$lease = id(new DrydockLeaseQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withIDs(array($this->getTaskData()))
->executeOne();
if (!$lease) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No such lease %d!', $this->getTaskData()));
}
$this->lease = $lease;
}
return $this->lease;
}
private function logToDrydock($message) {
DrydockBlueprintImplementation::writeLog(
null,
$this->loadLease(),
$message);
}
/**
* @task allocate Allocator
* @task resource Managing Resources
* @task lease Managing Leases
*/
final class DrydockAllocatorWorker extends DrydockWorker {
protected function doWork() {
$lease = $this->loadLease();
$this->logToDrydock(pht('Allocating Lease'));
$lease_phid = $this->getTaskDataValue('leasePHID');
$lease = $this->loadLease($lease_phid);
try {
$this->allocateLease($lease);
} catch (Exception $ex) {
$this->allocateAndAcquireLease($lease);
}
// TODO: We should really do this when archiving the task, if we've
// suffered a permanent failure. But we don't have hooks for that yet
// and always fail after the first retry right now, so this is
// functionally equivalent.
$lease->reload();
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_PENDING) {
$lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN);
$lease->save();
/* -( Allocator )---------------------------------------------------------- */
/**
* Find or build a resource which can satisfy a given lease request, then
* acquire the lease.
*
* @param DrydockLease Requested lease.
* @return void
* @task allocator
*/
private function allocateAndAcquireLease(DrydockLease $lease) {
$blueprints = $this->loadBlueprintsForAllocatingLease($lease);
// If we get nothing back, that means no blueprint is defined which can
// ever build the requested resource. This is a permanent failure, since
// we don't expect to succeed no matter how many times we try.
if (!$blueprints) {
$lease
->setStatus(DrydockLeaseStatus::STATUS_BROKEN)
->save();
throw new PhabricatorWorkerPermanentFailureException(
pht(
'No active Drydock blueprint exists which can ever allocate a '.
'resource for lease "%s".',
$lease->getPHID()));
}
// First, try to find a suitable open resource which we can acquire a new
// lease on.
$resources = $this->loadResourcesForAllocatingLease($blueprints, $lease);
// If no resources exist yet, see if we can build one.
if (!$resources) {
$usable_blueprints = $this->removeOverallocatedBlueprints(
$blueprints,
$lease);
// If we get nothing back here, some blueprint claims it can eventually
// satisfy the lease, just not right now. This is a temporary failure,
// and we expect allocation to succeed eventually.
if (!$blueprints) {
// TODO: More formal temporary failure here. We should retry this
// "soon" but not "immediately".
throw new Exception(
pht('No blueprints have space to allocate a resource right now.'));
}
throw $ex;
$usable_blueprints = $this->rankBlueprints($blueprints, $lease);
$exceptions = array();
foreach ($usable_blueprints as $blueprint) {
try {
$resources[] = $this->allocateResource($blueprint, $lease);
// Bail after allocating one resource, we don't need any more than
// this.
break;
} catch (Exception $ex) {
$exceptions[] = $ex;
}
}
if (!$resources) {
// TODO: We should distinguish between temporary and permament failures
// here. If any blueprint failed temporarily, retry "soon". If none
// of these failures were temporary, maybe this should be a permanent
// failure?
throw new PhutilAggregateException(
pht(
'All blueprints failed to allocate a suitable new resource when '.
'trying to allocate lease "%s".',
$lease->getPHID()),
$exceptions);
}
// NOTE: We have not acquired the lease yet, so it is possible that the
// resource we just built will be snatched up by some other lease before
// we can. This is not problematic: we'll retry a little later and should
// suceed eventually.
}
$resources = $this->rankResources($resources, $lease);
$exceptions = array();
$allocated = false;
foreach ($resources as $resource) {
try {
$this->acquireLease($resource, $lease);
$allocated = true;
break;
} catch (Exception $ex) {
$exceptions[] = $ex;
}
}
if (!$allocated) {
// TODO: We should distinguish between temporary and permanent failures
// here. If any failures were temporary (specifically, failed to acquire
// locks)
throw new PhutilAggregateException(
pht(
'Unable to acquire lease "%s" on any resouce.',
$lease->getPHID()),
$exceptions);
}
}
private function loadAllBlueprints() {
$viewer = PhabricatorUser::getOmnipotentUser();
$instances = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->execute();
$blueprints = array();
foreach ($instances as $instance) {
$blueprints[$instance->getPHID()] = $instance;
}
return $blueprints;
}
private function allocateLease(DrydockLease $lease) {
$type = $lease->getResourceType();
/**
* Get all the @{class:DrydockBlueprintImplementation}s which can possibly
* build a resource to satisfy a lease.
*
* This method returns blueprints which might, at some time, be able to
* build a resource which can satisfy the lease. They may not be able to
* build that resource right now.
*
* @param DrydockLease Requested lease.
* @return list<DrydockBlueprintImplementation> List of qualifying blueprint
* implementations.
* @task allocator
*/
private function loadBlueprintImplementationsForAllocatingLease(
DrydockLease $lease) {
$blueprints = $this->loadAllBlueprints();
$impls = DrydockBlueprintImplementation::getAllBlueprintImplementations();
// TODO: Policy stuff.
$pool = id(new DrydockResource())->loadAllWhere(
'type = %s AND status = %s',
$lease->getResourceType(),
DrydockResourceStatus::STATUS_OPEN);
$this->logToDrydock(
pht('Found %d Open Resource(s)', count($pool)));
$candidates = array();
foreach ($pool as $key => $candidate) {
if (!isset($blueprints[$candidate->getBlueprintPHID()])) {
unset($pool[$key]);
$keep = array();
foreach ($impls as $key => $impl) {
// Don't use disabled blueprint types.
if (!$impl->isEnabled()) {
continue;
}
$blueprint = $blueprints[$candidate->getBlueprintPHID()];
$implementation = $blueprint->getImplementation();
if ($implementation->filterResource($candidate, $lease)) {
$candidates[] = $candidate;
// Don't use blueprint types which can't allocate the correct kind of
// resource.
if ($impl->getType() != $lease->getResourceType()) {
continue;
}
if (!$impl->canAnyBlueprintEverAllocateResourceForLease($lease)) {
continue;
}
$keep[$key] = $impl;
}
$this->logToDrydock(pht('%d Open Resource(s) Remain', count($candidates)));
return $keep;
}
$resource = null;
if ($candidates) {
shuffle($candidates);
foreach ($candidates as $candidate_resource) {
$blueprint = $blueprints[$candidate_resource->getBlueprintPHID()]
->getImplementation();
if ($blueprint->allocateLease($candidate_resource, $lease)) {
$resource = $candidate_resource;
break;
}
}
/**
* Get all the concrete @{class:DrydockBlueprint}s which can possibly
* build a resource to satisfy a lease.
*
* @param DrydockLease Requested lease.
* @return list<DrydockBlueprint> List of qualifying blueprints.
* @task allocator
*/
private function loadBlueprintsForAllocatingLease(
DrydockLease $lease) {
$viewer = $this->getViewer();
$impls = $this->loadBlueprintImplementationsForAllocatingLease($lease);
if (!$impls) {
return array();
}
if (!$resource) {
$blueprints = DrydockBlueprintImplementation
::getAllBlueprintImplementationsForResource($type);
$blueprints = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withBlueprintClasses(array_keys($impls))
->withDisabled(false)
->execute();
$this->logToDrydock(
pht('Found %d Blueprints', count($blueprints)));
foreach ($blueprints as $key => $candidate_blueprint) {
if (!$candidate_blueprint->isEnabled()) {
unset($blueprints[$key]);
continue;
}
$keep = array();
foreach ($blueprints as $key => $blueprint) {
if (!$blueprint->canEverAllocateResourceForLease($lease)) {
continue;
}
$this->logToDrydock(
pht('%d Blueprints Enabled', count($blueprints)));
foreach ($blueprints as $key => $candidate_blueprint) {
if (!$candidate_blueprint->canAllocateMoreResources($pool)) {
unset($blueprints[$key]);
continue;
}
}
$this->logToDrydock(
pht('%d Blueprints Can Allocate', count($blueprints)));
if (!$blueprints) {
$lease->setStatus(DrydockLeaseStatus::STATUS_BROKEN);
$lease->save();
$this->logToDrydock(
pht(
"There are no resources of type '%s' available, and no ".
"blueprints which can allocate new ones.",
$type));
return;
}
// TODO: Rank intelligently.
shuffle($blueprints);
$blueprint = head($blueprints);
$resource = $blueprint->allocateResource($lease);
if (!$blueprint->allocateLease($resource, $lease)) {
// TODO: This "should" happen only if we lost a race with another lease,
// which happened to acquire this resource immediately after we
// allocated it. In this case, the right behavior is to retry
// immediately. However, other things like a blueprint allocating a
// resource it can't actually allocate the lease on might be happening
// too, in which case we'd just allocate infinite resources. Probably
// what we should do is test for an active or allocated lease and retry
// if we find one (although it might have already been released by now)
// and fail really hard ("your configuration is a huge broken mess")
// otherwise. But just throw for now since this stuff is all edge-casey.
// Alternatively we could bring resources up in a "BESPOKE" status
// and then switch them to "OPEN" only after the allocating lease gets
// its grubby mitts on the resource. This might make more sense but
// is a bit messy.
throw new Exception(pht('Lost an allocation race?'));
}
$keep[$key] = $blueprint;
}
return $keep;
}
/**
* Load a list of all resources which a given lease can possibly be
* allocated against.
*
* @param list<DrydockBlueprint> Blueprints which may produce suitable
* resources.
* @param DrydockLease Requested lease.
* @return list<DrydockResource> Resources which may be able to allocate
* the lease.
* @task allocator
*/
private function loadResourcesForAllocatingLease(
array $blueprints,
DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
$viewer = $this->getViewer();
$resources = id(new DrydockResourceQuery())
->setViewer($viewer)
->withBlueprintPHIDs(mpull($blueprints, 'getPHID'))
->withTypes(array($lease->getResourceType()))
->withStatuses(
array(
DrydockResourceStatus::STATUS_PENDING,
DrydockResourceStatus::STATUS_ACTIVE,
))
->execute();
$keep = array();
foreach ($resources as $key => $resource) {
$blueprint = $resource->getBlueprint();
if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) {
continue;
}
$keep[$key] = $resource;
}
return $keep;
}
/**
* Remove blueprints which are too heavily allocated to build a resource for
* a lease from a list of blueprints.
*
* @param list<DrydockBlueprint> List of blueprints.
* @return list<DrydockBlueprint> List with blueprints that can not allocate
* a resource for the lease right now removed.
* @task allocator
*/
private function removeOverallocatedBlueprints(
array $blueprints,
DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
$keep = array();
foreach ($blueprints as $key => $blueprint) {
if (!$blueprint->canAllocateResourceForLease($lease)) {
continue;
}
$keep[$key] = $blueprint;
}
return $keep;
}
/**
* Rank blueprints by suitability for building a new resource for a
* particular lease.
*
* @param list<DrydockBlueprint> List of blueprints.
* @param DrydockLease Requested lease.
* @return list<DrydockBlueprint> Ranked list of blueprints.
* @task allocator
*/
private function rankBlueprints(array $blueprints, DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
// TODO: Implement improvements to this ranking algorithm if they become
// available.
shuffle($blueprints);
return $blueprints;
}
/**
* Rank resources by suitability for allocating a particular lease.
*
* @param list<DrydockResource> List of resources.
* @param DrydockLease Requested lease.
* @return list<DrydockResource> Ranked list of resources.
* @task allocator
*/
private function rankResources(array $resources, DrydockLease $lease) {
assert_instances_of($resources, 'DrydockResource');
// TODO: Implement improvements to this ranking algorithm if they become
// available.
shuffle($resources);
return $resources;
}
/* -( Managing Resources )------------------------------------------------- */
/**
* Perform an actual resource allocation with a particular blueprint.
*
* @param DrydockBlueprint The blueprint to allocate a resource from.
* @param DrydockLease Requested lease.
* @return DrydockResource Allocated resource.
* @task resource
*/
private function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$resource = $blueprint->allocateResource($lease);
$this->validateAllocatedResource($blueprint, $resource, $lease);
// If this resource was allocated as a pending resource, queue a task to
// activate it.
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
PhabricatorWorker::scheduleTask(
'DrydockResourceWorker',
array(
'resourcePHID' => $resource->getPHID(),
),
array(
'objectPHID' => $resource->getPHID(),
));
}
return $resource;
}
/**
* Check that the resource a blueprint allocated is roughly the sort of
* object we expect.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param wild Thing which the blueprint claims is a valid resource.
* @param DrydockLease Lease the resource was allocated for.
* @return void
* @task resource
*/
private function validateAllocatedResource(
DrydockBlueprint $blueprint,
$resource,
DrydockLease $lease) {
if (!($resource instanceof DrydockResource)) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: %s must '.
'return an object of type %s or throw, but returned something else.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'allocateResource()',
'DrydockResource'));
}
if (!$resource->isAllocatedResource()) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: %s '.
'must actually allocate the resource it returns.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'allocateResource()'));
}
$resource_type = $resource->getType();
$lease_type = $lease->getResourceType();
if ($resource_type !== $lease_type) {
// TODO: Destroy the resource here?
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'built a resource of type "%s" to satisfy a lease requesting a '.
'resource of type "%s".',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
$resource_type,
$lease_type));
}
}
/* -( Managing Leases )---------------------------------------------------- */
/**
* Perform an actual lease acquisition on a particular resource.
*
* @param DrydockResource Resource to acquire a lease on.
* @param DrydockLease Lease to acquire.
* @return void
* @task lease
*/
private function acquireLease(
DrydockResource $resource,
DrydockLease $lease) {
$blueprint = $resource->getBlueprint();
$blueprint->acquireLease($resource, $lease);
$this->validateAcquiredLease($blueprint, $resource, $lease);
// If this lease has been acquired but not activated, queue a task to
// activate it.
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) {
PhabricatorWorker::scheduleTask(
'DrydockLeaseWorker',
array(
'leasePHID' => $lease->getPHID(),
),
array(
'objectPHID' => $lease->getPHID(),
));
}
}
/**
* Make sure that a lease was really acquired properly.
*
* @param DrydockBlueprint Blueprint which created the resource.
* @param DrydockResource Resource which was acquired.
* @param DrydockLease The lease which was supposedly acquired.
* @return void
* @task lease
*/
private function validateAcquiredLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
if (!$lease->isAcquiredLease()) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'returned from "%s" without acquiring a lease.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'acquireLease()'));
}
$lease_phid = $lease->getResourcePHID();
$resource_phid = $resource->getPHID();
if ($lease_phid !== $resource_phid) {
// TODO: Destroy the lease?
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'returned from "%s" with a lease acquired on the wrong resource.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'acquireLease()'));
}
}
}

View file

@ -0,0 +1,37 @@
<?php
final class DrydockLeaseDestroyWorker extends DrydockWorker {
protected function doWork() {
$lease_phid = $this->getTaskDataValue('leasePHID');
$lease = $this->loadLease($lease_phid);
$this->destroyLease($lease);
}
private function destroyLease(DrydockLease $lease) {
$status = $lease->getStatus();
switch ($status) {
case DrydockLeaseStatus::STATUS_RELEASED:
case DrydockLeaseStatus::STATUS_BROKEN:
break;
default:
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Unable to destroy lease ("%s"), lease has the wrong '.
'status ("%s").',
$lease->getPHID(),
$status));
}
$resource = $lease->getResource();
$blueprint = $resource->getBlueprint();
$blueprint->destroyLease($resource, $lease);
$lease
->setStatus(DrydockLeaseStatus::STATUS_DESTROYED)
->save();
}
}

View file

@ -0,0 +1,107 @@
<?php
final class DrydockLeaseUpdateWorker extends DrydockWorker {
protected function doWork() {
$lease_phid = $this->getTaskDataValue('leasePHID');
$hash = PhabricatorHash::digestForIndex($lease_phid);
$lock_key = 'drydock.lease:'.$hash;
$lock = PhabricatorGlobalLock::newLock($lock_key)
->lock(1);
try {
$lease = $this->loadLease($lease_phid);
$this->updateLease($lease);
} catch (Exception $ex) {
$lock->unlock();
throw $ex;
}
$lock->unlock();
}
private function updateLease(DrydockLease $lease) {
if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) {
return;
}
$viewer = $this->getViewer();
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
// Check if the lease has expired. If it is, we're going to send it a
// release command. This command will be handled immediately below, it
// just generates a command log and improves consistency.
$now = PhabricatorTime::getNow();
$expires = $lease->getUntil();
if ($expires && ($expires <= $now)) {
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($lease->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
}
$commands = $this->loadCommands($lease->getPHID());
foreach ($commands as $command) {
if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) {
// Leases can't receive commands before they activate or after they
// release.
break;
}
$this->processCommand($lease, $command);
$command
->setIsConsumed(true)
->save();
}
// If this is the task which will eventually release the lease after it
// expires but it is still active, reschedule the task to run after the
// lease expires. This can happen if the lease's expiration was pushed
// forward.
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE) {
if ($this->getTaskDataValue('isExpireTask') && $expires) {
throw new PhabricatorWorkerYieldException($expires - $now);
}
}
}
private function processCommand(
DrydockLease $lease,
DrydockCommand $command) {
switch ($command->getCommand()) {
case DrydockCommand::COMMAND_RELEASE:
$this->releaseLease($lease);
break;
}
}
private function releaseLease(DrydockLease $lease) {
$lease->openTransaction();
$lease
->setStatus(DrydockLeaseStatus::STATUS_RELEASED)
->save();
// TODO: Hold slot locks until destruction?
DrydockSlotLock::releaseLocks($lease->getPHID());
$lease->saveTransaction();
PhabricatorWorker::scheduleTask(
'DrydockLeaseDestroyWorker',
array(
'leasePHID' => $lease->getPHID(),
),
array(
'objectPHID' => $lease->getPHID(),
));
$resource = $lease->getResource();
$blueprint = $resource->getBlueprint();
$blueprint->didReleaseLease($resource, $lease);
}
}

View file

@ -0,0 +1,74 @@
<?php
final class DrydockLeaseWorker extends DrydockWorker {
protected function doWork() {
$lease_phid = $this->getTaskDataValue('leasePHID');
$lease = $this->loadLease($lease_phid);
$this->activateLease($lease);
}
private function activateLease(DrydockLease $lease) {
$actual_status = $lease->getStatus();
if ($actual_status != DrydockLeaseStatus::STATUS_ACQUIRED) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Trying to activate lease from wrong status ("%s").',
$actual_status));
}
$resource = $lease->getResource();
if (!$resource) {
throw new PhabricatorWorkerPermanentFailureException(
pht('Trying to activate lease with no resource.'));
}
$resource_status = $resource->getStatus();
if ($resource_status == DrydockResourceStatus::STATUS_PENDING) {
// TODO: This is explicitly a temporary failure -- we are waiting for
// the resource to come up.
throw new Exception(pht('Resource still activating.'));
}
if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Trying to activate lease on a dead resource (in status "%s").',
$resource_status));
}
// NOTE: We can race resource destruction here. Between the time we
// performed the read above and now, the resource might have closed, so
// we may activate leases on dead resources. At least for now, this seems
// fine: a resource dying right before we activate a lease on it should not
// be distinguisahble from a resource dying right after we activate a lease
// on it. We end up with an active lease on a dead resource either way, and
// can not prevent resources dying from lightning strikes.
$blueprint = $resource->getBlueprint();
$blueprint->activateLease($resource, $lease);
$this->validateActivatedLease($blueprint, $resource, $lease);
}
private function validateActivatedLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
if (!$lease->isActivatedLease()) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'returned from "%s" without activating a lease.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'acquireLease()'));
}
}
}

View file

@ -0,0 +1,35 @@
<?php
final class DrydockResourceDestroyWorker extends DrydockWorker {
protected function doWork() {
$resource_phid = $this->getTaskDataValue('resourcePHID');
$resource = $this->loadResource($resource_phid);
$this->destroyResource($resource);
}
private function destroyResource(DrydockResource $resource) {
$status = $resource->getStatus();
switch ($status) {
case DrydockResourceStatus::STATUS_RELEASED:
case DrydockResourceStatus::STATUS_BROKEN:
break;
default:
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Unable to destroy resource ("%s"), resource has the wrong '.
'status ("%s").',
$resource->getPHID(),
$status));
}
$blueprint = $resource->getBlueprint();
$blueprint->destroyResource($resource);
$resource
->setStatus(DrydockResourceStatus::STATUS_DESTROYED)
->save();
}
}

View file

@ -0,0 +1,99 @@
<?php
final class DrydockResourceUpdateWorker extends DrydockWorker {
protected function doWork() {
$resource_phid = $this->getTaskDataValue('resourcePHID');
$hash = PhabricatorHash::digestForIndex($resource_phid);
$lock_key = 'drydock.resource:'.$hash;
$lock = PhabricatorGlobalLock::newLock($lock_key)
->lock(1);
$resource = $this->loadResource($resource_phid);
$this->updateResource($resource);
$lock->unlock();
}
private function updateResource(DrydockResource $resource) {
$commands = $this->loadCommands($resource->getPHID());
foreach ($commands as $command) {
if ($resource->getStatus() != DrydockResourceStatus::STATUS_ACTIVE) {
// Resources can't receive commands before they activate or after they
// release.
break;
}
$this->processCommand($resource, $command);
$command
->setIsConsumed(true)
->save();
}
}
private function processCommand(
DrydockResource $resource,
DrydockCommand $command) {
switch ($command->getCommand()) {
case DrydockCommand::COMMAND_RELEASE:
$this->releaseResource($resource);
break;
}
}
private function releaseResource(DrydockResource $resource) {
if ($resource->getStatus() != DrydockResourceStatus::STATUS_ACTIVE) {
// If we had multiple release commands
// This command is only meaningful to resources in the "Open" state.
return;
}
$viewer = $this->getViewer();
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
$resource->openTransaction();
$resource
->setStatus(DrydockResourceStatus::STATUS_RELEASED)
->save();
// TODO: Hold slot locks until destruction?
DrydockSlotLock::releaseLocks($resource->getPHID());
$resource->saveTransaction();
$statuses = array(
DrydockLeaseStatus::STATUS_PENDING,
DrydockLeaseStatus::STATUS_ACQUIRED,
DrydockLeaseStatus::STATUS_ACTIVE,
);
$leases = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withResourcePHIDs(array($resource->getPHID()))
->withStatuses($statuses)
->execute();
foreach ($leases as $lease) {
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($lease->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$lease->scheduleUpdate();
}
PhabricatorWorker::scheduleTask(
'DrydockResourceDestroyWorker',
array(
'resourcePHID' => $resource->getPHID(),
),
array(
'objectPHID' => $resource->getPHID(),
));
}
}

View file

@ -0,0 +1,45 @@
<?php
final class DrydockResourceWorker extends DrydockWorker {
protected function doWork() {
$resource_phid = $this->getTaskDataValue('resourcePHID');
$resource = $this->loadResource($resource_phid);
$this->activateResource($resource);
}
private function activateResource(DrydockResource $resource) {
$resource_status = $resource->getStatus();
if ($resource_status != DrydockResourceStatus::STATUS_PENDING) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Trying to activate resource from wrong status ("%s").',
$resource_status));
}
$blueprint = $resource->getBlueprint();
$blueprint->activateResource($resource);
$this->validateActivatedResource($blueprint, $resource);
}
private function validateActivatedResource(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
if (!$resource->isActivatedResource()) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: %s '.
'must actually allocate the resource it returns.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'allocateResource()'));
}
}
}

View file

@ -0,0 +1,53 @@
<?php
abstract class DrydockWorker extends PhabricatorWorker {
protected function getViewer() {
return PhabricatorUser::getOmnipotentUser();
}
protected function loadLease($lease_phid) {
$viewer = $this->getViewer();
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No such lease "%s"!', $lease_phid));
}
return $lease;
}
protected function loadResource($resource_phid) {
$viewer = $this->getViewer();
$resource = id(new DrydockResourceQuery())
->setViewer($viewer)
->withPHIDs(array($resource_phid))
->executeOne();
if (!$resource) {
throw new PhabricatorWorkerPermanentFailureException(
pht('No such resource "%s"!', $resource_phid));
}
return $resource;
}
protected function loadCommands($target_phid) {
$viewer = $this->getViewer();
$commands = id(new DrydockCommandQuery())
->setViewer($viewer)
->withTargetPHIDs(array($target_phid))
->withConsumed(false)
->execute();
$commands = msort($commands, 'getID');
return $commands;
}
}

View file

@ -116,11 +116,11 @@ final class PhabricatorFileTransformListController
$dst_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('File Sources'))
->appendChild($dst_table);
->setTable($dst_table);
$src_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Available Transforms'))
->appendChild($src_table);
->setTable($src_table);
return $this->buildApplicationPage(
array(

View file

@ -103,7 +103,8 @@ final class FundInitiativeViewController
'default',
$viewer);
$view->addSectionHeader(pht('Description'));
$view->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent($description);
}
@ -114,7 +115,8 @@ final class FundInitiativeViewController
'default',
$viewer);
$view->addSectionHeader(pht('Risks/Challenges'));
$view->addSectionHeader(
pht('Risks/Challenges'), 'fa-ambulance');
$view->addTextContent($risks);
}

View file

@ -65,12 +65,13 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication {
'delete/(?:(?P<id>\d+)/)?' => 'HarbormasterStepDeleteController',
),
'buildable/' => array(
'(?P<id>\d+)/(?P<action>stop|resume|restart)/'
'(?P<id>\d+)/(?P<action>pause|resume|restart|abort)/'
=> 'HarbormasterBuildableActionController',
),
'build/' => array(
'(?P<id>\d+)/' => 'HarbormasterBuildViewController',
'(?P<action>stop|resume|restart)/(?P<id>\d+)/(?:(?P<via>[^/]+)/)?'
'(?P<action>pause|resume|restart|abort)/'.
'(?P<id>\d+)/(?:(?P<via>[^/]+)/)?'
=> 'HarbormasterBuildActionController',
),
'plan/' => array(

View file

@ -0,0 +1,73 @@
<?php
abstract class HarbormasterDrydockLeaseArtifact
extends HarbormasterArtifact {
public function getArtifactParameterSpecification() {
return array(
'drydockLeasePHID' => 'string',
);
}
public function getArtifactParameterDescriptions() {
return array(
'drydockLeasePHID' => pht(
'Drydock working copy lease to create an artifact from.'),
);
}
public function getArtifactDataExample() {
return array(
'drydockLeasePHID' => 'PHID-DRYL-abcdefghijklmnopqrst',
);
}
public function renderArtifactSummary(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$lease_phid = $artifact->getProperty('drydockLeasePHID');
return $viewer->renderHandle($lease_phid);
}
public function willCreateArtifact(PhabricatorUser $actor) {
$this->loadArtifactLease($actor);
}
public function loadArtifactLease(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$lease_phid = $artifact->getProperty('drydockLeasePHID');
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
throw new Exception(
pht(
'Drydock lease PHID "%s" does not correspond to a valid lease.',
$lease_phid));
}
return $lease;
}
public function releaseArtifact(PhabricatorUser $actor) {
$lease = $this->loadArtifactLease($actor);
if (!$lease->canRelease()) {
return;
}
$author_phid = $actor->getPHID();
if (!$author_phid) {
$author_phid = id(new PhabricatorHarbormasterApplication())->getPHID();
}
$command = DrydockCommand::initializeNewCommand($actor)
->setTargetPHID($lease->getPHID())
->setAuthorPHID($author_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
$lease->scheduleUpdate();
}
}

View file

@ -1,6 +1,7 @@
<?php
final class HarbormasterHostArtifact extends HarbormasterArtifact {
final class HarbormasterHostArtifact
extends HarbormasterDrydockLeaseArtifact {
const ARTIFACTCONST = 'host';
@ -12,63 +13,4 @@ final class HarbormasterHostArtifact extends HarbormasterArtifact {
return pht('References a host lease from Drydock.');
}
public function getArtifactParameterSpecification() {
return array(
'drydockLeasePHID' => 'string',
);
}
public function getArtifactParameterDescriptions() {
return array(
'drydockLeasePHID' => pht(
'Drydock host lease to create an artifact from.'),
);
}
public function getArtifactDataExample() {
return array(
'drydockLeasePHID' => 'PHID-DRYL-abcdefghijklmnopqrst',
);
}
public function renderArtifactSummary(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$file_phid = $artifact->getProperty('drydockLeasePHID');
return $viewer->renderHandle($file_phid);
}
public function willCreateArtifact(PhabricatorUser $actor) {
$this->loadArtifactLease($actor);
}
public function loadArtifactLease(PhabricatorUser $viewer) {
$artifact = $this->getBuildArtifact();
$lease_phid = $artifact->getProperty('drydockLeasePHID');
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
throw new Exception(
pht(
'Drydock lease PHID "%s" does not correspond to a valid lease.',
$lease_phid));
}
return $lease;
}
public function releaseArtifact(PhabricatorUser $actor) {
$lease = $this->loadArtifactLease($actor);
$resource = $lease->getResource();
$blueprint = $resource->getBlueprint();
if ($lease->isActive()) {
$blueprint->releaseLease($resource, $lease);
}
}
}

View file

@ -0,0 +1,16 @@
<?php
final class HarbormasterWorkingCopyArtifact
extends HarbormasterDrydockLeaseArtifact {
const ARTIFACTCONST = 'working-copy';
public function getArtifactTypeName() {
return pht('Drydock Working Copy');
}
public function getArtifactTypeDescription() {
return pht('References a working copy lease from Drydock.');
}
}

View file

@ -35,12 +35,15 @@ final class HarbormasterBuildActionController
case HarbormasterBuildCommand::COMMAND_RESTART:
$can_issue = $build->canRestartBuild();
break;
case HarbormasterBuildCommand::COMMAND_STOP:
$can_issue = $build->canStopBuild();
case HarbormasterBuildCommand::COMMAND_PAUSE:
$can_issue = $build->canPauseBuild();
break;
case HarbormasterBuildCommand::COMMAND_RESUME:
$can_issue = $build->canResumeBuild();
break;
case HarbormasterBuildCommand::COMMAND_ABORT:
$can_issue = $build->canAbortBuild();
break;
default:
return new Aphront400Response();
}
@ -90,7 +93,19 @@ final class HarbormasterBuildActionController
}
}
break;
case HarbormasterBuildCommand::COMMAND_STOP:
case HarbormasterBuildCommand::COMMAND_ABORT:
if ($can_issue) {
$title = pht('Really abort build?');
$body = pht(
'Progress on this build will be discarded. Really '.
'abort build?');
$submit = pht('Abort Build');
} else {
$title = pht('Unable to Abort Build');
$body = pht('You can not abort this build.');
}
break;
case HarbormasterBuildCommand::COMMAND_PAUSE:
if ($can_issue) {
$title = pht('Really pause build?');
$body = pht(
@ -103,11 +118,11 @@ final class HarbormasterBuildActionController
$body = pht(
'This build is already complete. You can not pause a completed '.
'build.');
} else if ($build->isStopped()) {
} else if ($build->isPaused()) {
$body = pht(
'This build is already paused. You can not pause a build which '.
'has already been paused.');
} else if ($build->isStopping()) {
} else if ($build->isPausing()) {
$body = pht(
'This build is already pausing. You can not reissue a pause '.
'command to a pausing build.');
@ -129,9 +144,9 @@ final class HarbormasterBuildActionController
$body = pht(
'This build is already resuming. You can not reissue a resume '.
'command to a resuming build.');
} else if (!$build->isStopped()) {
} else if (!$build->isPaused()) {
$body = pht(
'This build is not stopped. You can only resume a stopped '.
'This build is not paused. You can only resume a paused '.
'build.');
}
}

View file

@ -29,10 +29,12 @@ final class HarbormasterBuildViewController
if ($build->isRestarting()) {
$header->setStatus('fa-exclamation-triangle', 'red', pht('Restarting'));
} else if ($build->isStopping()) {
} else if ($build->isPausing()) {
$header->setStatus('fa-exclamation-triangle', 'red', pht('Pausing'));
} else if ($build->isResuming()) {
$header->setStatus('fa-exclamation-triangle', 'red', pht('Resuming'));
} else if ($build->isAborting()) {
$header->setStatus('fa-exclamation-triangle', 'red', pht('Aborting'));
}
$box = id(new PHUIObjectBoxView())
@ -186,7 +188,8 @@ final class HarbormasterBuildViewController
'default',
$viewer);
$properties->addSectionHeader(pht('Description'));
$properties->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
$properties->addTextContent($rendered);
}
} else {
@ -446,8 +449,9 @@ final class HarbormasterBuildViewController
->setObjectURI("/build/{$id}");
$can_restart = $build->canRestartBuild();
$can_stop = $build->canStopBuild();
$can_pause = $build->canPauseBuild();
$can_resume = $build->canResumeBuild();
$can_abort = $build->canAbortBuild();
$list->addAction(
id(new PhabricatorActionView())
@ -470,11 +474,19 @@ final class HarbormasterBuildViewController
id(new PhabricatorActionView())
->setName(pht('Pause Build'))
->setIcon('fa-pause')
->setHref($this->getApplicationURI('/build/stop/'.$id.'/'))
->setDisabled(!$can_stop)
->setHref($this->getApplicationURI('/build/pause/'.$id.'/'))
->setDisabled(!$can_pause)
->setWorkflow(true));
}
$list->addAction(
id(new PhabricatorActionView())
->setName(pht('Abort Build'))
->setIcon('fa-exclamation-triangle')
->setHref($this->getApplicationURI('/build/abort/'.$id.'/'))
->setDisabled(!$can_abort)
->setWorkflow(true));
return $list;
}
@ -521,7 +533,7 @@ final class HarbormasterBuildViewController
$item = new PHUIStatusItemView();
if ($build->isStopping()) {
if ($build->isPausing()) {
$status_name = pht('Pausing');
$icon = PHUIStatusItemView::ICON_RIGHT;
$color = 'dark';

View file

@ -39,8 +39,8 @@ final class HarbormasterBuildableActionController
$issuable[] = $build;
}
break;
case HarbormasterBuildCommand::COMMAND_STOP:
if ($build->canStopBuild()) {
case HarbormasterBuildCommand::COMMAND_PAUSE:
if ($build->canPauseBuild()) {
$issuable[] = $build;
}
break;
@ -49,6 +49,11 @@ final class HarbormasterBuildableActionController
$issuable[] = $build;
}
break;
case HarbormasterBuildCommand::COMMAND_ABORT:
if ($build->canAbortBuild()) {
$issuable[] = $build;
}
break;
default:
return new Aphront400Response();
}
@ -94,20 +99,32 @@ final class HarbormasterBuildableActionController
'restart all builds?');
$submit = pht('Restart All Builds');
} else {
$title = pht('Unable to Restart Build');
$title = pht('Unable to Restart Builds');
$body = pht('No builds can be restarted.');
}
break;
case HarbormasterBuildCommand::COMMAND_STOP:
case HarbormasterBuildCommand::COMMAND_PAUSE:
if ($issuable) {
$title = pht('Really stop all builds?');
$title = pht('Really pause all builds?');
$body = pht(
'If you stop all build, work will halt once the current steps '.
'If you pause all builds, work will halt once the current steps '.
'complete. You can resume the builds later.');
$submit = pht('Stop All Builds');
$submit = pht('Pause All Builds');
} else {
$title = pht('Unable to Stop Build');
$body = pht('No builds can be stopped.');
$title = pht('Unable to Pause Builds');
$body = pht('No builds can be paused.');
}
break;
case HarbormasterBuildCommand::COMMAND_ABORT:
if ($issuable) {
$title = pht('Really abort all builds?');
$body = pht(
'If you abort all builds, work will halt immediately. Work '.
'will be discarded, and builds must be completely restarted.');
$submit = pht('Abort All Builds');
} else {
$title = pht('Unable to Abort Builds');
$body = pht('No builds can be aborted.');
}
break;
case HarbormasterBuildCommand::COMMAND_RESUME:
@ -116,7 +133,7 @@ final class HarbormasterBuildableActionController
$body = pht('Work will continue on all builds. Really resume?');
$submit = pht('Resume All Builds');
} else {
$title = pht('Unable to Resume Build');
$title = pht('Unable to Resume Builds');
$body = pht('No builds can be resumed.');
}
break;

View file

@ -84,7 +84,8 @@ final class HarbormasterBuildableViewController
$can_restart = false;
$can_resume = false;
$can_stop = false;
$can_pause = false;
$can_abort = false;
foreach ($buildable->getBuilds() as $build) {
if ($build->canRestartBuild()) {
@ -93,14 +94,18 @@ final class HarbormasterBuildableViewController
if ($build->canResumeBuild()) {
$can_resume = true;
}
if ($build->canStopBuild()) {
$can_stop = true;
if ($build->canPauseBuild()) {
$can_pause = true;
}
if ($build->canAbortBuild()) {
$can_abort = true;
}
}
$restart_uri = "buildable/{$id}/restart/";
$stop_uri = "buildable/{$id}/stop/";
$pause_uri = "buildable/{$id}/pause/";
$resume_uri = "buildable/{$id}/resume/";
$abort_uri = "buildable/{$id}/abort/";
$list->addAction(
id(new PhabricatorActionView())
@ -114,9 +119,9 @@ final class HarbormasterBuildableViewController
id(new PhabricatorActionView())
->setIcon('fa-pause')
->setName(pht('Pause All Builds'))
->setHref($this->getApplicationURI($stop_uri))
->setHref($this->getApplicationURI($pause_uri))
->setWorkflow(true)
->setDisabled(!$can_stop || !$can_edit));
->setDisabled(!$can_pause || !$can_edit));
$list->addAction(
id(new PhabricatorActionView())
@ -126,6 +131,14 @@ final class HarbormasterBuildableViewController
->setWorkflow(true)
->setDisabled(!$can_resume || !$can_edit));
$list->addAction(
id(new PhabricatorActionView())
->setIcon('fa-exclamation-triangle')
->setName(pht('Abort All Builds'))
->setHref($this->getApplicationURI($abort_uri))
->setWorkflow(true)
->setDisabled(!$can_abort || !$can_edit));
return $list;
}
@ -181,7 +194,7 @@ final class HarbormasterBuildableViewController
if ($build->isRestarting()) {
$item->addIcon('fa-repeat', pht('Restarting'));
} else if ($build->isStopping()) {
} else if ($build->isPausing()) {
$item->addIcon('fa-pause', pht('Pausing'));
} else if ($build->isResuming()) {
$item->addIcon('fa-play', pht('Resuming'));
@ -191,7 +204,8 @@ final class HarbormasterBuildableViewController
$restart_uri = "build/restart/{$build_id}/buildable/";
$resume_uri = "build/resume/{$build_id}/buildable/";
$stop_uri = "build/stop/{$build_id}/buildable/";
$pause_uri = "build/pause/{$build_id}/buildable/";
$abort_uri = "build/abort/{$build_id}/buildable/";
$item->addAction(
id(new PHUIListItemView())
@ -213,9 +227,9 @@ final class HarbormasterBuildableViewController
id(new PHUIListItemView())
->setIcon('fa-pause')
->setName(pht('Pause'))
->setHref($this->getApplicationURI($stop_uri))
->setHref($this->getApplicationURI($pause_uri))
->setWorkflow(true)
->setDisabled(!$build->canStopBuild()));
->setDisabled(!$build->canPauseBuild()));
}
$targets = $build->getBuildTargets();

View file

@ -71,12 +71,15 @@ final class HarbormasterBuildTransactionEditor
case HarbormasterBuildCommand::COMMAND_RESTART:
$issuable = $build->canRestartBuild();
break;
case HarbormasterBuildCommand::COMMAND_STOP:
$issuable = $build->canStopBuild();
case HarbormasterBuildCommand::COMMAND_PAUSE:
$issuable = $build->canPauseBuild();
break;
case HarbormasterBuildCommand::COMMAND_RESUME:
$issuable = $build->canResumeBuild();
break;
case HarbormasterBuildCommand::COMMAND_ABORT:
$issuable = $build->canAbortBuild();
break;
default:
throw new Exception(pht('Unknown command %s', $command));
}

View file

@ -98,6 +98,12 @@ final class HarbormasterBuildEngine extends Phobject {
}
private function updateBuild(HarbormasterBuild $build) {
if ($build->isAborting()) {
$this->releaseAllArtifacts($build);
$build->setBuildStatus(HarbormasterBuild::STATUS_ABORTED);
$build->save();
}
if (($build->getBuildStatus() == HarbormasterBuild::STATUS_PENDING) ||
($build->isRestarting())) {
$this->restartBuild($build);
@ -110,8 +116,8 @@ final class HarbormasterBuildEngine extends Phobject {
$build->save();
}
if ($build->isStopping() && !$build->isComplete()) {
$build->setBuildStatus(HarbormasterBuild::STATUS_STOPPED);
if ($build->isPausing() && !$build->isComplete()) {
$build->setBuildStatus(HarbormasterBuild::STATUS_PAUSED);
$build->save();
}

View file

@ -0,0 +1,50 @@
<?php
final class HarbormasterExecFuture
extends Future {
private $future;
private $stdout;
private $stderr;
public function setFuture(ExecFuture $future) {
$this->future = $future;
return $this;
}
public function getFuture() {
return $this->future;
}
public function setLogs(
HarbormasterBuildLog $stdout,
HarbormasterBuildLog $stderr) {
$this->stdout = $stdout;
$this->stderr = $stderr;
return $this;
}
public function isReady() {
$future = $this->getFuture();
$result = $future->isReady();
list($stdout, $stderr) = $future->read();
$future->discardBuffers();
if ($this->stdout) {
$this->stdout->append($stdout);
}
if ($this->stderr) {
$this->stderr->append($stderr);
}
return $result;
}
protected function getResult() {
return $this->getFuture()->getResult();
}
}

Some files were not shown because too many files have changed in this diff Show more