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:
commit
256fd47f2a
140 changed files with 4846 additions and 1962 deletions
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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};
|
10
resources/sql/autopatches/20150922.drydock.commands.1.sql
Normal file
10
resources/sql/autopatches/20150922.drydock.commands.1.sql
Normal 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};
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
|
||||
ADD resourcePHID VARBINARY(64);
|
|
@ -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;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
|
||||
DROP resourceID;
|
2
resources/sql/autopatches/20150923.drydock.taskid.1.sql
Normal file
2
resources/sql/autopatches/20150923.drydock.taskid.1.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_drydock.drydock_lease
|
||||
DROP taskID;
|
2
resources/sql/autopatches/20150924.drydock.disable.1.sql
Normal file
2
resources/sql/autopatches/20150924.drydock.disable.1.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_drydock.drydock_blueprint
|
||||
ADD isDisabled BOOL NOT NULL;
|
39
resources/sql/autopatches/20150924.drydock.status.1.sql
Normal file
39
resources/sql/autopatches/20150924.drydock.status.1.sql
Normal 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';
|
|
@ -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',
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ final class PhabricatorBadgesEditController
|
|||
}
|
||||
$is_new = false;
|
||||
} else {
|
||||
$this->requireApplicationCapability(
|
||||
PhabricatorBadgesCreateCapability::CAPABILITY);
|
||||
|
||||
$badge = PhabricatorBadgesBadge::initializeNewBadge($viewer);
|
||||
$is_new = true;
|
||||
}
|
||||
|
|
|
@ -109,7 +109,8 @@ final class PhabricatorBadgesViewController
|
|||
'default',
|
||||
$viewer);
|
||||
|
||||
$view->addSectionHeader(pht('Description'));
|
||||
$view->addSectionHeader(
|
||||
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
|
||||
$view->addTextContent($description);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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' =>
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -337,7 +337,8 @@ final class DiffusionBrowseFileController extends DiffusionBrowseController {
|
|||
|
||||
$corpus = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->appendChild($corpus);
|
||||
->appendChild($corpus)
|
||||
->setCollapsed(true);
|
||||
|
||||
return $corpus;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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/');
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
<?php
|
||||
|
||||
abstract class DrydockBlueprintCustomField
|
||||
extends PhabricatorCustomField {}
|
||||
extends PhabricatorCustomField {
|
||||
|
||||
abstract public function getBlueprintFieldValue();
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -29,6 +29,7 @@ final class DrydockBlueprintPHIDType extends PhabricatorPHIDType {
|
|||
$blueprint = $objects[$phid];
|
||||
$id = $blueprint->getID();
|
||||
|
||||
$handle->setName($blueprint->getBlueprintName());
|
||||
$handle->setURI("/drydock/blueprint/{$id}/");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}/");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
82
src/applications/drydock/query/DrydockCommandQuery.php
Normal file
82
src/applications/drydock/query/DrydockCommandQuery.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 )------------------------- */
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
69
src/applications/drydock/storage/DrydockCommand.php
Normal file
69
src/applications/drydock/storage/DrydockCommand.php
Normal 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.');
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
|
175
src/applications/drydock/storage/DrydockSlotLock.php
Normal file
175
src/applications/drydock/storage/DrydockSlotLock.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
107
src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
Normal file
107
src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
74
src/applications/drydock/worker/DrydockLeaseWorker.php
Normal file
74
src/applications/drydock/worker/DrydockLeaseWorker.php
Normal 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()'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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(),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
45
src/applications/drydock/worker/DrydockResourceWorker.php
Normal file
45
src/applications/drydock/worker/DrydockResourceWorker.php
Normal 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()'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
53
src/applications/drydock/worker/DrydockWorker.php
Normal file
53
src/applications/drydock/worker/DrydockWorker.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue