1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-07 20:38:32 +01:00

(stable) Promote 2015 Week 40

This commit is contained in:
epriestley 2015-10-03 05:09:52 -07:00
commit f760b3ec98
152 changed files with 5065 additions and 1830 deletions

1
bin/garbage Symbolic link
View file

@ -0,0 +1 @@
../scripts/setup/manage_garbage.php

View file

@ -151,7 +151,7 @@ return array(
'rsrc/css/phui/phui-two-column-view.css' => '39ecafb1',
'rsrc/css/phui/phui-workboard-view.css' => '6704d68d',
'rsrc/css/phui/phui-workpanel-view.css' => 'adec7699',
'rsrc/css/sprite-login.css' => '1ebb9bf9',
'rsrc/css/sprite-login.css' => '60e8560e',
'rsrc/css/sprite-main-header.css' => 'f07bbb87',
'rsrc/css/sprite-menu.css' => '9dd65b92',
'rsrc/css/sprite-projects.css' => 'e5ad842a',
@ -310,8 +310,8 @@ return array(
'rsrc/image/people/washington.png' => '40dd301c',
'rsrc/image/phrequent_active.png' => 'a466a8ed',
'rsrc/image/phrequent_inactive.png' => 'bfc15a69',
'rsrc/image/sprite-login-X2.png' => 'a4bf0a98',
'rsrc/image/sprite-login.png' => '5f4d0069',
'rsrc/image/sprite-login-X2.png' => 'e3991e37',
'rsrc/image/sprite-login.png' => '03d5af29',
'rsrc/image/sprite-main-header.png' => '3673af44',
'rsrc/image/sprite-menu-X2.png' => 'cfd8fca5',
'rsrc/image/sprite-menu.png' => 'd7a99faa',
@ -817,7 +817,7 @@ return array(
'releeph-request-differential-create-dialog' => '8d8b92cd',
'releeph-request-typeahead-css' => '667a48ae',
'setup-issue-css' => 'db7e9c40',
'sprite-login-css' => '1ebb9bf9',
'sprite-login-css' => '60e8560e',
'sprite-main-header-css' => 'f07bbb87',
'sprite-menu-css' => '9dd65b92',
'sprite-projects-css' => 'e5ad842a',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1,000 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -9,7 +9,7 @@
"login-Asana": {
"name": "login-Asana",
"rule": ".login-Asana",
"hash": "f8d322843355da1abce614983044c0f8"
"hash": "cfc35b62afb103c5d484a715071e3cc3"
},
"login-Bitbucket": {
"name": "login-Bitbucket",
@ -131,6 +131,6 @@
1,
2
],
"header": "\/**\n * @provides sprite-login-css\n * @generated\n *\/\n\n.sprite-login {\n background-image: url(\/rsrc\/image\/sprite-login.png);\n background-repeat: no-repeat;\n}\n\n@media\nonly screen and (min-device-pixel-ratio: 1.5),\nonly screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 1.5dppx) {\n .sprite-login {\n background-image: url(\/rsrc\/image\/sprite-login-X2.png);\n background-size: {X}px {Y}px;\n }\n}\n",
"header": "/**\n * @provides sprite-login-css\n * @generated\n */\n\n.sprite-login {\n background-image: url(/rsrc/image/sprite-login.png);\n background-repeat: no-repeat;\n}\n\n@media\nonly screen and (min-device-pixel-ratio: 1.5),\nonly screen and (-webkit-min-device-pixel-ratio: 1.5),\nonly screen and (min-resolution: 1.5dppx) {\n .sprite-login {\n background-image: url(/rsrc/image/sprite-login-X2.png);\n background-size: {X}px {Y}px;\n }\n}\n",
"type": "standard"
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_drydock.drydock_resource
ADD until INT UNSIGNED;

View file

@ -0,0 +1,25 @@
TRUNCATE {$NAMESPACE}_drydock.drydock_log;
ALTER TABLE {$NAMESPACE}_drydock.drydock_log
DROP resourceID;
ALTER TABLE {$NAMESPACE}_drydock.drydock_log
DROP leaseID;
ALTER TABLE {$NAMESPACE}_drydock.drydock_log
DROP message;
ALTER TABLE {$NAMESPACE}_drydock.drydock_log
ADD blueprintPHID VARBINARY(64);
ALTER TABLE {$NAMESPACE}_drydock.drydock_log
ADD resourcePHID VARBINARY(64);
ALTER TABLE {$NAMESPACE}_drydock.drydock_log
ADD leasePHID VARBINARY(64);
ALTER TABLE {$NAMESPACE}_drydock.drydock_log
ADD type VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};
ALTER TABLE {$NAMESPACE}_drydock.drydock_log
ADD data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};

View file

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

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_dashboard.dashboard
SET status = 'active' WHERE status = 'open';

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build
ADD buildParameters LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL;
UPDATE {$NAMESPACE}_harbormaster.harbormaster_build
SET buildParameters = '{}' WHERE buildParameters = '';

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('manage garbage colletors'));
$args->setSynopsis(<<<EOSYNOPSIS
**garbage** __command__ [__options__]
Manage garbage collectors.
EOSYNOPSIS
);
$args->parseStandardArguments();
$workflows = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorGarbageCollectorManagementWorkflow')
->execute();
$workflows[] = new PhutilHelpArgumentWorkflow();
$args->parseWorkflows($workflows);

View file

@ -796,7 +796,6 @@ phutil_register_library_map(array(
'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php',
'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',
@ -830,25 +829,33 @@ phutil_register_library_map(array(
'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php',
'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php',
'DrydockLease' => 'applications/drydock/storage/DrydockLease.php',
'DrydockLeaseAcquiredLogType' => 'applications/drydock/logtype/DrydockLeaseAcquiredLogType.php',
'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php',
'DrydockLeaseActivationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php',
'DrydockLeaseActivationYieldLogType' => 'applications/drydock/logtype/DrydockLeaseActivationYieldLogType.php',
'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php',
'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php',
'DrydockLeaseDestroyWorker' => 'applications/drydock/worker/DrydockLeaseDestroyWorker.php',
'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php',
'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php',
'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php',
'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php',
'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php',
'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php',
'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php',
'DrydockLeaseReleasedLogType' => 'applications/drydock/logtype/DrydockLeaseReleasedLogType.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',
'DrydockLeaseWorker' => 'applications/drydock/worker/DrydockLeaseWorker.php',
'DrydockLeaseWaitingForResourcesLogType' => 'applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php',
'DrydockLog' => 'applications/drydock/storage/DrydockLog.php',
'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php',
'DrydockLogGarbageCollector' => 'applications/drydock/garbagecollector/DrydockLogGarbageCollector.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',
'DrydockLogType' => 'applications/drydock/logtype/DrydockLogType.php',
'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php',
'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php',
'DrydockManagementReleaseLeaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php',
@ -858,9 +865,10 @@ phutil_register_library_map(array(
'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php',
'DrydockResource' => 'applications/drydock/storage/DrydockResource.php',
'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php',
'DrydockResourceActivationYieldLogType' => 'applications/drydock/logtype/DrydockResourceActivationYieldLogType.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',
@ -870,11 +878,11 @@ phutil_register_library_map(array(
'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',
'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php',
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php',
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
@ -964,6 +972,7 @@ phutil_register_library_map(array(
'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php',
'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
'HarbormasterBuildRequest' => 'applications/harbormaster/engine/HarbormasterBuildRequest.php',
'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php',
'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php',
@ -1821,6 +1830,7 @@ phutil_register_library_map(array(
'PhabricatorConfigAllController' => 'applications/config/controller/PhabricatorConfigAllController.php',
'PhabricatorConfigApplication' => 'applications/config/application/PhabricatorConfigApplication.php',
'PhabricatorConfigCacheController' => 'applications/config/controller/PhabricatorConfigCacheController.php',
'PhabricatorConfigCollectorsModule' => 'applications/config/module/PhabricatorConfigCollectorsModule.php',
'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php',
'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php',
'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php',
@ -2187,7 +2197,9 @@ phutil_register_library_map(array(
'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php',
'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php',
'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php',
'PhabricatorGarbageCollectorConfigOptions' => 'applications/config/option/PhabricatorGarbageCollectorConfigOptions.php',
'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php',
'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php',
'PhabricatorGarbageCollectorManagementWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php',
'PhabricatorGestureUIExample' => 'applications/uiexample/examples/PhabricatorGestureUIExample.php',
'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php',
'PhabricatorGitHubAuthProvider' => 'applications/auth/provider/PhabricatorGitHubAuthProvider.php',
@ -4520,7 +4532,6 @@ phutil_register_library_map(array(
'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'DoorkeeperTagView' => 'AphrontView',
'DoorkeeperTagsController' => 'PhabricatorController',
'DrydockAllocatorWorker' => 'DrydockWorker',
'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
'DrydockBlueprint' => array(
@ -4568,28 +4579,36 @@ phutil_register_library_map(array(
'DrydockDAO',
'PhabricatorPolicyInterface',
),
'DrydockLeaseAcquiredLogType' => 'DrydockLogType',
'DrydockLeaseActivatedLogType' => 'DrydockLogType',
'DrydockLeaseActivationFailureLogType' => 'DrydockLogType',
'DrydockLeaseActivationYieldLogType' => 'DrydockLogType',
'DrydockLeaseController' => 'DrydockController',
'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockLeaseDestroyWorker' => 'DrydockWorker',
'DrydockLeaseDestroyedLogType' => 'DrydockLogType',
'DrydockLeaseListController' => 'DrydockLeaseController',
'DrydockLeaseListView' => 'AphrontView',
'DrydockLeasePHIDType' => 'PhabricatorPHIDType',
'DrydockLeaseQuery' => 'DrydockQuery',
'DrydockLeaseQueuedLogType' => 'DrydockLogType',
'DrydockLeaseReleaseController' => 'DrydockLeaseController',
'DrydockLeaseReleasedLogType' => 'DrydockLogType',
'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockLeaseStatus' => 'DrydockConstants',
'DrydockLeaseUpdateWorker' => 'DrydockWorker',
'DrydockLeaseViewController' => 'DrydockLeaseController',
'DrydockLeaseWorker' => 'DrydockWorker',
'DrydockLeaseWaitingForResourcesLogType' => 'DrydockLogType',
'DrydockLog' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
),
'DrydockLogController' => 'DrydockController',
'DrydockLogGarbageCollector' => 'PhabricatorGarbageCollector',
'DrydockLogListController' => 'DrydockLogController',
'DrydockLogListView' => 'AphrontView',
'DrydockLogQuery' => 'DrydockQuery',
'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockLogType' => 'Phobject',
'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow',
'DrydockManagementReleaseLeaseWorkflow' => 'DrydockManagementWorkflow',
@ -4602,9 +4621,10 @@ phutil_register_library_map(array(
'DrydockDAO',
'PhabricatorPolicyInterface',
),
'DrydockResourceActivationFailureLogType' => 'DrydockLogType',
'DrydockResourceActivationYieldLogType' => 'DrydockLogType',
'DrydockResourceController' => 'DrydockController',
'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockResourceDestroyWorker' => 'DrydockWorker',
'DrydockResourceListController' => 'DrydockResourceController',
'DrydockResourceListView' => 'AphrontView',
'DrydockResourcePHIDType' => 'PhabricatorPHIDType',
@ -4614,11 +4634,11 @@ phutil_register_library_map(array(
'DrydockResourceStatus' => 'DrydockConstants',
'DrydockResourceUpdateWorker' => 'DrydockWorker',
'DrydockResourceViewController' => 'DrydockResourceController',
'DrydockResourceWorker' => 'DrydockWorker',
'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface',
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
'DrydockSlotLock' => 'DrydockDAO',
'DrydockSlotLockException' => 'Exception',
'DrydockSlotLockFailureLogType' => 'DrydockLogType',
'DrydockWebrootInterface' => 'DrydockInterface',
'DrydockWorker' => 'PhabricatorWorker',
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
@ -4740,6 +4760,7 @@ phutil_register_library_map(array(
'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction',
'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildRequest' => 'Phobject',
'HarbormasterBuildStep' => array(
'HarbormasterDAO',
'PhabricatorApplicationTransactionInterface',
@ -5745,6 +5766,7 @@ phutil_register_library_map(array(
'PhabricatorConfigAllController' => 'PhabricatorConfigController',
'PhabricatorConfigApplication' => 'PhabricatorApplication',
'PhabricatorConfigCacheController' => 'PhabricatorConfigController',
'PhabricatorConfigCollectorsModule' => 'PhabricatorConfigModule',
'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema',
'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType',
'PhabricatorConfigController' => 'PhabricatorController',
@ -6177,7 +6199,9 @@ phutil_register_library_map(array(
'PhabricatorFundApplication' => 'PhabricatorApplication',
'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorGarbageCollector' => 'Phobject',
'PhabricatorGarbageCollectorConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow',
'PhabricatorGarbageCollectorManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorGestureUIExample' => 'PhabricatorUIExample',
'PhabricatorGitGraphStream' => 'PhabricatorRepositoryGraphStream',
'PhabricatorGitHubAuthProvider' => 'PhabricatorOAuth2AuthProvider',

View file

@ -3,7 +3,17 @@
final class PhabricatorAuthSessionGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
const COLLECTORCONST = 'auth.sessions';
public function getCollectorName() {
return pht('Authentication Sessions');
}
public function hasAutomaticPolicy() {
return true;
}
protected function collectGarbage() {
$session_table = new PhabricatorAuthSession();
$conn_w = $session_table->establishConnection('w');

View file

@ -3,7 +3,17 @@
final class PhabricatorAuthTemporaryTokenGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
const COLLECTORCONST = 'auth.tokens';
public function getCollectorName() {
return pht('Authentication Tokens');
}
public function hasAutomaticPolicy() {
return true;
}
protected function collectGarbage() {
$session_table = new PhabricatorAuthTemporaryToken();
$conn_w = $session_table->establishConnection('w');

View file

@ -3,13 +3,17 @@
final class PhabricatorCacheGeneralGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
$key = 'gcdaemon.ttl.general-cache';
$ttl = PhabricatorEnv::getEnvConfig($key);
if ($ttl <= 0) {
return false;
}
const COLLECTORCONST = 'cache.general';
public function getCollectorName() {
return pht('General Cache');
}
public function getDefaultRetentionPolicy() {
return phutil_units('30 days in seconds');
}
protected function collectGarbage() {
$cache = new PhabricatorKeyValueDatabaseCache();
$conn_w = $cache->establishConnection('w');
@ -18,7 +22,7 @@ final class PhabricatorCacheGeneralGarbageCollector
'DELETE FROM %T WHERE cacheCreated < %d
ORDER BY cacheCreated ASC LIMIT 100',
$cache->getTableName(),
time() - $ttl);
$this->getGarbageEpoch());
return ($conn_w->getAffectedRows() == 100);
}

View file

@ -3,13 +3,17 @@
final class PhabricatorCacheMarkupGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
$key = 'gcdaemon.ttl.markup-cache';
$ttl = PhabricatorEnv::getEnvConfig($key);
if ($ttl <= 0) {
return false;
}
const COLLECTORCONST = 'cache.markup';
public function getCollectorName() {
return pht('Markup Cache');
}
public function getDefaultRetentionPolicy() {
return phutil_units('30 days in seconds');
}
protected function collectGarbage() {
$table = new PhabricatorMarkupCache();
$conn_w = $table->establishConnection('w');
@ -17,7 +21,7 @@ final class PhabricatorCacheMarkupGarbageCollector
$conn_w,
'DELETE FROM %T WHERE dateCreated < %d LIMIT 100',
$table->getTableName(),
time() - $ttl);
$this->getGarbageEpoch());
return ($conn_w->getAffectedRows() == 100);
}

View file

@ -3,7 +3,17 @@
final class PhabricatorCacheTTLGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
const COLLECTORCONST = 'cache.general.ttl';
public function getCollectorName() {
return pht('General Cache (TTL)');
}
public function hasAutomaticPolicy() {
return true;
}
protected function collectGarbage() {
$cache = new PhabricatorKeyValueDatabaseCache();
$conn_w = $cache->establishConnection('w');
@ -12,7 +22,7 @@ final class PhabricatorCacheTTLGarbageCollector
'DELETE FROM %T WHERE cacheExpires < %d
ORDER BY cacheExpires ASC LIMIT 100',
$cache->getTableName(),
time());
PhabricatorTime::getNow());
return ($conn_w->getAffectedRows() == 100);
}

View file

@ -3,21 +3,26 @@
final class ConduitConnectionGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
$key = 'gcdaemon.ttl.conduit-logs';
$ttl = PhabricatorEnv::getEnvConfig($key);
if ($ttl <= 0) {
return false;
}
const COLLECTORCONST = 'conduit.connections';
public function getCollectorName() {
return pht('Conduit Connections');
}
public function getDefaultRetentionPolicy() {
return phutil_units('180 days in seconds');
}
protected function collectGarbage() {
$table = new PhabricatorConduitConnectionLog();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE dateCreated < %d
ORDER BY dateCreated ASC LIMIT 100',
$table->getTableName(),
time() - $ttl);
$this->getGarbageEpoch());
return ($conn_w->getAffectedRows() == 100);
}

View file

@ -3,21 +3,26 @@
final class ConduitLogGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
$key = 'gcdaemon.ttl.conduit-logs';
$ttl = PhabricatorEnv::getEnvConfig($key);
if ($ttl <= 0) {
return false;
}
const COLLECTORCONST = 'conduit.logs';
public function getCollectorName() {
return pht('Conduit Logs');
}
public function getDefaultRetentionPolicy() {
return phutil_units('180 days in seconds');
}
protected function collectGarbage() {
$table = new PhabricatorConduitMethodCallLog();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE dateCreated < %d
ORDER BY dateCreated ASC LIMIT 100',
$table->getTableName(),
time() - $ttl);
$this->getGarbageEpoch());
return ($conn_w->getAffectedRows() == 100);
}

View file

@ -3,9 +3,20 @@
final class ConduitTokenGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
const COLLECTORCONST = 'conduit.tokens';
public function getCollectorName() {
return pht('Conduit Tokens');
}
public function hasAutomaticPolicy() {
return true;
}
protected function collectGarbage() {
$table = new PhabricatorConduitToken();
$conn_w = $table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE expires <= %d

View file

@ -176,6 +176,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'Inbound mail addresses are now configured for each application '.
'in the Applications tool.');
$gc_reason = pht(
'Garbage collectors are now configured with "%s".',
'bin/garbage set-policy');
$ancient_config += array(
'phid.external-loaders' =>
pht(
@ -280,6 +284,14 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'auth.login-message' => pht(
'This configuration option has been replaced with a modular '.
'handler. See T9346.'),
'gcdaemon.ttl.herald-transcripts' => $gc_reason,
'gcdaemon.ttl.daemon-logs' => $gc_reason,
'gcdaemon.ttl.differential-parse-cache' => $gc_reason,
'gcdaemon.ttl.markup-cache' => $gc_reason,
'gcdaemon.ttl.task-archive' => $gc_reason,
'gcdaemon.ttl.general-cache' => $gc_reason,
'gcdaemon.ttl.conduit-logs' => $gc_reason,
);
return $ancient_config;

View file

@ -0,0 +1,79 @@
<?php
final class PhabricatorConfigCollectorsModule extends PhabricatorConfigModule {
public function getModuleKey() {
return 'collectors';
}
public function getModuleName() {
return pht('Garbage Collectors');
}
public function renderModuleStatus(AphrontRequest $request) {
$viewer = $request->getViewer();
$collectors = PhabricatorGarbageCollector::getAllCollectors();
$collectors = msort($collectors, 'getCollectorConstant');
$rows = array();
$rowc = array();
foreach ($collectors as $key => $collector) {
$class = null;
if ($collector->hasAutomaticPolicy()) {
$policy_view = phutil_tag('em', array(), pht('Automatic'));
} else {
$policy = $collector->getRetentionPolicy();
if ($policy === null) {
$policy_view = pht('Indefinite');
} else {
$days = ceil($policy / phutil_units('1 day in seconds'));
$policy_view = pht(
'%s Day(s)',
new PhutilNumber($days));
}
$default = $collector->getDefaultRetentionPolicy();
if ($policy !== $default) {
$class = 'highlighted';
$policy_view = phutil_tag('strong', array(), $policy_view);
}
}
$rowc[] = $class;
$rows[] = array(
$collector->getCollectorConstant(),
$collector->getCollectorName(),
$policy_view,
);
}
$table = id(new AphrontTableView($rows))
->setRowClasses($rowc)
->setHeaders(
array(
pht('Constant'),
pht('Name'),
pht('Retention Policy'),
))
->setColumnClasses(
array(
null,
'pri wide',
null,
));
$header = id(new PHUIHeaderView())
->setHeader(pht('Garbage Collectors'))
->setSubheader(
pht(
'Collectors with custom policies are highlighted. Use '.
'%s to change retention policies.',
phutil_tag('tt', array(), 'bin/garbage set-policy')));
return id(new PHUIObjectBoxView())
->setHeader($header)
->setTable($table);
}
}

View file

@ -1,70 +0,0 @@
<?php
final class PhabricatorGarbageCollectorConfigOptions
extends PhabricatorApplicationConfigOptions {
public function getName() {
return pht('Garbage Collector');
}
public function getDescription() {
return pht('Configure the GC for old logs, caches, etc.');
}
public function getFontIcon() {
return 'fa-trash-o';
}
public function getGroup() {
return 'core';
}
public function getOptions() {
$options = array(
'gcdaemon.ttl.herald-transcripts' => array(
30,
pht('Number of seconds to retain Herald transcripts for.'),
),
'gcdaemon.ttl.daemon-logs' => array(
7,
pht('Number of seconds to retain Daemon logs for.'),
),
'gcdaemon.ttl.differential-parse-cache' => array(
14,
pht('Number of seconds to retain Differential parse caches for.'),
),
'gcdaemon.ttl.markup-cache' => array(
30,
pht('Number of seconds to retain Markup cache entries for.'),
),
'gcdaemon.ttl.task-archive' => array(
14,
pht('Number of seconds to retain archived background tasks for.'),
),
'gcdaemon.ttl.general-cache' => array(
30,
pht('Number of seconds to retain general cache entries for.'),
),
'gcdaemon.ttl.conduit-logs' => array(
180,
pht('Number of seconds to retain Conduit call logs for.'),
),
);
$result = array();
foreach ($options as $key => $spec) {
list($default_days, $description) = $spec;
$result[] = $this
->newOption($key, 'int', $default_days * (24 * 60 * 60))
->setDescription($description)
->addExample((7 * 24 * 60 * 60), pht('Retain for 1 week'))
->addExample((14 * 24 * 60 * 60), pht('Retain for 2 weeks'))
->addExample((30 * 24 * 60 * 60), pht('Retain for 30 days'))
->addExample((60 * 24 * 60 * 60), pht('Retain for 60 days'))
->addExample(0, pht('Retain indefinitely'));
}
return $result;
}
}

View file

@ -80,6 +80,17 @@ final class PhabricatorPHDConfigOptions
'and the daemons. Primarily, this is a way to suppress the '.
'"Daemons and Web Have Different Config" setup issue on a per '.
'config key basis.')),
$this->newOption('phd.garbage-collection', 'wild', array())
->setLocked(true)
->setLockedMessage(
pht(
'This option can not be edited from the web UI. Use %s to adjust '.
'garbage collector policies.',
phutil_tag('tt', array(), 'bin/garbage set-policy')))
->setSummary(pht('Retention policies for garbage collection.'))
->setDescription(
pht(
'Customizes retention policies for garbage collectors.')),
);
}

View file

@ -3,12 +3,17 @@
final class PhabricatorDaemonLogEventGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
$ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.daemon-logs');
if ($ttl <= 0) {
return false;
}
const COLLECTORCONST = 'daemon.processes';
public function getCollectorName() {
return pht('Daemon Processes');
}
public function getDefaultRetentionPolicy() {
return phutil_units('7 days in seconds');
}
protected function collectGarbage() {
$table = new PhabricatorDaemonLogEvent();
$conn_w = $table->establishConnection('w');
@ -16,7 +21,7 @@ final class PhabricatorDaemonLogEventGarbageCollector
$conn_w,
'DELETE FROM %T WHERE epoch < %d LIMIT 100',
$table->getTableName(),
time() - $ttl);
$this->getGarbageEpoch());
return ($conn_w->getAffectedRows() == 100);
}

View file

@ -3,12 +3,17 @@
final class PhabricatorDaemonLogGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
$ttl = PhabricatorEnv::getEnvConfig('gcdaemon.ttl.daemon-logs');
if ($ttl <= 0) {
return false;
}
const COLLECTORCONST = 'daemon.logs';
public function getCollectorName() {
return pht('Daemon Logs');
}
public function getDefaultRetentionPolicy() {
return phutil_units('7 days in seconds');
}
protected function collectGarbage() {
$table = new PhabricatorDaemonLog();
$conn_w = $table->establishConnection('w');
@ -16,7 +21,7 @@ final class PhabricatorDaemonLogGarbageCollector
$conn_w,
'DELETE FROM %T WHERE dateCreated < %d AND status != %s LIMIT 100',
$table->getTableName(),
time() - $ttl,
$this->getGarbageEpoch(),
PhabricatorDaemonLog::STATUS_RUNNING);
return ($conn_w->getAffectedRows() == 100);

View file

@ -3,21 +3,25 @@
final class PhabricatorDaemonTaskGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
$key = 'gcdaemon.ttl.task-archive';
$ttl = PhabricatorEnv::getEnvConfig($key);
if ($ttl <= 0) {
return false;
}
const COLLECTORCONST = 'worker.tasks';
public function getCollectorName() {
return pht('Archived Tasks');
}
public function getDefaultRetentionPolicy() {
return phutil_units('14 days in seconds');
}
protected function collectGarbage() {
$table = new PhabricatorWorkerArchiveTask();
$data_table = new PhabricatorWorkerTaskData();
$conn_w = $table->establishConnection('w');
$tasks = id(new PhabricatorWorkerArchiveTaskQuery())
->withDateCreatedBefore(time() - $ttl)
->withDateCreatedBefore($this->getGarbageEpoch())
->setLimit(100)
->execute();
if (!$tasks) {
return false;
}

View file

@ -133,7 +133,7 @@ final class PhabricatorDifferentialConfigOptions
'to affect existing revisions. For instructions, see '.
'**[[ %s | Managing Caches ]]** in the documentation.',
$caches_href))
->addExample("/config\.h$/\n#/autobuilt/#", pht('Valid Setting')),
->addExample("/config\.h$/\n#(^|/)autobuilt/#", pht('Valid Setting')),
$this->newOption('differential.sticky-accept', 'bool', true)
->setBoolOptions(
array(

View file

@ -3,13 +3,17 @@
final class DifferentialParseCacheGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
$key = 'gcdaemon.ttl.differential-parse-cache';
$ttl = PhabricatorEnv::getEnvConfig($key);
if ($ttl <= 0) {
return false;
}
const COLLECTORCONST = 'differential.parse';
public function getCollectorName() {
return pht('Differential Parse Cache');
}
public function getDefaultRetentionPolicy() {
return phutil_units('14 days in seconds');
}
protected function collectGarbage() {
$table = new DifferentialChangeset();
$conn_w = $table->establishConnection('w');
@ -17,7 +21,7 @@ final class DifferentialParseCacheGarbageCollector
$conn_w,
'DELETE FROM %T WHERE dateCreated < %d LIMIT 100',
DifferentialChangeset::TABLE_CACHE,
time() - $ttl);
$this->getGarbageEpoch());
return ($conn_w->getAffectedRows() == 100);
}

View file

@ -10,7 +10,7 @@ final class HeraldDifferentialRevisionAdapter
protected $changesets;
private $haveHunks;
private $buildPlanPHIDs = array();
private $buildRequests = array();
public function getAdapterApplicationClass() {
return 'PhabricatorDifferentialApplication';
@ -139,12 +139,13 @@ final class HeraldDifferentialRevisionAdapter
return $this->getObject()->getPHID();
}
public function getQueuedHarbormasterBuildPlanPHIDs() {
return $this->buildPlanPHIDs;
public function getQueuedHarbormasterBuildRequests() {
return $this->buildRequests;
}
public function queueHarbormasterBuildPlanPHID($phid) {
$this->buildPlanPHIDs[] = $phid;
public function queueHarbormasterBuildRequest(
HarbormasterBuildRequest $request) {
$this->buildRequests[] = $request;
}
}

View file

@ -446,6 +446,13 @@ final class DifferentialDiff
$results['repository.phid'] = $repo->getPHID();
$results['repository.vcs'] = $repo->getVersionControlSystem();
$results['repository.uri'] = $repo->getPublicCloneURI();
// TODO: We're just hoping to get lucky. Instead, `arc` should store
// where it sent changes and we should only provide staging details
// if we reasonably believe they are accurate.
$staging_ref = 'refs/tags/phabricator/diff/'.$this->getID();
$results['repository.staging.uri'] = $repo->getStagingURI();
$results['repository.staging.ref'] = $staging_ref;
}
}
@ -466,6 +473,10 @@ final class DifferentialDiff
pht('The version control system, either "svn", "hg" or "git".'),
'repository.uri' =>
pht('The URI to clone or checkout the repository from.'),
'repository.staging.uri' =>
pht('The URI of the staging repository.'),
'repository.staging.ref' =>
pht('The ref name for this change in the staging repository.'),
);
}

View file

@ -7,6 +7,12 @@ final class DiffusionCommitController extends DiffusionController {
private $auditAuthorityPHIDs;
private $highlightedAudits;
private $commitParents;
private $commitRefs;
private $commitMerges;
private $commitErrors;
private $commitExists;
public function shouldAllowPublic() {
return true;
}
@ -17,6 +23,7 @@ final class DiffusionCommitController extends DiffusionController {
protected function processDiffusionRequest(AphrontRequest $request) {
$user = $request->getUser();
// This controller doesn't use blob/path stuff, just pass the dictionary
// in directly instead of using the AphrontRequest parsing mechanism.
$data = $request->getURIMap();
@ -45,10 +52,7 @@ final class DiffusionCommitController extends DiffusionController {
));
if (!$commit) {
$exists = $this->callConduitWithDiffusionRequest(
'diffusion.existsquery',
array('commit' => $drequest->getCommit()));
if (!$exists) {
if (!$this->getCommitExists()) {
return new Aphront404Response();
}
@ -95,18 +99,6 @@ final class DiffusionCommitController extends DiffusionController {
require_celerity_resource('phabricator-remarkup-css');
$parents = $this->callConduitWithDiffusionRequest(
'diffusion.commitparentsquery',
array('commit' => $drequest->getCommit()));
if ($parents) {
$parents = id(new DiffusionCommitQuery())
->setViewer($user)
->withRepository($repository)
->withIdentifiers($parents)
->execute();
}
$headsup_view = id(new PHUIHeaderView())
->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')));
@ -115,7 +107,6 @@ final class DiffusionCommitController extends DiffusionController {
$commit_properties = $this->loadCommitProperties(
$commit,
$commit_data,
$parents,
$audit_requests);
$property_list = id(new PHUIPropertyListView())
->setHasKeyboardShortcuts(true)
@ -148,8 +139,10 @@ final class DiffusionCommitController extends DiffusionController {
$message));
$headsup_view->setTall(true);
$object_box = id(new PHUIObjectBoxView())
->setHeader($headsup_view)
->setFormErrors($this->getCommitErrors())
->addPropertyList($property_list)
->addPropertyList($detail_list);
@ -216,6 +209,10 @@ final class DiffusionCommitController extends DiffusionController {
'This commit is enormous, and affects more than %d files. '.
'Changes are not shown.',
$hard_limit));
} else if (!$this->getCommitExists()) {
$content[] = $this->renderStatusMessage(
pht('Commit No Longer Exists'),
pht('This commit no longer exists in the repository.'));
} else {
$show_changesets = true;
@ -382,10 +379,8 @@ final class DiffusionCommitController extends DiffusionController {
private function loadCommitProperties(
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data,
array $parents,
array $audit_requests) {
assert_instances_of($parents, 'PhabricatorRepositoryCommit');
$viewer = $this->getRequest()->getUser();
$commit_phid = $commit->getPHID();
$drequest = $this->getDiffusionRequest();
@ -423,11 +418,6 @@ final class DiffusionCommitController extends DiffusionController {
if ($data->getCommitDetail('committerPHID')) {
$phids[] = $data->getCommitDetail('committerPHID');
}
if ($parents) {
foreach ($parents as $parent) {
$phids[] = $parent->getPHID();
}
}
// NOTE: We should never normally have more than a single push log, but
// it can occur naturally if a commit is pushed, then the branch it was
@ -564,39 +554,47 @@ final class DiffusionCommitController extends DiffusionController {
$props['Differential Revision'] = $handles[$revision_phid]->renderLink();
}
$parents = $this->getCommitParents();
if ($parents) {
$parent_links = array();
foreach ($parents as $parent) {
$parent_links[] = $handles[$parent->getPHID()]->renderLink();
}
$props['Parents'] = phutil_implode_html(" \xC2\xB7 ", $parent_links);
$props['Parents'] = $viewer->renderHandleList(mpull($parents, 'getPHID'));
}
$props['Branches'] = phutil_tag(
'span',
array(
'id' => 'commit-branches',
),
pht('Unknown'));
$props['Tags'] = phutil_tag(
'span',
array(
'id' => 'commit-tags',
),
pht('Unknown'));
if ($this->getCommitExists()) {
$props['Branches'] = phutil_tag(
'span',
array(
'id' => 'commit-branches',
),
pht('Unknown'));
$props['Tags'] = phutil_tag(
'span',
array(
'id' => 'commit-tags',
),
pht('Unknown'));
$callsign = $repository->getCallsign();
$root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier();
Javelin::initBehavior(
'diffusion-commit-branches',
array(
$root.'/branches/' => 'commit-branches',
$root.'/tags/' => 'commit-tags',
));
$callsign = $repository->getCallsign();
$root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier();
Javelin::initBehavior(
'diffusion-commit-branches',
array(
$root.'/branches/' => 'commit-branches',
$root.'/tags/' => 'commit-tags',
));
}
$refs = $this->buildRefs($drequest);
$refs = $this->getCommitRefs();
if ($refs) {
$props['References'] = $refs;
$ref_links = array();
foreach ($refs as $ref_data) {
$ref_links[] = phutil_tag(
'a',
array(
'href' => $ref_data['href'],
),
$ref_data['ref']);
}
$props['References'] = phutil_implode_html(', ', $ref_links);
}
if ($reverts_phids) {
@ -860,26 +858,12 @@ final class DiffusionCommitController extends DiffusionController {
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
// These aren't supported under SVN.
return null;
}
$limit = 50;
$merges = $this->callConduitWithDiffusionRequest(
'diffusion.mergedcommitsquery',
array(
'commit' => $drequest->getCommit(),
'limit' => $limit + 1,
));
$merges = $this->getCommitMerges();
if (!$merges) {
return null;
}
$merges = DiffusionPathChange::newFromConduit($merges);
$limit = $this->getMergeDisplayLimit();
$caption = null;
if (count($merges) > $limit) {
@ -961,27 +945,6 @@ final class DiffusionCommitController extends DiffusionController {
return $actions;
}
private function buildRefs(DiffusionRequest $request) {
// this is git-only, so save a conduit round trip and just get out of
// here if the repository isn't git
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$repository = $request->getRepository();
if ($repository->getVersionControlSystem() != $type_git) {
return null;
}
$results = $this->callConduitWithDiffusionRequest(
'diffusion.refsquery',
array('commit' => $request->getCommit()));
$ref_links = array();
foreach ($results as $ref_data) {
$ref_links[] = phutil_tag('a',
array('href' => $ref_data['href']),
$ref_data['ref']);
}
return phutil_implode_html(', ', $ref_links);
}
private function buildRawDiffResponse(DiffusionRequest $drequest) {
$raw_diff = $this->callConduitWithDiffusionRequest(
'diffusion.rawdiffquery',
@ -1137,4 +1100,140 @@ final class DiffusionCommitController extends DiffusionController {
return $toc_view;
}
private function loadCommitState() {
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$commit = $drequest->getCommit();
// TODO: We could use futures here and resolve these calls in parallel.
$exceptions = array();
try {
$parent_refs = $this->callConduitWithDiffusionRequest(
'diffusion.commitparentsquery',
array(
'commit' => $commit,
));
if ($parent_refs) {
$parents = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withRepository($repository)
->withIdentifiers($parent_refs)
->execute();
} else {
$parents = array();
}
$this->commitParents = $parents;
} catch (Exception $ex) {
$this->commitParents = false;
$exceptions[] = $ex;
}
$merge_limit = $this->getMergeDisplayLimit();
try {
$merges = $this->callConduitWithDiffusionRequest(
'diffusion.mergedcommitsquery',
array(
'commit' => $commit,
'limit' => $merge_limit + 1,
));
$this->commitMerges = DiffusionPathChange::newFromConduit($merges);
} catch (Exception $ex) {
$this->commitMerges = false;
$exceptions[] = $ex;
}
try {
if ($repository->isGit()) {
$refs = $this->callConduitWithDiffusionRequest(
'diffusion.refsquery',
array(
'commit' => $commit,
));
} else {
$refs = array();
}
$this->commitRefs = $refs;
} catch (Exception $ex) {
$this->commitRefs = false;
$exceptions[] = $ex;
}
if ($exceptions) {
$exists = $this->callConduitWithDiffusionRequest(
'diffusion.existsquery',
array(
'commit' => $commit,
));
if ($exists) {
$this->commitExists = true;
foreach ($exceptions as $exception) {
$this->commitErrors[] = $exception->getMessage();
}
} else {
$this->commitExists = false;
$this->commitErrors[] = pht(
'This commit no longer exists in the repository. It may have '.
'been part of a branch which was deleted.');
}
} else {
$this->commitExists = true;
$this->commitErrors = array();
}
}
private function getMergeDisplayLimit() {
return 50;
}
private function getCommitExists() {
if ($this->commitExists === null) {
$this->loadCommitState();
}
return $this->commitExists;
}
private function getCommitParents() {
if ($this->commitParents === null) {
$this->loadCommitState();
}
return $this->commitParents;
}
private function getCommitRefs() {
if ($this->commitRefs === null) {
$this->loadCommitState();
}
return $this->commitRefs;
}
private function getCommitMerges() {
if ($this->commitMerges === null) {
$this->loadCommitState();
}
return $this->commitMerges;
}
private function getCommitErrors() {
if ($this->commitErrors === null) {
$this->loadCommitState();
}
return $this->commitErrors;
}
}

View file

@ -17,7 +17,7 @@ final class HeraldCommitAdapter
protected $affectedPackages;
protected $auditNeededPackages;
private $buildPlanPHIDs = array();
private $buildRequests = array();
public function getAdapterApplicationClass() {
return 'PhabricatorDiffusionApplication';
@ -308,12 +308,13 @@ final class HeraldCommitAdapter
return $this->getObject()->getRepository()->getPHID();
}
public function getQueuedHarbormasterBuildPlanPHIDs() {
return $this->buildPlanPHIDs;
public function getQueuedHarbormasterBuildRequests() {
return $this->buildRequests;
}
public function queueHarbormasterBuildPlanPHID($phid) {
$this->buildPlanPHIDs[] = $phid;
public function queueHarbormasterBuildRequest(
HarbormasterBuildRequest $request) {
$this->buildRequests[] = $request;
}
}

View file

@ -121,6 +121,25 @@ final class DiffusionCommitRemarkupRuleTestCase extends PhabricatorTestCase {
),
),
),
// After an "@", we should not be recognizing references because these
// are username mentions.
'deadbeef' => array(
'embed' => array(
),
'ref' => array(
array(
'offset' => 0,
'id' => 'deadbeef',
),
),
),
'@deadbeef' => array(
'embed' => array(
),
'ref' => array(
),
),
);
foreach ($cases as $input => $expect) {

View file

@ -88,6 +88,11 @@ final class DiffusionHistoryTableView extends DiffusionView {
$drequest = $this->getDiffusionRequest();
$viewer = $this->getUser();
$show_revisions = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorDifferentialApplication',
$viewer);
$handles = $viewer->loadHandles($this->getRequiredHandlePHIDs());
$graph = null;
@ -242,6 +247,10 @@ final class DiffusionHistoryTableView extends DiffusionView {
$view->setColumnVisibility(
array(
$graph ? true : false,
true,
true,
true,
$show_revisions,
));
$view->setDeviceVisibility(
array(

View file

@ -47,7 +47,7 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
return array(
'/drydock/' => array(
'' => 'DrydockConsoleController',
'blueprint/' => array(
'(?P<type>blueprint)/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockBlueprintListController',
'(?P<id>[1-9]\d*)/' => array(
'' => 'DrydockBlueprintViewController',
@ -55,29 +55,32 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
'DrydockBlueprintDisableController',
'resources/(?:query/(?P<queryKey>[^/]+)/)?' =>
'DrydockResourceListController',
'logs/(?:query/(?P<queryKey>[^/]+)/)?' =>
'DrydockLogListController',
),
'create/' => 'DrydockBlueprintCreateController',
'edit/(?:(?P<id>[1-9]\d*)/)?' => 'DrydockBlueprintEditController',
),
'resource/' => array(
'(?P<type>resource)/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockResourceListController',
'(?P<id>[1-9]\d*)/' => array(
'' => 'DrydockResourceViewController',
'release/' => 'DrydockResourceReleaseController',
'leases/(?:query/(?P<queryKey>[^/]+)/)?' =>
'DrydockLeaseListController',
'logs/(?:query/(?P<queryKey>[^/]+)/)?' =>
'DrydockLogListController',
),
),
'lease/' => array(
'(?P<type>lease)/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockLeaseListController',
'(?P<id>[1-9]\d*)/' => array(
'' => 'DrydockLeaseViewController',
'release/' => 'DrydockLeaseReleaseController',
'logs/(?:query/(?P<queryKey>[^/]+)/)?' =>
'DrydockLogListController',
),
),
'log/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockLogListController',
),
),
);
}

View file

@ -69,8 +69,9 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
$binding_phid = $binding->getPHID();
$resource = $this->newResourceTemplate($blueprint, $device_name)
$resource = $this->newResourceTemplate($blueprint)
->setActivateWhenAllocated(true)
->setAttribute('almanacDeviceName', $device_name)
->setAttribute('almanacServicePHID', $binding->getServicePHID())
->setAttribute('almanacBindingPHID', $binding_phid)
->needSlotLock("almanac.host.binding({$binding_phid})");
@ -95,6 +96,15 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
return;
}
public function getResourceName(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
$device_name = $resource->getAttribute(
'almanacDeviceName',
pht('<Unknown>'));
return pht('Host (%s)', $device_name);
}
public function canAcquireLeaseOnResource(
DrydockBlueprint $blueprint,
DrydockResource $resource,
@ -163,7 +173,6 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
->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.',

View file

@ -237,6 +237,19 @@ abstract class DrydockBlueprintImplementation extends Phobject {
DrydockResource $resource);
/**
* Get a human readable name for a resource.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param DrydockResource Resource to get the name of.
* @return string Human-readable resource name.
* @task resource
*/
abstract public function getResourceName(
DrydockBlueprint $blueprint,
DrydockResource $resource);
/* -( Resource Interfaces )------------------------------------------------ */
@ -250,46 +263,6 @@ abstract class DrydockBlueprintImplementation extends Phobject {
/* -( Logging )------------------------------------------------------------ */
/**
* @task log
*/
protected function logException(Exception $ex) {
$this->log($ex->getMessage());
}
/**
* @task log
*/
protected function log($message) {
self::writeLog(null, null, $message);
}
/**
* @task log
*/
public static function writeLog(
DrydockResource $resource = null,
DrydockLease $lease = null,
$message = null) {
$log = id(new DrydockLog())
->setEpoch(time())
->setMessage($message);
if ($resource) {
$log->setResourceID($resource->getID());
}
if ($lease) {
$log->setLeaseID($lease->getID());
}
$log->save();
}
public static function getAllBlueprintImplementations() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
@ -300,16 +273,13 @@ abstract class DrydockBlueprintImplementation extends Phobject {
return idx(self::getAllBlueprintImplementations(), $class);
}
protected function newResourceTemplate(
DrydockBlueprint $blueprint,
$name) {
protected function newResourceTemplate(DrydockBlueprint $blueprint) {
$resource = id(new DrydockResource())
->setBlueprintPHID($blueprint->getPHID())
->attachBlueprint($blueprint)
->setType($this->getType())
->setStatus(DrydockResourceStatus::STATUS_PENDING)
->setName($name);
->setStatus(DrydockResourceStatus::STATUS_PENDING);
// Pre-allocate the resource PHID.
$resource->setPHID($resource->generatePHID());
@ -325,14 +295,18 @@ abstract class DrydockBlueprintImplementation extends Phobject {
$lease_status = $lease->getStatus();
switch ($lease_status) {
case DrydockLeaseStatus::STATUS_PENDING:
case DrydockLeaseStatus::STATUS_ACQUIRED:
// TODO: Temporary failure.
throw new Exception(pht('Lease still activating.'));
throw new PhabricatorWorkerYieldException(15);
case DrydockLeaseStatus::STATUS_ACTIVE:
return;
default:
// TODO: Permanent failure.
throw new Exception(pht('Lease in bad state.'));
throw new Exception(
pht(
'Lease ("%s") is in bad state ("%s"), expected "%s".',
$lease->getPHID(),
$lease_status,
DrydockLeaseStatus::STATUS_ACTIVE));
}
}

View file

@ -37,10 +37,37 @@ final class DrydockWorkingCopyBlueprintImplementation
DrydockResource $resource,
DrydockLease $lease) {
$have_phid = $resource->getAttribute('repositoryPHID');
$need_phid = $lease->getAttribute('repositoryPHID');
$need_map = $lease->getAttribute('repositories.map');
if (!is_array($need_map)) {
return false;
}
if ($need_phid !== $have_phid) {
$have_map = $resource->getAttribute('repositories.map');
if (!is_array($have_map)) {
return false;
}
$have_as = ipull($have_map, 'phid');
$need_as = ipull($need_map, 'phid');
foreach ($need_as as $need_directory => $need_phid) {
if (empty($have_as[$need_directory])) {
// This resource is missing a required working copy.
return false;
}
if ($have_as[$need_directory] != $need_phid) {
// This resource has a required working copy, but it contains
// the wrong repository.
return false;
}
unset($have_as[$need_directory]);
}
if ($have_as && $lease->getAttribute('repositories.strict')) {
// This resource has extra repositories, but the lease is strict about
// which repositories are allowed to exist.
return false;
}
@ -70,14 +97,7 @@ final class DrydockWorkingCopyBlueprintImplementation
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$repository_phid = $lease->getAttribute('repositoryPHID');
$repository = $this->loadRepository($repository_phid);
$resource = $this->newResourceTemplate(
$blueprint,
pht(
'Working Copy (%s)',
$repository->getCallsign()));
$resource = $this->newResourceTemplate($blueprint);
$resource_phid = $resource->getPHID();
@ -90,8 +110,17 @@ final class DrydockWorkingCopyBlueprintImplementation
// TODO: Add some limits to the number of working copies we can have at
// once?
$map = $lease->getAttribute('repositories.map');
foreach ($map as $key => $value) {
$map[$key] = array_select_keys(
$value,
array(
'phid',
));
}
return $resource
->setAttribute('repositoryPHID', $repository->getPHID())
->setAttribute('repositories.map', $map)
->setAttribute('host.leasePHID', $host_lease->getPHID())
->allocateResource();
}
@ -103,26 +132,32 @@ final class DrydockWorkingCopyBlueprintImplementation
$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);
$map = $resource->getAttribute('repositories.map');
$repositories = $this->loadRepositories(ipull($map, 'phid'));
foreach ($map as $directory => $spec) {
// TODO: Validate directory isn't goofy like "/etc" or "../../lol"
// somewhere?
$repository = $repositories[$spec['phid']];
$path = "{$root}/repo/{$directory}/";
// TODO: Run these in parallel?
$interface->execx(
'git clone -- %s %s',
(string)$repository->getCloneURIObject(),
$path);
}
$resource
->setAttribute('workingcopy.root', $root)
->setAttribute('workingcopy.path', $path)
->activateResource();
}
@ -135,49 +170,99 @@ final class DrydockWorkingCopyBlueprintImplementation
// Destroy the lease on the host.
$lease->releaseOnDestruction();
// Destroy the working copy on disk.
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $lease->getInterface($command_type);
if ($lease->isActive()) {
// 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);
$root_key = 'workingcopy.root';
$root = $resource->getAttribute($root_key);
if (strlen($root)) {
$interface->execx('rm -rf -- %s', $root);
}
}
}
public function getResourceName(
DrydockBlueprint $blueprint,
DrydockResource $resource) {
return pht('Working Copy');
}
public function activateLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
$host_lease = $this->loadHostLease($resource);
$command_type = DrydockCommandInterface::INTERFACE_TYPE;
$interface = $lease->getInterface($command_type);
$interface = $host_lease->getInterface($command_type);
$cmd = array();
$arg = array();
$map = $lease->getAttribute('repositories.map');
$root = $resource->getAttribute('workingcopy.root');
$cmd[] = 'git clean -d --force';
$cmd[] = 'git reset --hard HEAD';
$cmd[] = 'git fetch';
$default = null;
foreach ($map as $directory => $spec) {
$cmd = array();
$arg = array();
$commit = $lease->getAttribute('commit');
$branch = $lease->getAttribute('branch');
$cmd[] = 'cd %s';
$arg[] = "{$root}/repo/{$directory}/";
if ($commit !== null) {
$cmd[] = 'git reset --hard %s';
$arg[] = $commit;
} else if ($branch !== null) {
$cmd[] = 'git reset --hard %s';
$arg[] = $branch;
$cmd[] = 'git clean -d --force';
$cmd[] = 'git fetch';
$commit = idx($spec, 'commit');
$branch = idx($spec, 'branch');
$ref = idx($spec, 'ref');
if ($commit !== null) {
$cmd[] = 'git reset --hard %s';
$arg[] = $commit;
} else if ($branch !== null) {
$cmd[] = 'git checkout %s';
$arg[] = $branch;
$cmd[] = 'git reset --hard origin/%s';
$arg[] = $branch;
} else if ($ref) {
$ref_uri = $ref['uri'];
$ref_ref = $ref['ref'];
$cmd[] = 'git fetch --no-tags -- %s +%s:%s';
$arg[] = $ref_uri;
$arg[] = $ref_ref;
$arg[] = $ref_ref;
$cmd[] = 'git checkout %s';
$arg[] = $ref_ref;
$cmd[] = 'git reset --hard %s';
$arg[] = $ref_ref;
} else {
$cmd[] = 'git reset --hard HEAD';
}
$cmd = implode(' && ', $cmd);
$argv = array_merge(array($cmd), $arg);
$result = call_user_func_array(
array($interface, 'execx'),
$argv);
if (idx($spec, 'default')) {
$default = $directory;
}
}
$cmd = implode(' && ', $cmd);
$argv = array_merge(array($cmd), $arg);
if ($default === null) {
$default = head_key($map);
}
$result = call_user_func_array(
array($interface, 'execx'),
$argv);
// TODO: Use working storage?
$lease->setAttribute('workingcopy.default', "{$root}/repo/{$default}/");
$lease->activateOnResource($resource);
}
@ -217,35 +302,44 @@ final class DrydockWorkingCopyBlueprintImplementation
$host_lease = $this->loadHostLease($resource);
$command_interface = $host_lease->getInterface($type);
$path = $resource->getAttribute('workingcopy.path');
$path = $lease->getAttribute('workingcopy.default');
$command_interface->setWorkingDirectory($path);
return $command_interface;
}
}
private function loadRepository($repository_phid) {
$repository = id(new PhabricatorRepositoryQuery())
private function loadRepositories(array $phids) {
$repositories = 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));
->withPHIDs($phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($phids as $phid) {
if (empty($repositories[$phid])) {
throw new Exception(
pht(
'Repository PHID "%s" does not exist.',
$phid));
}
}
switch ($repository->getVersionControlSystem()) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
default:
// TODO: Permanent failure.
throw new Exception(pht('Unsupported VCS!'));
foreach ($repositories as $repository) {
$repository_vcs = $repository->getVersionControlSystem();
switch ($repository_vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
break;
default:
throw new Exception(
pht(
'Repository ("%s") has unsupported VCS ("%s").',
$repository->getPHID(),
$repository_vcs));
}
}
return $repository;
return $repositories;
}
private function loadHostLease(DrydockResource $resource) {
@ -258,8 +352,10 @@ final class DrydockWorkingCopyBlueprintImplementation
->withPHIDs(array($lease_phid))
->executeOne();
if (!$lease) {
// TODO: Permanent failure.
throw new Exception(pht('Unable to load lease "%s".', $lease_phid));
throw new Exception(
pht(
'Unable to load lease ("%s").',
$lease_phid));
}
return $lease;

View file

@ -8,4 +8,8 @@ final class DrydockDefaultViewCapability extends PhabricatorPolicyCapability {
return pht('Default Blueprint View Policy');
}
public function shouldAllowPublicPolicySetting() {
return true;
}
}

View file

@ -56,11 +56,19 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
new DrydockBlueprintTransactionQuery());
$timeline->setShouldTerminate(true);
$log_query = id(new DrydockLogQuery())
->withBlueprintPHIDs(array($blueprint->getPHID()));
$log_box = $this->buildLogBox(
$log_query,
$this->getApplicationURI("blueprint/{$id}/logs/query/all/"));
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$resource_box,
$log_box,
$timeline,
),
array(

View file

@ -15,7 +15,6 @@ final class DrydockConsoleController extends DrydockController {
$nav->addFilter('blueprint', pht('Blueprints'));
$nav->addFilter('resource', pht('Resources'));
$nav->addFilter('lease', pht('Leases'));
$nav->addFilter('log', pht('Logs'));
$nav->selectFilter(null);
@ -31,6 +30,7 @@ final class DrydockConsoleController extends DrydockController {
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Blueprints'))
->setFontIcon('fa-map-o')
->setHref($this->getApplicationURI('blueprint/'))
->addAttribute(
pht(
@ -40,6 +40,7 @@ final class DrydockConsoleController extends DrydockController {
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Resources'))
->setFontIcon('fa-map')
->setHref($this->getApplicationURI('resource/'))
->addAttribute(
pht('View and manage resources Drydock has built, like hosts.')));
@ -47,16 +48,10 @@ final class DrydockConsoleController extends DrydockController {
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Leases'))
->setFontIcon('fa-link')
->setHref($this->getApplicationURI('lease/'))
->addAttribute(pht('Manage leases on resources.')));
$menu->addItem(
id(new PHUIObjectItemView())
->setHeader(pht('Logs'))
->setHref($this->getApplicationURI('log/'))
->addAttribute(pht('View logs.')));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Console'));

View file

@ -85,4 +85,31 @@ abstract class DrydockController extends PhabricatorController {
->addRawContent($table);
}
protected function buildLogBox(DrydockLogQuery $query, $all_uri) {
$viewer = $this->getViewer();
$logs = $query
->setViewer($viewer)
->setLimit(100)
->execute();
$log_table = id(new DrydockLogListView())
->setUser($viewer)
->setLogs($logs)
->render();
$log_header = id(new PHUIHeaderView())
->setHeader(pht('Logs'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setHref($all_uri)
->setIconFont('fa-search')
->setText(pht('View All Logs')));
return id(new PHUIObjectBoxView())
->setHeader($log_header)
->setTable($log_table);
}
}

View file

@ -44,7 +44,7 @@ abstract class DrydockLeaseController
$this->getApplicationURI('resource/'));
$crumbs->addTextCrumb(
$resource->getName(),
$resource->getResourceName(),
$this->getApplicationURI("resource/{$id}/"));
$crumbs->addTextCrumb(

View file

@ -9,35 +9,33 @@ final class DrydockLeaseViewController extends DrydockLeaseController {
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withIDs(array($id))
->needUnconsumedCommands(true)
->executeOne();
if (!$lease) {
return new Aphront404Response();
}
$lease_uri = $this->getApplicationURI('lease/'.$lease->getID().'/');
$id = $lease->getID();
$lease_uri = $this->getApplicationURI("lease/{$id}/");
$title = pht('Lease %d', $lease->getID());
$header = id(new PHUIHeaderView())
->setHeader($title);
if ($lease->isReleasing()) {
$header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing'));
}
$actions = $this->buildActionListView($lease);
$properties = $this->buildPropertyListView($lease, $actions);
$pager = new PHUIPagerView();
$pager->setURI(new PhutilURI($lease_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
$log_query = id(new DrydockLogQuery())
->withLeasePHIDs(array($lease->getPHID()));
$logs = id(new DrydockLogQuery())
->setViewer($viewer)
->withLeaseIDs(array($lease->getID()))
->executeWithOffsetPager($pager);
$log_table = id(new DrydockLogListView())
->setUser($viewer)
->setLogs($logs)
->render();
$log_table->appendChild($pager);
$log_box = $this->buildLogBox(
$log_query,
$this->getApplicationURI("lease/{$id}/logs/query/all/"));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title, $lease_uri);
@ -51,10 +49,6 @@ final class DrydockLeaseViewController extends DrydockLeaseController {
->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,
@ -78,6 +72,10 @@ final class DrydockLeaseViewController extends DrydockLeaseController {
$id = $lease->getID();
$can_release = $lease->canRelease();
if ($lease->isReleasing()) {
$can_release = false;
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$lease,
@ -110,6 +108,14 @@ final class DrydockLeaseViewController extends DrydockLeaseController {
pht('Resource Type'),
$lease->getResourceType());
$owner_phid = $lease->getOwnerPHID();
if ($owner_phid) {
$owner_display = $viewer->renderHandle($owner_phid);
} else {
$owner_display = phutil_tag('em', array(), pht('No Owner'));
}
$view->addProperty(pht('Owner'), $owner_display);
$resource_phid = $lease->getResourcePHID();
if ($resource_phid) {
$resource_display = $viewer->renderHandle($resource_phid);

View file

@ -3,13 +3,60 @@
abstract class DrydockLogController
extends DrydockController {
private $blueprint;
private $resource;
private $lease;
public function setBlueprint(DrydockBlueprint $blueprint) {
$this->blueprint = $blueprint;
return $this;
}
public function getBlueprint() {
return $this->blueprint;
}
public function setResource(DrydockResource $resource) {
$this->resource = $resource;
return $this;
}
public function getResource() {
return $this->resource;
}
public function setLease(DrydockLease $lease) {
$this->lease = $lease;
return $this;
}
public function getLease() {
return $this->lease;
}
public function buildSideNavView() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new DrydockLogSearchEngine())
->setViewer($this->getRequest()->getUser())
->addNavigationItems($nav->getMenu());
$engine = id(new DrydockLogSearchEngine())
->setViewer($this->getRequest()->getUser());
$blueprint = $this->getBlueprint();
if ($blueprint) {
$engine->setBlueprint($blueprint);
}
$resource = $this->getResource();
if ($resource) {
$engine->setResource($resource);
}
$lease = $this->getLease();
if ($lease) {
$engine->setLease($lease);
}
$engine->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
@ -18,9 +65,54 @@ abstract class DrydockLogController
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Logs'),
$this->getApplicationURI('log/'));
$blueprint = $this->getBlueprint();
$resource = $this->getResource();
$lease = $this->getLease();
if ($blueprint) {
$id = $blueprint->getID();
$crumbs->addTextCrumb(
pht('Blueprints'),
$this->getApplicationURI('blueprint/'));
$crumbs->addTextCrumb(
$blueprint->getBlueprintName(),
$this->getApplicationURI("blueprint/{$id}/"));
$crumbs->addTextCrumb(
pht('Logs'),
$this->getApplicationURI("blueprint/{$id}/logs/"));
} else if ($resource) {
$id = $resource->getID();
$crumbs->addTextCrumb(
pht('Resources'),
$this->getApplicationURI('resource/'));
$crumbs->addTextCrumb(
$resource->getResourceName(),
$this->getApplicationURI("resource/{$id}/"));
$crumbs->addTextCrumb(
pht('Logs'),
$this->getApplicationURI("resource/{$id}/logs/"));
} else if ($lease) {
$id = $lease->getID();
$crumbs->addTextCrumb(
pht('Leases'),
$this->getApplicationURI('lease/'));
$crumbs->addTextCrumb(
$lease->getLeaseName(),
$this->getApplicationURI("lease/{$id}/"));
$crumbs->addTextCrumb(
pht('Logs'),
$this->getApplicationURI("lease/{$id}/logs/"));
}
return $crumbs;
}

View file

@ -8,11 +8,53 @@ final class DrydockLogListController extends DrydockLogController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$querykey = $request->getURIData('queryKey');
$engine = new DrydockLogSearchEngine();
$id = $request->getURIData('id');
$type = $request->getURIData('type');
switch ($type) {
case 'blueprint':
$blueprint = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$blueprint) {
return new Aphront404Response();
}
$engine->setBlueprint($blueprint);
$this->setBlueprint($blueprint);
break;
case 'resource':
$resource = id(new DrydockResourceQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$resource) {
return new Aphront404Response();
}
$engine->setResource($resource);
$this->setResource($resource);
break;
case 'lease':
$lease = id(new DrydockLeaseQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$lease) {
return new Aphront404Response();
}
$engine->setLease($lease);
$this->setLease($lease);
break;
default:
return new Aphront404Response();
}
$query_key = $request->getURIData('queryKey');
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($querykey)
->setSearchEngine(new DrydockLogSearchEngine())
->setQueryKey($query_key)
->setSearchEngine($engine)
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);

View file

@ -9,38 +9,38 @@ final class DrydockResourceViewController extends DrydockResourceController {
$resource = id(new DrydockResourceQuery())
->setViewer($viewer)
->withIDs(array($id))
->needUnconsumedCommands(true)
->executeOne();
if (!$resource) {
return new Aphront404Response();
}
$title = pht('Resource %s %s', $resource->getID(), $resource->getName());
$title = pht(
'Resource %s %s',
$resource->getID(),
$resource->getResourceName());
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setPolicyObject($resource)
->setHeader($title);
if ($resource->isReleasing()) {
$header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing'));
}
$actions = $this->buildActionListView($resource);
$properties = $this->buildPropertyListView($resource, $actions);
$resource_uri = 'resource/'.$resource->getID().'/';
$resource_uri = $this->getApplicationURI($resource_uri);
$id = $resource->getID();
$resource_uri = $this->getApplicationURI("resource/{$id}/");
$pager = new PHUIPagerView();
$pager->setURI(new PhutilURI($resource_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
$log_query = id(new DrydockLogQuery())
->withResourcePHIDs(array($resource->getPHID()));
$logs = id(new DrydockLogQuery())
->setViewer($viewer)
->withResourceIDs(array($resource->getID()))
->executeWithOffsetPager($pager);
$log_table = id(new DrydockLogListView())
->setUser($viewer)
->setLogs($logs)
->render();
$log_table->appendChild($pager);
$log_box = $this->buildLogBox(
$log_query,
$this->getApplicationURI("resource/{$id}/logs/query/all/"));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Resource %d', $resource->getID()));
@ -56,10 +56,6 @@ final class DrydockResourceViewController extends DrydockResourceController {
$lease_box = $this->buildLeaseBox($resource);
$log_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Resource Logs'))
->setTable($log_table);
return $this->buildApplicationPage(
array(
$crumbs,
@ -82,6 +78,10 @@ final class DrydockResourceViewController extends DrydockResourceController {
->setObject($resource);
$can_release = $resource->canRelease();
if ($resource->isReleasing()) {
$can_release = false;
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$resource,
@ -116,6 +116,14 @@ final class DrydockResourceViewController extends DrydockResourceController {
pht('Status'),
$status);
$until = $resource->getUntil();
if ($until) {
$until_display = phabricator_datetime($until, $viewer);
} else {
$until_display = phutil_tag('em', array(), pht('Never'));
}
$view->addProperty(pht('Expires'), $until_display);
$view->addProperty(
pht('Resource Type'),
$resource->getType());

View file

@ -22,4 +22,8 @@ final class DrydockSlotLockException extends Exception {
parent::__construct($message);
}
public function getLockMap() {
return $this->lockMap;
}
}

View file

@ -0,0 +1,29 @@
<?php
final class DrydockLogGarbageCollector
extends PhabricatorGarbageCollector {
const COLLECTORCONST = 'drydock.logs';
public function getCollectorName() {
return pht('Drydock Logs');
}
public function getDefaultRetentionPolicy() {
return phutil_units('30 days in seconds');
}
protected function collectGarbage() {
$log_table = new DrydockLog();
$conn_w = $log_table->establishConnection('w');
queryfx(
$conn_w,
'DELETE FROM %T WHERE epoch <= %d LIMIT 100',
$log_table->getTableName(),
$this->getGarbageEpoch());
return ($conn_w->getAffectedRows() == 100);
}
}

View file

@ -0,0 +1,19 @@
<?php
final class DrydockLeaseAcquiredLogType extends DrydockLogType {
const LOGCONST = 'core.lease.acquired';
public function getLogTypeName() {
return pht('Lease Acquired');
}
public function getLogTypeIcon(array $data) {
return 'fa-link yellow';
}
public function renderLog(array $data) {
return pht('Lease acquired.');
}
}

View file

@ -0,0 +1,19 @@
<?php
final class DrydockLeaseActivatedLogType extends DrydockLogType {
const LOGCONST = 'core.lease.activated';
public function getLogTypeName() {
return pht('Lease Activated');
}
public function getLogTypeIcon(array $data) {
return 'fa-link green';
}
public function renderLog(array $data) {
return pht('Lease activated.');
}
}

View file

@ -0,0 +1,22 @@
<?php
final class DrydockLeaseActivationFailureLogType extends DrydockLogType {
const LOGCONST = 'core.lease.activation-failure';
public function getLogTypeName() {
return pht('Activation Failed');
}
public function getLogTypeIcon(array $data) {
return 'fa-times red';
}
public function renderLog(array $data) {
$class = idx($data, 'class');
$message = idx($data, 'message');
return pht('Lease activation failed: [%s] %s', $class, $message);
}
}

View file

@ -0,0 +1,23 @@
<?php
final class DrydockLeaseActivationYieldLogType extends DrydockLogType {
const LOGCONST = 'core.lease.activation-yield';
public function getLogTypeName() {
return pht('Waiting for Activation');
}
public function getLogTypeIcon(array $data) {
return 'fa-clock-o green';
}
public function renderLog(array $data) {
$duration = idx($data, 'duration');
return pht(
'Waiting %s second(s) for lease to activate.',
new PhutilNumber($duration));
}
}

View file

@ -0,0 +1,19 @@
<?php
final class DrydockLeaseDestroyedLogType extends DrydockLogType {
const LOGCONST = 'core.lease.destroyed';
public function getLogTypeName() {
return pht('Lease Destroyed');
}
public function getLogTypeIcon(array $data) {
return 'fa-link grey';
}
public function renderLog(array $data) {
return pht('Lease destroyed.');
}
}

View file

@ -0,0 +1,19 @@
<?php
final class DrydockLeaseQueuedLogType extends DrydockLogType {
const LOGCONST = 'core.lease.queued';
public function getLogTypeName() {
return pht('Lease Queued');
}
public function getLogTypeIcon(array $data) {
return 'fa-link blue';
}
public function renderLog(array $data) {
return pht('Lease queued for acquisition.');
}
}

View file

@ -0,0 +1,19 @@
<?php
final class DrydockLeaseReleasedLogType extends DrydockLogType {
const LOGCONST = 'core.lease.released';
public function getLogTypeName() {
return pht('Lease Released');
}
public function getLogTypeIcon(array $data) {
return 'fa-link black';
}
public function renderLog(array $data) {
return pht('Lease released.');
}
}

View file

@ -0,0 +1,25 @@
<?php
final class DrydockLeaseWaitingForResourcesLogType extends DrydockLogType {
const LOGCONST = 'core.lease.waiting-for-resources';
public function getLogTypeName() {
return pht('Waiting For Resource');
}
public function getLogTypeIcon(array $data) {
return 'fa-clock-o yellow';
}
public function renderLog(array $data) {
$viewer = $this->getViewer();
$blueprint_phids = idx($data, 'blueprintPHIDs', array());
return pht(
'Waiting for available resources from: %s.',
$viewer->renderHandleList($blueprint_phids));
}
}

View file

@ -0,0 +1,41 @@
<?php
abstract class DrydockLogType extends Phobject {
private $viewer;
private $log;
abstract public function getLogTypeName();
abstract public function getLogTypeIcon(array $data);
abstract public function renderLog(array $data);
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
final public function setLog(DrydockLog $log) {
$this->log = $log;
return $this;
}
final public function getLog() {
return $this->log;
}
final public function getLogTypeConstant() {
return $this->getPhobjectClassConstant('LOGCONST', 64);
}
final public static function getAllLogTypes() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getLogTypeConstant')
->execute();
}
}

View file

@ -0,0 +1,22 @@
<?php
final class DrydockResourceActivationFailureLogType extends DrydockLogType {
const LOGCONST = 'core.resource.activation-failure';
public function getLogTypeName() {
return pht('Activation Failed');
}
public function getLogTypeIcon(array $data) {
return 'fa-times red';
}
public function renderLog(array $data) {
$class = idx($data, 'class');
$message = idx($data, 'message');
return pht('Resource activation failed: [%s] %s', $class, $message);
}
}

View file

@ -0,0 +1,23 @@
<?php
final class DrydockResourceActivationYieldLogType extends DrydockLogType {
const LOGCONST = 'core.resource.activation-yield';
public function getLogTypeName() {
return pht('Waiting for Activation');
}
public function getLogTypeIcon(array $data) {
return 'fa-clock-o green';
}
public function renderLog(array $data) {
$duration = idx($data, 'duration');
return pht(
'Waiting %s second(s) for resource to activate.',
new PhutilNumber($duration));
}
}

View file

@ -0,0 +1,26 @@
<?php
final class DrydockSlotLockFailureLogType extends DrydockLogType {
const LOGCONST = 'core.resource.slot-lock.failure';
public function getLogTypeName() {
return pht('Slot Lock Failure');
}
public function getLogTypeIcon(array $data) {
return 'fa-lock yellow';
}
public function renderLog(array $data) {
$locks = idx($data, 'locks', array());
if ($locks) {
return pht(
'Failed to acquire slot locks: %s.',
implode(', ', array_keys($locks)));
} else {
return pht('Failed to acquire slot locks.');
}
}
}

View file

@ -33,7 +33,7 @@ final class DrydockResourcePHIDType extends PhabricatorPHIDType {
pht(
'Resource %d: %s',
$id,
$resource->getName()));
$resource->getResourceName()));
$handle->setURI("/drydock/resource/{$id}/");
}

View file

@ -7,7 +7,7 @@ final class DrydockLeaseQuery extends DrydockQuery {
private $resourcePHIDs;
private $statuses;
private $datasourceQuery;
private $needCommands;
private $needUnconsumedCommands;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -34,6 +34,11 @@ final class DrydockLeaseQuery extends DrydockQuery {
return $this;
}
public function needUnconsumedCommands($need) {
$this->needUnconsumedCommands = $need;
return $this;
}
public function newResultObject() {
return new DrydockLease();
}
@ -71,6 +76,25 @@ final class DrydockLeaseQuery extends DrydockQuery {
return $leases;
}
protected function didFilterPage(array $leases) {
if ($this->needUnconsumedCommands) {
$commands = id(new DrydockCommandQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withTargetPHIDs(mpull($leases, 'getPHID'))
->withConsumed(false)
->execute();
$commands = mgroup($commands, 'getTargetPHID');
foreach ($leases as $lease) {
$list = idx($commands, $lease->getPHID(), array());
$lease->attachUnconsumedCommands($list);
}
}
return $leases;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);

View file

@ -2,112 +2,125 @@
final class DrydockLogQuery extends DrydockQuery {
private $resourceIDs;
private $leaseIDs;
private $blueprintPHIDs;
private $resourcePHIDs;
private $leasePHIDs;
public function withResourceIDs(array $ids) {
$this->resourceIDs = $ids;
public function withBlueprintPHIDs(array $phids) {
$this->blueprintPHIDs = $phids;
return $this;
}
public function withLeaseIDs(array $ids) {
$this->leaseIDs = $ids;
public function withResourcePHIDs(array $phids) {
$this->resourcePHIDs = $phids;
return $this;
}
public function withLeasePHIDs(array $phids) {
$this->leasePHIDs = $phids;
return $this;
}
public function newResultObject() {
return new DrydockLog();
}
protected function loadPage() {
$table = new DrydockLog();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT log.* FROM %T log %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $logs) {
$resource_ids = array_filter(mpull($logs, 'getResourceID'));
if ($resource_ids) {
protected function didFilterPage(array $logs) {
$blueprint_phids = array_filter(mpull($logs, 'getBlueprintPHID'));
if ($blueprint_phids) {
$blueprints = id(new DrydockBlueprintQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($blueprint_phids)
->execute();
$blueprints = mpull($blueprints, null, 'getPHID');
} else {
$blueprints = array();
}
foreach ($logs as $key => $log) {
$blueprint = null;
$blueprint_phid = $log->getBlueprintPHID();
if ($blueprint_phid) {
$blueprint = idx($blueprints, $blueprint_phid);
}
$log->attachBlueprint($blueprint);
}
$resource_phids = array_filter(mpull($logs, 'getResourcePHID'));
if ($resource_phids) {
$resources = id(new DrydockResourceQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withIDs(array_unique($resource_ids))
->withPHIDs($resource_phids)
->execute();
$resources = mpull($resources, null, 'getPHID');
} else {
$resources = array();
}
foreach ($logs as $key => $log) {
$resource = null;
if ($log->getResourceID()) {
$resource = idx($resources, $log->getResourceID());
if (!$resource) {
unset($logs[$key]);
continue;
}
$resource_phid = $log->getResourcePHID();
if ($resource_phid) {
$resource = idx($resources, $resource_phid);
}
$log->attachResource($resource);
}
$lease_ids = array_filter(mpull($logs, 'getLeaseID'));
if ($lease_ids) {
$lease_phids = array_filter(mpull($logs, 'getLeasePHID'));
if ($lease_phids) {
$leases = id(new DrydockLeaseQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withIDs(array_unique($lease_ids))
->withPHIDs($lease_phids)
->execute();
$leases = mpull($leases, null, 'getPHID');
} else {
$leases = array();
}
foreach ($logs as $key => $log) {
$lease = null;
if ($log->getLeaseID()) {
$lease = idx($leases, $log->getLeaseID());
if (!$lease) {
unset($logs[$key]);
continue;
}
$lease_phid = $log->getLeasePHID();
if ($lease_phid) {
$lease = idx($leases, $lease_phid);
}
$log->attachLease($lease);
}
// These logs are meaningless and their policies aren't computable. They
// shouldn't exist, but throw them away if they do.
foreach ($logs as $key => $log) {
if (!$log->getResource() && !$log->getLease()) {
unset($logs[$key]);
}
}
return $logs;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->resourceIDs !== null) {
if ($this->blueprintPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'resourceID IN (%Ld)',
$this->resourceIDs);
$conn,
'blueprintPHID IN (%Ls)',
$this->blueprintPHIDs);
}
if ($this->leaseIDs !== null) {
if ($this->resourcePHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'leaseID IN (%Ld)',
$this->leaseIDs);
$conn,
'resourcePHID IN (%Ls)',
$this->resourcePHIDs);
}
$where[] = $this->buildPagingClause($conn_r);
if ($this->leasePHIDs !== null) {
$where[] = qsprintf(
$conn,
'leasePHID IN (%Ls)',
$this->leasePHIDs);
}
return $this->formatWhereClause($where);
return $where;
}
}

View file

@ -2,6 +2,43 @@
final class DrydockLogSearchEngine extends PhabricatorApplicationSearchEngine {
private $blueprint;
private $resource;
private $lease;
public function setBlueprint(DrydockBlueprint $blueprint) {
$this->blueprint = $blueprint;
return $this;
}
public function getBlueprint() {
return $this->blueprint;
}
public function setResource(DrydockResource $resource) {
$this->resource = $resource;
return $this;
}
public function getResource() {
return $this->resource;
}
public function setLease(DrydockLease $lease) {
$this->lease = $lease;
return $this;
}
public function getLease() {
return $this->lease;
}
public function canUseInPanelContext() {
// Prevent use on Dashboard panels since all log queries currently need a
// parent object and these don't seem particularly useful in any case.
return false;
}
public function getResultTypeDescription() {
return pht('Drydock Logs');
}
@ -10,75 +47,59 @@ final class DrydockLogSearchEngine extends PhabricatorApplicationSearchEngine {
return 'PhabricatorDrydockApplication';
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$query = new PhabricatorSavedQuery();
$query->setParameter(
'resourcePHIDs',
$this->readListFromRequest($request, 'resources'));
$query->setParameter(
'leasePHIDs',
$this->readListFromRequest($request, 'leases'));
return $query;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$resource_phids = $saved->getParameter('resourcePHIDs', array());
$lease_phids = $saved->getParameter('leasePHIDs', array());
// TODO: Change logs to use PHIDs instead of IDs.
$resource_ids = array();
$lease_ids = array();
if ($resource_phids) {
$resource_ids = id(new DrydockResourceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($resource_phids)
->execute();
$resource_ids = mpull($resource_ids, 'getID');
}
if ($lease_phids) {
$lease_ids = id(new DrydockLeaseQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($lease_phids)
->execute();
$lease_ids = mpull($lease_ids, 'getID');
}
public function newQuery() {
$query = new DrydockLogQuery();
if ($resource_ids) {
$query->withResourceIDs($resource_ids);
$blueprint = $this->getBlueprint();
if ($blueprint) {
$query->withBlueprintPHIDs(array($blueprint->getPHID()));
}
if ($lease_ids) {
$query->withLeaseIDs($lease_ids);
$resource = $this->getResource();
if ($resource) {
$query->withResourcePHIDs(array($resource->getPHID()));
}
$lease = $this->getLease();
if ($lease) {
$query->withLeasePHIDs(array($lease->getPHID()));
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
$form
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new DrydockResourceDatasource())
->setName('resources')
->setLabel(pht('Resources'))
->setValue($saved->getParameter('resourcePHIDs', array())))
->appendControl(
id(new AphrontFormTokenizerControl())
->setDatasource(new DrydockLeaseDatasource())
->setName('leases')
->setLabel(pht('Leases'))
->setValue($saved->getParameter('leasePHIDs', array())));
return $query;
}
protected function buildCustomSearchFields() {
return array();
}
protected function getURI($path) {
return '/drydock/log/'.$path;
$blueprint = $this->getBlueprint();
if ($blueprint) {
$id = $blueprint->getID();
return "/drydock/blueprint/{$id}/logs/{$path}";
}
$resource = $this->getResource();
if ($resource) {
$id = $resource->getID();
return "/drydock/resource/{$id}/logs/{$path}";
}
$lease = $this->getLease();
if ($lease) {
$id = $lease->getID();
return "/drydock/lease/{$id}/logs/{$path}";
}
throw new Exception(
pht(
'Search engine has no blueprint, resource, or lease.'));
}
protected function getBuiltinQueryNames() {

View file

@ -8,6 +8,7 @@ final class DrydockResourceQuery extends DrydockQuery {
private $types;
private $blueprintPHIDs;
private $datasourceQuery;
private $needUnconsumedCommands;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -39,6 +40,11 @@ final class DrydockResourceQuery extends DrydockQuery {
return $this;
}
public function needUnconsumedCommands($need) {
$this->needUnconsumedCommands = $need;
return $this;
}
public function newResultObject() {
return new DrydockResource();
}
@ -69,6 +75,25 @@ final class DrydockResourceQuery extends DrydockQuery {
return $resources;
}
protected function didFilterPage(array $resources) {
if ($this->needUnconsumedCommands) {
$commands = id(new DrydockCommandQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withTargetPHIDs(mpull($resources, 'getPHID'))
->withConsumed(false)
->execute();
$commands = mgroup($commands, 'getTargetPHID');
foreach ($resources as $resource) {
$list = idx($commands, $resource->getPHID(), array());
$resource->attachUnconsumedCommands($list);
}
}
return $resources;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);

View file

@ -107,6 +107,17 @@ final class DrydockBlueprint extends DrydockDAO
return $this->fields;
}
public function logEvent($type, array $data = array()) {
$log = id(new DrydockLog())
->setEpoch(PhabricatorTime::getNow())
->setType($type)
->setData($data);
$log->setBlueprintPHID($this->getPHID());
return $log->save();
}
/* -( Allocating Resources )----------------------------------------------- */
@ -162,6 +173,16 @@ final class DrydockBlueprint extends DrydockDAO
}
/**
* @task resource
*/
public function getResourceName(DrydockResource $resource) {
return $this->getImplementation()->getResourceName(
$this,
$resource);
}
/* -( Acquiring Leases )--------------------------------------------------- */

View file

@ -11,6 +11,8 @@ final class DrydockLease extends DrydockDAO
protected $status = DrydockLeaseStatus::STATUS_PENDING;
private $resource = self::ATTACHABLE;
private $unconsumedCommands = self::ATTACHABLE;
private $releaseOnDestruction;
private $isAcquired = false;
private $isActivated = false;
@ -104,6 +106,25 @@ final class DrydockLease extends DrydockDAO
return ($this->resource !== null);
}
public function getUnconsumedCommands() {
return $this->assertAttached($this->unconsumedCommands);
}
public function attachUnconsumedCommands(array $commands) {
$this->unconsumedCommands = $commands;
return $this;
}
public function isReleasing() {
foreach ($this->getUnconsumedCommands() as $command) {
if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) {
return true;
}
}
return false;
}
public function queueForActivation() {
if ($this->getID()) {
throw new Exception(
@ -114,14 +135,9 @@ final class DrydockLease extends DrydockDAO
->setStatus(DrydockLeaseStatus::STATUS_PENDING)
->save();
$task = PhabricatorWorker::scheduleTask(
'DrydockAllocatorWorker',
array(
'leasePHID' => $this->getPHID(),
),
array(
'objectPHID' => $this->getPHID(),
));
$this->scheduleUpdate();
$this->logEvent(DrydockLeaseQueuedLogType::LOGCONST);
return $this;
}
@ -216,19 +232,37 @@ final class DrydockLease extends DrydockDAO
}
$this->openTransaction();
$this
->setResourcePHID($resource->getPHID())
->setStatus($new_status)
->save();
try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
$this->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
try {
$this
->setResourcePHID($resource->getPHID())
->attachResource($resource)
->setStatus($new_status)
->save();
} catch (Exception $ex) {
$this->killTransaction();
throw $ex;
}
$this->saveTransaction();
$this->isAcquired = true;
$this->logEvent(DrydockLeaseAcquiredLogType::LOGCONST);
if ($new_status == DrydockLeaseStatus::STATUS_ACTIVE) {
$this->didActivate();
}
@ -295,6 +329,16 @@ final class DrydockLease extends DrydockDAO
}
}
public function canReceiveCommands() {
switch ($this->getStatus()) {
case DrydockLeaseStatus::STATUS_RELEASED:
case DrydockLeaseStatus::STATUS_DESTROYED:
return false;
default:
return true;
}
}
public function scheduleUpdate($epoch = null) {
PhabricatorWorker::scheduleTask(
'DrydockLeaseUpdateWorker',
@ -304,14 +348,21 @@ final class DrydockLease extends DrydockDAO
),
array(
'objectPHID' => $this->getPHID(),
'delayUntil' => $epoch,
'delayUntil' => ($epoch ? (int)$epoch : null),
));
}
public function setAwakenTaskIDs(array $ids) {
$this->setAttribute('internal.awakenTaskIDs', $ids);
return $this;
}
private function didActivate() {
$viewer = PhabricatorUser::getOmnipotentUser();
$need_update = false;
$this->logEvent(DrydockLeaseActivatedLogType::LOGCONST);
$commands = id(new DrydockCommandQuery())
->setViewer($viewer)
->withTargetPHIDs(array($this->getPHID()))
@ -329,8 +380,33 @@ final class DrydockLease extends DrydockDAO
if ($expires) {
$this->scheduleUpdate($expires);
}
$awaken_ids = $this->getAttribute('internal.awakenTaskIDs');
if (is_array($awaken_ids) && $awaken_ids) {
PhabricatorWorker::awakenTaskIDs($awaken_ids);
}
}
public function logEvent($type, array $data = array()) {
$log = id(new DrydockLog())
->setEpoch(PhabricatorTime::getNow())
->setType($type)
->setData($data);
$log->setLeasePHID($this->getPHID());
$resource_phid = $this->getResourcePHID();
if ($resource_phid) {
$resource = $this->getResource();
$log->setResourcePHID($resource->getPHID());
$log->setBlueprintPHID($resource->getBlueprintPHID());
}
return $log->save();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -3,28 +3,38 @@
final class DrydockLog extends DrydockDAO
implements PhabricatorPolicyInterface {
protected $resourceID;
protected $leaseID;
protected $blueprintPHID;
protected $resourcePHID;
protected $leasePHID;
protected $epoch;
protected $message;
protected $type;
protected $data = array();
private $blueprint = self::ATTACHABLE;
private $resource = self::ATTACHABLE;
private $lease = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_SERIALIZATION => array(
'data' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'resourceID' => 'id?',
'leaseID' => 'id?',
'message' => 'text',
'blueprintPHID' => 'phid?',
'resourcePHID' => 'phid?',
'leasePHID' => 'phid?',
'type' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'resourceID' => array(
'columns' => array('resourceID', 'epoch'),
'key_blueprint' => array(
'columns' => array('blueprintPHID', 'type'),
),
'leaseID' => array(
'columns' => array('leaseID', 'epoch'),
'key_resource' => array(
'columns' => array('resourcePHID', 'type'),
),
'key_lease' => array(
'columns' => array('leasePHID', 'type'),
),
'epoch' => array(
'columns' => array('epoch'),
@ -33,6 +43,15 @@ final class DrydockLog extends DrydockDAO
) + parent::getConfiguration();
}
public function attachBlueprint(DrydockBlueprint $blueprint = null) {
$this->blueprint = $blueprint;
return $this;
}
public function getBlueprint() {
return $this->assertAttached($this->blueprint);
}
public function attachResource(DrydockResource $resource = null) {
$this->resource = $resource;
return $this;
@ -51,6 +70,22 @@ final class DrydockLog extends DrydockDAO
return $this->assertAttached($this->lease);
}
public function isComplete() {
if ($this->getBlueprintPHID() && !$this->getBlueprint()) {
return false;
}
if ($this->getResourcePHID() && !$this->getResource()) {
return false;
}
if ($this->getLeasePHID() && !$this->getLease()) {
return false;
}
return true;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -62,21 +97,19 @@ final class DrydockLog extends DrydockDAO
}
public function getPolicy($capability) {
if ($this->getResource()) {
return $this->getResource()->getPolicy($capability);
}
return $this->getLease()->getPolicy($capability);
// NOTE: We let you see that logs exist no matter what, but don't actually
// show you log content unless you can see all of the associated objects.
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->getResource()) {
return $this->getResource()->hasAutomaticCapability($capability, $viewer);
}
return $this->getLease()->hasAutomaticCapability($capability, $viewer);
return false;
}
public function describeAutomaticCapability($capability) {
return pht('Logs inherit the policy of their resources.');
return pht(
'To view log details, you must be able to view the associated '.
'blueprint, resource and lease.');
}
}

View file

@ -7,14 +7,15 @@ final class DrydockResource extends DrydockDAO
protected $phid;
protected $blueprintPHID;
protected $status;
protected $until;
protected $type;
protected $name;
protected $attributes = array();
protected $capabilities = array();
protected $ownerPHID;
private $blueprint = self::ATTACHABLE;
private $unconsumedCommands = self::ATTACHABLE;
private $isAllocated = false;
private $isActivated = false;
private $activateWhenAllocated = false;
@ -28,10 +29,10 @@ final class DrydockResource extends DrydockDAO
'capabilities' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'ownerPHID' => 'phid?',
'status' => 'text32',
'type' => 'text64',
'until' => 'epoch?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_type' => array(
@ -48,6 +49,10 @@ final class DrydockResource extends DrydockDAO
return PhabricatorPHID::generateNewPHID(DrydockResourcePHIDType::TYPECONST);
}
public function getResourceName() {
return $this->getBlueprint()->getResourceName($this);
}
public function getAttribute($key, $default = null) {
return idx($this->attributes, $key, $default);
}
@ -78,6 +83,25 @@ final class DrydockResource extends DrydockDAO
return $this;
}
public function getUnconsumedCommands() {
return $this->assertAttached($this->unconsumedCommands);
}
public function attachUnconsumedCommands(array $commands) {
$this->unconsumedCommands = $commands;
return $this;
}
public function isReleasing() {
foreach ($this->getUnconsumedCommands() as $command) {
if ($command->getCommand() == DrydockCommand::COMMAND_RELEASE) {
return true;
}
}
return false;
}
public function setActivateWhenAllocated($activate) {
$this->activateWhenAllocated = $activate;
return $this;
@ -96,6 +120,16 @@ final class DrydockResource extends DrydockDAO
'Only new resources may be allocated.'));
}
// We expect resources to have a pregenerated PHID, as they should have
// been created by a call to DrydockBlueprint->newResourceTemplate().
if (!$this->getPHID()) {
throw new Exception(
pht(
'Trying to allocate a resource with no generated PHID. Use "%s" to '.
'create new resource templates.',
'newResourceTemplate()'));
}
$expect_status = DrydockResourceStatus::STATUS_PENDING;
$actual_status = $this->getStatus();
if ($actual_status != $expect_status) {
@ -115,17 +149,40 @@ final class DrydockResource extends DrydockDAO
$this->openTransaction();
try {
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
} catch (DrydockSlotLockException $ex) {
$this->killTransaction();
// NOTE: We have to log this on the blueprint, as the resource is not
// going to be saved so the PHID will vanish.
$this->getBlueprint()->logEvent(
DrydockSlotLockFailureLogType::LOGCONST,
array(
'locks' => $ex->getLockMap(),
));
throw $ex;
}
try {
$this
->setStatus($new_status)
->save();
DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks);
$this->slotLocks = array();
} catch (Exception $ex) {
$this->killTransaction();
throw $ex;
}
$this->saveTransaction();
$this->isAllocated = true;
if ($new_status == DrydockResourceStatus::STATUS_ACTIVE) {
$this->didActivate();
}
return $this;
}
@ -164,6 +221,8 @@ final class DrydockResource extends DrydockDAO
$this->isActivated = true;
$this->didActivate();
return $this;
}
@ -181,14 +240,16 @@ final class DrydockResource extends DrydockDAO
}
}
public function scheduleUpdate() {
public function scheduleUpdate($epoch = null) {
PhabricatorWorker::scheduleTask(
'DrydockResourceUpdateWorker',
array(
'resourcePHID' => $this->getPHID(),
'isExpireTask' => ($epoch !== null),
),
array(
'objectPHID' => $this->getPHID(),
'delayUntil' => ($epoch ? (int)$epoch : null),
));
}
@ -209,6 +270,34 @@ final class DrydockResource extends DrydockDAO
if ($need_update) {
$this->scheduleUpdate();
}
$expires = $this->getUntil();
if ($expires) {
$this->scheduleUpdate($expires);
}
}
public function canReceiveCommands() {
switch ($this->getStatus()) {
case DrydockResourceStatus::STATUS_RELEASED:
case DrydockResourceStatus::STATUS_BROKEN:
case DrydockResourceStatus::STATUS_DESTROYED:
return false;
default:
return true;
}
}
public function logEvent($type, array $data = array()) {
$log = id(new DrydockLog())
->setEpoch(PhabricatorTime::getNow())
->setType($type)
->setData($data);
$log->setResourcePHID($this->getPHID());
$log->setBlueprintPHID($this->getBlueprintPHID());
return $log->save();
}

View file

@ -22,19 +22,10 @@ final class DrydockLeaseListView extends AphrontView {
->setHeader($lease->getLeaseName())
->setHref('/drydock/lease/'.$lease->getID().'/');
if ($lease->hasAttachedResource()) {
$resource = $lease->getResource();
$resource_href = '/drydock/resource/'.$resource->getID().'/';
$resource_name = $resource->getName();
$resource_phid = $lease->getResourcePHID();
if ($resource_phid) {
$item->addAttribute(
phutil_tag(
'a',
array(
'href' => $resource_href,
),
$resource_name));
$viewer->renderHandle($resource_phid));
}
$status = DrydockLeaseStatus::getNameForStatus($lease->getStatus());

View file

@ -16,57 +16,90 @@ final class DrydockLogListView extends AphrontView {
$view = new PHUIObjectItemListView();
$types = DrydockLogType::getAllLogTypes();
$rows = array();
foreach ($logs as $log) {
$resource_uri = '/drydock/resource/'.$log->getResourceID().'/';
$lease_uri = '/drydock/lease/'.$log->getLeaseID().'/';
$blueprint_phid = $log->getBlueprintPHID();
if ($blueprint_phid) {
$blueprint = $viewer->renderHandle($blueprint_phid);
} else {
$blueprint = null;
}
$resource_name = $log->getResourceID();
if ($log->getResourceID() !== null) {
$resource_name = $log->getResource()->getName();
$resource_phid = $log->getResourcePHID();
if ($resource_phid) {
$resource = $viewer->renderHandle($resource_phid);
} else {
$resource = null;
}
$lease_phid = $log->getLeasePHID();
if ($lease_phid) {
$lease = $viewer->renderHandle($lease_phid);
} else {
$lease = null;
}
if ($log->isComplete()) {
$type_key = $log->getType();
if (isset($types[$type_key])) {
$type_object = id(clone $types[$type_key])
->setLog($log)
->setViewer($viewer);
$log_data = $log->getData();
$type = $type_object->getLogTypeName();
$icon = $type_object->getLogTypeIcon($log_data);
$data = $type_object->renderLog($log_data);
} else {
$type = pht('<Unknown: %s>', $type_key);
$data = null;
$icon = 'fa-question-circle red';
}
} else {
$type = phutil_tag('em', array(), pht('Restricted'));
$data = phutil_tag(
'em',
array(),
pht('You do not have permission to view this log event.'));
$icon = 'fa-lock grey';
}
$rows[] = array(
phutil_tag(
'a',
array(
'href' => $resource_uri,
),
$resource_name),
phutil_tag(
'a',
array(
'href' => $lease_uri,
),
$log->getLeaseID()),
$log->getMessage(),
$blueprint,
$resource,
$lease,
id(new PHUIIconView())->setIconFont($icon),
$type,
$data,
phabricator_datetime($log->getEpoch(), $viewer),
);
}
$table = new AphrontTableView($rows);
$table->setDeviceReadyTable(true);
$table->setHeaders(
array(
pht('Resource'),
pht('Lease'),
pht('Message'),
pht('Date'),
));
$table->setShortHeaders(
array(
pht('R'),
pht('L'),
pht('Message'),
'',
));
$table->setColumnClasses(
array(
'',
'',
'wide',
'',
));
$table = id(new AphrontTableView($rows))
->setDeviceReadyTable(true)
->setHeaders(
array(
pht('Blueprint'),
pht('Resource'),
pht('Lease'),
null,
pht('Type'),
pht('Data'),
pht('Date'),
))
->setColumnClasses(
array(
'',
'',
'',
'icon',
'',
'wide',
'',
));
return $table;
}

View file

@ -16,11 +16,12 @@ final class DrydockResourceListView extends AphrontView {
$view = new PHUIObjectItemListView();
foreach ($resources as $resource) {
$name = pht('Resource %d', $resource->getID()).': '.$resource->getName();
$id = $resource->getID();
$item = id(new PHUIObjectItemView())
->setHref('/drydock/resource/'.$resource->getID().'/')
->setHeader($name);
->setHref("/drydock/resource/{$id}/")
->setObjectName(pht('Resource %d', $id))
->setHeader($resource->getResourceName());
$status = DrydockResourceStatus::getNameForStatus($resource->getStatus());
$item->addAttribute($status);

View file

@ -1,479 +0,0 @@
<?php
/**
* @task allocate Allocator
* @task resource Managing Resources
* @task lease Managing Leases
*/
final class DrydockAllocatorWorker extends DrydockWorker {
protected function doWork() {
$lease_phid = $this->getTaskDataValue('leasePHID');
$lease = $this->loadLease($lease_phid);
$this->allocateAndAcquireLease($lease);
}
/* -( 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.'));
}
$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);
}
}
/**
* 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) {
$impls = DrydockBlueprintImplementation::getAllBlueprintImplementations();
$keep = array();
foreach ($impls as $key => $impl) {
// Don't use disabled blueprint types.
if (!$impl->isEnabled()) {
continue;
}
// 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;
}
return $keep;
}
/**
* 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();
}
$blueprints = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withBlueprintClasses(array_keys($impls))
->withDisabled(false)
->execute();
$keep = array();
foreach ($blueprints as $key => $blueprint) {
if (!$blueprint->canEverAllocateResourceForLease($lease)) {
continue;
}
$keep[$key] = $blueprint;
}
return $keep;
}
/**
* Load a list of all resources which a given lease can possibly be
* allocated against.
*
* @param list<DrydockBlueprint> Blueprints which may produce suitable
* resources.
* @param DrydockLease Requested lease.
* @return list<DrydockResource> Resources which may be able to allocate
* the lease.
* @task allocator
*/
private function loadResourcesForAllocatingLease(
array $blueprints,
DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
$viewer = $this->getViewer();
$resources = id(new DrydockResourceQuery())
->setViewer($viewer)
->withBlueprintPHIDs(mpull($blueprints, 'getPHID'))
->withTypes(array($lease->getResourceType()))
->withStatuses(
array(
DrydockResourceStatus::STATUS_PENDING,
DrydockResourceStatus::STATUS_ACTIVE,
))
->execute();
$keep = array();
foreach ($resources as $key => $resource) {
$blueprint = $resource->getBlueprint();
if (!$blueprint->canAcquireLeaseOnResource($resource, $lease)) {
continue;
}
$keep[$key] = $resource;
}
return $keep;
}
/**
* Remove blueprints which are too heavily allocated to build a resource for
* a lease from a list of blueprints.
*
* @param list<DrydockBlueprint> List of blueprints.
* @return list<DrydockBlueprint> List with blueprints that can not allocate
* a resource for the lease right now removed.
* @task allocator
*/
private function removeOverallocatedBlueprints(
array $blueprints,
DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
$keep = array();
foreach ($blueprints as $key => $blueprint) {
if (!$blueprint->canAllocateResourceForLease($lease)) {
continue;
}
$keep[$key] = $blueprint;
}
return $keep;
}
/**
* Rank blueprints by suitability for building a new resource for a
* particular lease.
*
* @param list<DrydockBlueprint> List of blueprints.
* @param DrydockLease Requested lease.
* @return list<DrydockBlueprint> Ranked list of blueprints.
* @task allocator
*/
private function rankBlueprints(array $blueprints, DrydockLease $lease) {
assert_instances_of($blueprints, 'DrydockBlueprint');
// TODO: Implement improvements to this ranking algorithm if they become
// available.
shuffle($blueprints);
return $blueprints;
}
/**
* Rank resources by suitability for allocating a particular lease.
*
* @param list<DrydockResource> List of resources.
* @param DrydockLease Requested lease.
* @return list<DrydockResource> Ranked list of resources.
* @task allocator
*/
private function rankResources(array $resources, DrydockLease $lease) {
assert_instances_of($resources, 'DrydockResource');
// TODO: Implement improvements to this ranking algorithm if they become
// available.
shuffle($resources);
return $resources;
}
/* -( Managing Resources )------------------------------------------------- */
/**
* Perform an actual resource allocation with a particular blueprint.
*
* @param DrydockBlueprint The blueprint to allocate a resource from.
* @param DrydockLease Requested lease.
* @return DrydockResource Allocated resource.
* @task resource
*/
private function allocateResource(
DrydockBlueprint $blueprint,
DrydockLease $lease) {
$resource = $blueprint->allocateResource($lease);
$this->validateAllocatedResource($blueprint, $resource, $lease);
// If this resource was allocated as a pending resource, queue a task to
// activate it.
if ($resource->getStatus() == DrydockResourceStatus::STATUS_PENDING) {
PhabricatorWorker::scheduleTask(
'DrydockResourceWorker',
array(
'resourcePHID' => $resource->getPHID(),
),
array(
'objectPHID' => $resource->getPHID(),
));
}
return $resource;
}
/**
* Check that the resource a blueprint allocated is roughly the sort of
* object we expect.
*
* @param DrydockBlueprint Blueprint which built the resource.
* @param wild Thing which the blueprint claims is a valid resource.
* @param DrydockLease Lease the resource was allocated for.
* @return void
* @task resource
*/
private function validateAllocatedResource(
DrydockBlueprint $blueprint,
$resource,
DrydockLease $lease) {
if (!($resource instanceof DrydockResource)) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: %s must '.
'return an object of type %s or throw, but returned something else.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'allocateResource()',
'DrydockResource'));
}
if (!$resource->isAllocatedResource()) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: %s '.
'must actually allocate the resource it returns.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'allocateResource()'));
}
$resource_type = $resource->getType();
$lease_type = $lease->getResourceType();
if ($resource_type !== $lease_type) {
// TODO: Destroy the resource here?
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'built a resource of type "%s" to satisfy a lease requesting a '.
'resource of type "%s".',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
$resource_type,
$lease_type));
}
}
/* -( Managing Leases )---------------------------------------------------- */
/**
* Perform an actual lease acquisition on a particular resource.
*
* @param DrydockResource Resource to acquire a lease on.
* @param DrydockLease Lease to acquire.
* @return void
* @task lease
*/
private function acquireLease(
DrydockResource $resource,
DrydockLease $lease) {
$blueprint = $resource->getBlueprint();
$blueprint->acquireLease($resource, $lease);
$this->validateAcquiredLease($blueprint, $resource, $lease);
// If this lease has been acquired but not activated, queue a task to
// activate it.
if ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACQUIRED) {
PhabricatorWorker::scheduleTask(
'DrydockLeaseWorker',
array(
'leasePHID' => $lease->getPHID(),
),
array(
'objectPHID' => $lease->getPHID(),
));
}
}
/**
* Make sure that a lease was really acquired properly.
*
* @param DrydockBlueprint Blueprint which created the resource.
* @param DrydockResource Resource which was acquired.
* @param DrydockLease The lease which was supposedly acquired.
* @return void
* @task lease
*/
private function validateAcquiredLease(
DrydockBlueprint $blueprint,
DrydockResource $resource,
DrydockLease $lease) {
if (!$lease->isAcquiredLease()) {
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'returned from "%s" without acquiring a lease.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'acquireLease()'));
}
$lease_phid = $lease->getResourcePHID();
$resource_phid = $resource->getPHID();
if ($lease_phid !== $resource_phid) {
// TODO: Destroy the lease?
throw new Exception(
pht(
'Blueprint "%s" (of type "%s") is not properly implemented: it '.
'returned from "%s" with a lease acquired on the wrong resource.',
$blueprint->getBlueprintName(),
$blueprint->getClassName(),
'acquireLease()'));
}
}
}

View file

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

View file

@ -1,5 +1,15 @@
<?php
/**
* @task update Updating Leases
* @task command Processing Commands
* @task allocator Drydock Allocator
* @task acquire Acquiring Leases
* @task activate Activating Leases
* @task release Releasing Leases
* @task break Breaking Leases
* @task destroy Destroying Leases
*/
final class DrydockLeaseUpdateWorker extends DrydockWorker {
protected function doWork() {
@ -13,7 +23,7 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
try {
$lease = $this->loadLease($lease_phid);
$this->updateLease($lease);
$this->handleUpdate($lease);
} catch (Exception $ex) {
$lock->unlock();
throw $ex;
@ -22,54 +32,103 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
$lock->unlock();
}
/* -( Updating Leases )---------------------------------------------------- */
/**
* @task update
*/
private function handleUpdate(DrydockLease $lease) {
try {
$this->updateLease($lease);
} catch (Exception $ex) {
if ($this->isTemporaryException($ex)) {
$this->yieldLease($lease, $ex);
} else {
$this->breakLease($lease, $ex);
}
}
}
/**
* @task update
*/
private function updateLease(DrydockLease $lease) {
if ($lease->getStatus() != DrydockLeaseStatus::STATUS_ACTIVE) {
$this->processLeaseCommands($lease);
$lease_status = $lease->getStatus();
switch ($lease_status) {
case DrydockLeaseStatus::STATUS_PENDING:
$this->executeAllocator($lease);
break;
case DrydockLeaseStatus::STATUS_ACQUIRED:
$this->activateLease($lease);
break;
case DrydockLeaseStatus::STATUS_ACTIVE:
// Nothing to do.
break;
case DrydockLeaseStatus::STATUS_RELEASED:
case DrydockLeaseStatus::STATUS_BROKEN:
$this->destroyLease($lease);
break;
case DrydockLeaseStatus::STATUS_DESTROYED:
break;
}
$this->yieldIfExpiringLease($lease);
}
/**
* @task update
*/
private function yieldLease(DrydockLease $lease, Exception $ex) {
$duration = $this->getYieldDurationFromException($ex);
$lease->logEvent(
DrydockLeaseActivationYieldLogType::LOGCONST,
array(
'duration' => $duration,
));
throw new PhabricatorWorkerYieldException($duration);
}
/* -( Processing Commands )------------------------------------------------ */
/**
* @task command
*/
private function processLeaseCommands(DrydockLease $lease) {
if (!$lease->canReceiveCommands()) {
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();
}
$this->checkLeaseExpiration($lease);
$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.
if (!$lease->canReceiveCommands()) {
break;
}
$this->processCommand($lease, $command);
$this->processLeaseCommand($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(
/**
* @task command
*/
private function processLeaseCommand(
DrydockLease $lease,
DrydockCommand $command) {
switch ($command->getCommand()) {
@ -79,29 +138,597 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker {
}
}
/* -( Drydock 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 executeAllocator(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) {
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 (!$usable_blueprints) {
$lease->logEvent(
DrydockLeaseWaitingForResourcesLogType::LOGCONST,
array(
'blueprintPHIDs' => mpull($blueprints, 'getPHID'),
));
throw new PhabricatorWorkerYieldException(15);
}
$usable_blueprints = $this->rankBlueprints($usable_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) {
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) {
throw new PhutilAggregateException(
pht(
'Unable to acquire lease "%s" on any resouce.',
$lease->getPHID()),
$exceptions);
}
}
/**
* 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) {
$impls = DrydockBlueprintImplementation::getAllBlueprintImplementations();
$keep = array();
foreach ($impls as $key => $impl) {
// Don't use disabled blueprint types.
if (!$impl->isEnabled()) {
continue;
}
// 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;
}
return $keep;
}
/**
* 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();
}
$blueprints = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withBlueprintClasses(array_keys($impls))
->withDisabled(false)
->execute();
$keep = array();
foreach ($blueprints as $key => $blueprint) {
if (!$blueprint->canEverAllocateResourceForLease($lease)) {
continue;
}
$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;
}
/**
* 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 allocator
*/
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(
'DrydockResourceUpdateWorker',
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 allocator
*/
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) {
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));
}
}
/* -( Acquiring 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 acquire
*/
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(
__CLASS__,
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 acquire
*/
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) {
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()'));
}
}
/* -( Activating Leases )-------------------------------------------------- */
/**
* @task activate
*/
private function activateLease(DrydockLease $lease) {
$resource = $lease->getResource();
if (!$resource) {
throw new Exception(
pht('Trying to activate lease with no resource.'));
}
$resource_status = $resource->getStatus();
if ($resource_status == DrydockResourceStatus::STATUS_PENDING) {
throw new PhabricatorWorkerYieldException(15);
}
if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) {
throw new Exception(
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);
}
/**
* @task activate
*/
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()'));
}
}
/* -( Releasing Leases )--------------------------------------------------- */
/**
* @task release
*/
private function releaseLease(DrydockLease $lease) {
$lease->openTransaction();
$lease
->setStatus(DrydockLeaseStatus::STATUS_RELEASED)
->save();
$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(),
));
$lease->logEvent(DrydockLeaseReleasedLogType::LOGCONST);
$resource = $lease->getResource();
$blueprint = $resource->getBlueprint();
if ($resource) {
$blueprint = $resource->getBlueprint();
$blueprint->didReleaseLease($resource, $lease);
}
$blueprint->didReleaseLease($resource, $lease);
$this->destroyLease($lease);
}
/* -( Breaking Leases )---------------------------------------------------- */
/**
* @task break
*/
protected function breakLease(DrydockLease $lease, Exception $ex) {
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_BROKEN:
case DrydockLeaseStatus::STATUS_RELEASED:
case DrydockLeaseStatus::STATUS_DESTROYED:
throw new PhutilProxyException(
pht(
'Unexpected failure while destroying lease ("%s").',
$lease->getPHID()),
$ex);
}
$lease
->setStatus(DrydockLeaseStatus::STATUS_BROKEN)
->save();
$lease->scheduleUpdate();
$lease->logEvent(
DrydockLeaseActivationFailureLogType::LOGCONST,
array(
'class' => get_class($ex),
'message' => $ex->getMessage(),
));
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Permanent failure while activating lease ("%s"): %s',
$lease->getPHID(),
$ex->getMessage()));
}
/* -( Destroying Leases )-------------------------------------------------- */
/**
* @task destroy
*/
private function destroyLease(DrydockLease $lease) {
$resource = $lease->getResource();
if ($resource) {
$blueprint = $resource->getBlueprint();
$blueprint->destroyLease($resource, $lease);
}
DrydockSlotLock::releaseLocks($lease->getPHID());
$lease
->setStatus(DrydockLeaseStatus::STATUS_DESTROYED)
->save();
$lease->logEvent(DrydockLeaseDestroyedLogType::LOGCONST);
}
}

View file

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

View file

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

View file

@ -1,5 +1,13 @@
<?php
/**
* @task update Updating Resources
* @task command Processing Commands
* @task activate Activating Resources
* @task release Releasing Resources
* @task break Breaking Resources
* @task destroy Destroying Resources
*/
final class DrydockResourceUpdateWorker extends DrydockWorker {
protected function doWork() {
@ -11,22 +19,112 @@ final class DrydockResourceUpdateWorker extends DrydockWorker {
$lock = PhabricatorGlobalLock::newLock($lock_key)
->lock(1);
$resource = $this->loadResource($resource_phid);
$this->updateResource($resource);
try {
$resource = $this->loadResource($resource_phid);
$this->handleUpdate($resource);
} catch (Exception $ex) {
$lock->unlock();
throw $ex;
}
$lock->unlock();
}
/* -( Updating Resources )------------------------------------------------- */
/**
* Update a resource, handling exceptions thrown during the update.
*
* @param DrydockReosource Resource to update.
* @return void
* @task update
*/
private function handleUpdate(DrydockResource $resource) {
try {
$this->updateResource($resource);
} catch (Exception $ex) {
if ($this->isTemporaryException($ex)) {
$this->yieldResource($resource, $ex);
} else {
$this->breakResource($resource, $ex);
}
}
}
/**
* Update a resource.
*
* @param DrydockResource Resource to update.
* @return void
* @task update
*/
private function updateResource(DrydockResource $resource) {
$this->processResourceCommands($resource);
$resource_status = $resource->getStatus();
switch ($resource_status) {
case DrydockResourceStatus::STATUS_PENDING:
$this->activateResource($resource);
break;
case DrydockResourceStatus::STATUS_ACTIVE:
// Nothing to do.
break;
case DrydockResourceStatus::STATUS_RELEASED:
case DrydockResourceStatus::STATUS_BROKEN:
$this->destroyResource($resource);
break;
case DrydockResourceStatus::STATUS_DESTROYED:
// Nothing to do.
break;
}
$this->yieldIfExpiringResource($resource);
}
/**
* Convert a temporary exception into a yield.
*
* @param DrydockResource Resource to yield.
* @param Exception Temporary exception worker encountered.
* @task update
*/
private function yieldResource(DrydockResource $resource, Exception $ex) {
$duration = $this->getYieldDurationFromException($ex);
$resource->logEvent(
DrydockResourceActivationYieldLogType::LOGCONST,
array(
'duration' => $duration,
));
throw new PhabricatorWorkerYieldException($duration);
}
/* -( Processing Commands )------------------------------------------------ */
/**
* @task command
*/
private function processResourceCommands(DrydockResource $resource) {
if (!$resource->canReceiveCommands()) {
return;
}
$this->checkResourceExpiration($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.
if (!$resource->canReceiveCommands()) {
break;
}
$this->processCommand($resource, $command);
$this->processResourceCommand($resource, $command);
$command
->setIsConsumed(true)
@ -34,7 +132,11 @@ final class DrydockResourceUpdateWorker extends DrydockWorker {
}
}
private function processCommand(
/**
* @task command
*/
private function processResourceCommand(
DrydockResource $resource,
DrydockCommand $command) {
@ -45,24 +147,53 @@ final class DrydockResourceUpdateWorker extends DrydockWorker {
}
}
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;
/* -( Activating Resources )----------------------------------------------- */
/**
* @task activate
*/
private function activateResource(DrydockResource $resource) {
$blueprint = $resource->getBlueprint();
$blueprint->activateResource($resource);
$this->validateActivatedResource($blueprint, $resource);
}
/**
* @task activate
*/
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()'));
}
}
/* -( Releasing Resources )------------------------------------------------ */
/**
* @task release
*/
private function releaseResource(DrydockResource $resource) {
$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();
$resource
->setStatus(DrydockResourceStatus::STATUS_RELEASED)
->save();
$statuses = array(
DrydockLeaseStatus::STATUS_PENDING,
@ -86,14 +217,65 @@ final class DrydockResourceUpdateWorker extends DrydockWorker {
$lease->scheduleUpdate();
}
PhabricatorWorker::scheduleTask(
'DrydockResourceDestroyWorker',
array(
'resourcePHID' => $resource->getPHID(),
),
array(
'objectPHID' => $resource->getPHID(),
));
$this->destroyResource($resource);
}
/* -( Breaking Resources )------------------------------------------------- */
/**
* @task break
*/
private function breakResource(DrydockResource $resource, Exception $ex) {
switch ($resource->getStatus()) {
case DrydockResourceStatus::STATUS_BROKEN:
case DrydockResourceStatus::STATUS_RELEASED:
case DrydockResourceStatus::STATUS_DESTROYED:
// If the resource was already broken, just throw a normal exception.
// This will retry the task eventually.
throw new PhutilProxyException(
pht(
'Unexpected failure while destroying resource ("%s").',
$resource->getPHID()),
$ex);
}
$resource
->setStatus(DrydockResourceStatus::STATUS_BROKEN)
->save();
$resource->scheduleUpdate();
$resource->logEvent(
DrydockResourceActivationFailureLogType::LOGCONST,
array(
'class' => get_class($ex),
'message' => $ex->getMessage(),
));
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Permanent failure while activating resource ("%s"): %s',
$resource->getPHID(),
$ex->getMessage()));
}
/* -( Destroying Resources )----------------------------------------------- */
/**
* @task destroy
*/
private function destroyResource(DrydockResource $resource) {
$blueprint = $resource->getBlueprint();
$blueprint->destroyResource($resource);
DrydockSlotLock::releaseLocks($resource->getPHID());
$resource
->setStatus(DrydockResourceStatus::STATUS_DESTROYED)
->save();
}
}

View file

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

View file

@ -50,4 +50,109 @@ abstract class DrydockWorker extends PhabricatorWorker {
return $commands;
}
protected function checkLeaseExpiration(DrydockLease $lease) {
$this->checkObjectExpiration($lease);
}
protected function checkResourceExpiration(DrydockResource $resource) {
$this->checkObjectExpiration($resource);
}
private function checkObjectExpiration($object) {
// Check if the resource or lease has expired. If it has, we're going to
// send it a release command.
// This command is sent from within the update worker so it is handled
// immediately, but doing this generates a log and improves consistency.
$expires = $object->getUntil();
if (!$expires) {
return;
}
$now = PhabricatorTime::getNow();
if ($expires > $now) {
return;
}
$viewer = $this->getViewer();
$drydock_phid = id(new PhabricatorDrydockApplication())->getPHID();
$command = DrydockCommand::initializeNewCommand($viewer)
->setTargetPHID($object->getPHID())
->setAuthorPHID($drydock_phid)
->setCommand(DrydockCommand::COMMAND_RELEASE)
->save();
}
protected function yieldIfExpiringLease(DrydockLease $lease) {
if (!$lease->canReceiveCommands()) {
return;
}
$this->yieldIfExpiring($lease->getUntil());
}
protected function yieldIfExpiringResource(DrydockResource $resource) {
if (!$resource->canReceiveCommands()) {
return;
}
$this->yieldIfExpiring($resource->getUntil());
}
private function yieldIfExpiring($expires) {
if (!$expires) {
return;
}
if (!$this->getTaskDataValue('isExpireTask')) {
return;
}
$now = PhabricatorTime::getNow();
throw new PhabricatorWorkerYieldException($expires - $now);
}
protected function isTemporaryException(Exception $ex) {
if ($ex instanceof PhabricatorWorkerYieldException) {
return true;
}
if ($ex instanceof DrydockSlotLockException) {
return true;
}
if ($ex instanceof PhutilAggregateException) {
$any_temporary = false;
foreach ($ex->getExceptions() as $sub) {
if ($this->isTemporaryException($sub)) {
$any_temporary = true;
break;
}
}
if ($any_temporary) {
return true;
}
}
if ($ex instanceof PhutilProxyException) {
return $this->isTemporaryException($ex->getPreviousException());
}
return false;
}
protected function getYieldDurationFromException(Exception $ex) {
if ($ex instanceof PhabricatorWorkerYieldException) {
return $ex->getDuration();
}
if ($ex instanceof DrydockSlotLockException) {
return 5;
}
return 15;
}
}

View file

@ -3,10 +3,20 @@
final class PhabricatorFileTemporaryGarbageCollector
extends PhabricatorGarbageCollector {
public function collectGarbage() {
const COLLECTORCONST = 'files.ttl';
public function getCollectorName() {
return pht('Files (TTL)');
}
public function hasAutomaticPolicy() {
return true;
}
protected function collectGarbage() {
$files = id(new PhabricatorFile())->loadAllWhere(
'ttl < %d LIMIT 100',
time());
PhabricatorTime::getNow());
foreach ($files as $file) {
$file->delete();

View file

@ -43,35 +43,7 @@ abstract class HarbormasterArtifact extends Phobject {
}
final public function getArtifactConstant() {
$class = new ReflectionClass($this);
$const = $class->getConstant('ARTIFACTCONST');
if ($const === false) {
throw new Exception(
pht(
'"%s" class "%s" must define a "%s" property.',
__CLASS__,
get_class($this),
'ARTIFACTCONST'));
}
$limit = self::getArtifactConstantByteLimit();
if (!is_string($const) || (strlen($const) > $limit)) {
throw new Exception(
pht(
'"%s" class "%s" has an invalid "%s" property. Action constants '.
'must be strings and no more than %s bytes in length.',
__CLASS__,
get_class($this),
'ARTIFACTCONST',
new PhutilNumber($limit)));
}
return $const;
}
final public static function getArtifactConstantByteLimit() {
return 32;
return $this->getPhobjectClassConstant('ARTIFACTCONST', 32);
}
final public static function getAllArtifactTypes() {

View file

@ -62,7 +62,7 @@ final class HarbormasterPlanRunController extends HarbormasterController {
if (!$errors) {
$buildable->save();
$buildable->applyPlan($plan);
$buildable->applyPlan($plan, array());
$buildable_uri = '/B'.$buildable->getID();
return id(new AphrontRedirectResponse())->setURI($buildable_uri);

View file

@ -72,9 +72,9 @@ final class HarbormasterStepEditController extends HarbormasterController {
$e_name = true;
$v_name = $step->getName();
$e_description = true;
$e_description = null;
$v_description = $step->getDescription();
$e_depends_on = true;
$e_depends_on = null;
$v_depends_on = $step->getDetail('dependsOn', array());
$errors = array();
@ -82,9 +82,7 @@ final class HarbormasterStepEditController extends HarbormasterController {
if ($request->isFormPost()) {
$e_name = null;
$v_name = $request->getStr('name');
$e_description = null;
$v_description = $request->getStr('description');
$e_depends_on = null;
$v_depends_on = $request->getArr('dependsOn');
$xactions = $field_list->buildFieldTransactionsFromRequest(
@ -139,6 +137,12 @@ final class HarbormasterStepEditController extends HarbormasterController {
->setError($e_name)
->setValue($v_name));
$form->appendChild(id(new AphrontFormDividerControl()));
$field_list->appendFieldsToForm($form);
$form->appendChild(id(new AphrontFormDividerControl()));
$form
->appendControl(
id(new AphrontFormTokenizerControl())
@ -152,8 +156,6 @@ final class HarbormasterStepEditController extends HarbormasterController {
->setError($e_depends_on)
->setValue($v_depends_on));
$field_list->appendFieldsToForm($form);
$form
->appendChild(
id(new PhabricatorRemarkupControl())

View file

@ -72,4 +72,8 @@ final class HarbormasterBuildStepCoreCustomField
return;
}
public function getBuildTargetFieldValue() {
return $this->getProxy()->getFieldValue();
}
}

View file

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

View file

@ -0,0 +1,37 @@
<?php
/**
* Structure used to ask Harbormaster to start a build.
*
* Requests to start builds sometimes originate several layers away from where
* they are processed. For example, Herald rules which start builds pass the
* requests through the adapter and then through the editor before they reach
* Harbormaster.
*
* This class is just a thin wrapper around these requests so we can make them
* more complex later without needing to rewrite any APIs.
*/
final class HarbormasterBuildRequest extends Phobject {
private $buildPlanPHID;
private $buildParameters = array();
public function setBuildPlanPHID($build_plan_phid) {
$this->buildPlanPHID = $build_plan_phid;
return $this;
}
public function getBuildPlanPHID() {
return $this->buildPlanPHID;
}
public function setBuildParameters(array $build_parameters) {
$this->buildParameters = $build_parameters;
return $this;
}
public function getBuildParameters() {
return $this->buildParameters;
}
}

View file

@ -206,7 +206,7 @@ final class HarbormasterTargetEngine extends Phobject {
// resource and "own" it, so we don't try to handle this, but may need
// to be more careful here if use of autotargets expands.
$build = $buildable->applyPlan($plan);
$build = $buildable->applyPlan($plan, array());
PhabricatorWorker::setRunAllTasksInProcess(false);
} catch (Exception $ex) {
PhabricatorWorker::setRunAllTasksInProcess(false);

View file

@ -4,8 +4,9 @@ interface HarbormasterBuildableAdapterInterface {
public function getHarbormasterBuildablePHID();
public function getHarbormasterContainerPHID();
public function getQueuedHarbormasterBuildPlanPHIDs();
public function queueHarbormasterBuildPlanPHID($phid);
public function getQueuedHarbormasterBuildRequests();
public function queueHarbormasterBuildRequest(
HarbormasterBuildRequest $request);
}

View file

@ -31,7 +31,9 @@ final class HarbormasterRunBuildPlansHeraldAction
$phids = array_fuse(array_keys($targets));
foreach ($phids as $phid) {
$adapter->queueHarbormasterBuildPlanPHID($phid);
$request = id(new HarbormasterBuildRequest())
->setBuildPlanPHID($phid);
$adapter->queueHarbormasterBuildRequest($request);
}
$this->logEffect(self::DO_BUILD, $phids);

View file

@ -89,7 +89,7 @@ final class HarbormasterManagementBuildWorkflow
PhabricatorEnv::getProductionURI('/B'.$buildable->getID()));
PhabricatorWorker::setRunAllTasksInProcess(true);
$buildable->applyPlan($plan);
$buildable->applyPlan($plan, array());
$console->writeOut("%s\n", pht('Done.'));

View file

@ -6,6 +6,16 @@
abstract class HarbormasterBuildStepImplementation extends Phobject {
private $settings;
private $currentWorkerTaskID;
public function setCurrentWorkerTaskID($id) {
$this->currentWorkerTaskID = $id;
return $this;
}
public function getCurrentWorkerTaskID() {
return $this->currentWorkerTaskID;
}
public static function getImplementations() {
return id(new PhutilClassMapQuery())
@ -184,7 +194,7 @@ abstract class HarbormasterBuildStepImplementation extends Phobject {
* @return string String with variables replaced safely into it.
*/
protected function mergeVariables($function, $pattern, array $variables) {
$regexp = '/\\$\\{(?P<name>[a-z\\.]+)\\}/';
$regexp = '@\\$\\{(?P<name>[a-z\\./-]+)\\}@';
$matches = null;
preg_match_all($regexp, $pattern, $matches);

View file

@ -75,6 +75,12 @@ final class HarbormasterHTTPRequestBuildStepImplementation
list($status, $body, $headers) = $future->resolve();
$header_lines = array();
// TODO: We don't currently preserve the entire "HTTP" response header, but
// should. Once we do, reproduce it here faithfully.
$status_code = $status->getStatusCode();
$header_lines[] = "HTTP {$status_code}";
foreach ($headers as $header) {
list($head, $tail) = $header;
$header_lines[] = "{$head}: {$tail}";
@ -89,7 +95,7 @@ final class HarbormasterHTTPRequestBuildStepImplementation
->newLog($uri, 'http.body')
->append($body);
if ($status->getStatusCode() != 200) {
if ($status->isError()) {
throw new HarbormasterBuildFailureException();
}
}

View file

@ -45,14 +45,14 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
->setResourceType($working_copy_type)
->setOwnerPHID($build_target->getPHID());
$variables = $build_target->getVariables();
$map = $this->buildRepositoryMap($build_target);
$repository_phid = idx($variables, 'repository.phid');
$commit = idx($variables, 'repository.commit');
$lease->setAttribute('repositories.map', $map);
$lease
->setAttribute('repositoryPHID', $repository_phid)
->setAttribute('commit', $commit);
$task_id = $this->getCurrentWorkerTaskID();
if ($task_id) {
$lease->setAwakenTaskIDs(array($task_id));
}
$lease->queueForActivation();
@ -100,7 +100,87 @@ final class HarbormasterLeaseWorkingCopyBuildStepImplementation
'type' => 'text',
'required' => true,
),
'repositoryPHIDs' => array(
'name' => pht('Also Clone'),
'type' => 'datasource',
'datasource.class' => 'DiffusionRepositoryDatasource',
),
);
}
private function buildRepositoryMap(HarbormasterBuildTarget $build_target) {
$viewer = PhabricatorUser::getOmnipotentUser();
$variables = $build_target->getVariables();
$repository_phid = idx($variables, 'repository.phid');
if (!$repository_phid) {
throw new Exception(
pht(
'Unable to determine how to clone the repository for this '.
'buildable: it is not associated with a tracked repository.'));
}
$also_phids = $build_target->getFieldValue('repositoryPHIDs');
$all_phids = $also_phids;
$all_phids[] = $repository_phid;
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withPHIDs($all_phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($all_phids as $phid) {
if (empty($repositories[$phid])) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Unable to load repository with PHID "%s".',
$phid));
}
}
$map = array();
foreach ($also_phids as $also_phid) {
$also_repo = $repositories[$also_phid];
$map[$also_repo->getCloneName()] = array(
'phid' => $also_repo->getPHID(),
'branch' => 'master',
);
}
$repository = $repositories[$repository_phid];
$commit = idx($variables, 'buildable.commit');
$ref_uri = idx($variables, 'repository.staging.uri');
$ref_ref = idx($variables, 'repository.staging.ref');
if ($commit) {
$spec = array(
'commit' => $commit,
);
} else if ($ref_uri && $ref_ref) {
$spec = array(
'ref' => array(
'uri' => $ref_uri,
'ref' => $ref_ref,
),
);
} else {
throw new Exception(
pht(
'Unable to determine how to fetch changes: this buildable does not '.
'identify a commit or a staging ref. You may need to configure a '.
'repository staging area.'));
}
$directory = $repository->getCloneName();
$map[$directory] = array(
'phid' => $repository->getPHID(),
'default' => true,
) + $spec;
return $map;
}
}

View file

@ -14,19 +14,7 @@ abstract class HarbormasterBuildStepGroup extends Phobject {
}
final public function getGroupKey() {
$class = new ReflectionClass($this);
$const = $class->getConstant('GROUPKEY');
if ($const === false) {
throw new Exception(
pht(
'"%s" class "%s" must define a "%s" property.',
__CLASS__,
get_class($this),
'GROUPKEY'));
}
return $const;
return $this->getPhobjectClassConstant('GROUPKEY');
}
final public static function getAllGroups() {

View file

@ -96,15 +96,21 @@ final class HarbormasterBuildable extends HarbormasterDAO
}
/**
* Looks up the plan PHIDs and applies the plans to the specified
* object identified by it's PHID.
* Start builds for a given buildable.
*
* @param phid PHID of the object to build.
* @param phid Container PHID for the buildable.
* @param list<HarbormasterBuildRequest> List of builds to perform.
* @return void
*/
public static function applyBuildPlans(
$phid,
$container_phid,
array $plan_phids) {
array $requests) {
if (!$plan_phids) {
assert_instances_of($requests, 'HarbormasterBuildRequest');
if (!$requests) {
return;
}
@ -116,31 +122,49 @@ final class HarbormasterBuildable extends HarbormasterDAO
return;
}
$viewer = PhabricatorUser::getOmnipotentUser();
$buildable = self::createOrLoadExisting(
PhabricatorUser::getOmnipotentUser(),
$viewer,
$phid,
$container_phid);
$plan_phids = mpull($requests, 'getBuildPlanPHID');
$plans = id(new HarbormasterBuildPlanQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->setViewer($viewer)
->withPHIDs($plan_phids)
->execute();
foreach ($plans as $plan) {
$plans = mpull($plans, null, 'getPHID');
foreach ($requests as $request) {
$plan_phid = $request->getBuildPlanPHID();
$plan = idx($plans, $plan_phid);
if (!$plan) {
throw new Exception(
pht(
'Failed to load build plan ("%s").',
$plan_phid));
}
if ($plan->isDisabled()) {
// TODO: This should be communicated more clearly -- maybe we should
// create the build but set the status to "disabled" or "derelict".
continue;
}
$buildable->applyPlan($plan);
$parameters = $request->getBuildParameters();
$buildable->applyPlan($plan, $parameters);
}
}
public function applyPlan(HarbormasterBuildPlan $plan) {
public function applyPlan(HarbormasterBuildPlan $plan, array $parameters) {
$viewer = PhabricatorUser::getOmnipotentUser();
$build = HarbormasterBuild::initializeNewBuild($viewer)
->setBuildablePHID($this->getPHID())
->setBuildPlanPHID($plan->getPHID())
->setBuildParameters($parameters)
->setBuildStatus(HarbormasterBuild::STATUS_PENDING);
$auto_key = $plan->getPlanAutoKey();

View file

@ -9,6 +9,7 @@ final class HarbormasterBuild extends HarbormasterDAO
protected $buildPlanPHID;
protected $buildStatus;
protected $buildGeneration;
protected $buildParameters = array();
protected $planAutoKey;
private $buildable = self::ATTACHABLE;
@ -156,6 +157,9 @@ final class HarbormasterBuild extends HarbormasterDAO
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'buildParameters' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'buildStatus' => 'text32',
'buildGeneration' => 'uint32',
@ -258,6 +262,10 @@ final class HarbormasterBuild extends HarbormasterDAO
'build.id' => null,
);
foreach ($this->getBuildParameters() as $key => $value) {
$results['build/'.$key] = $value;
}
$buildable = $this->getBuildable();
$object = $buildable->getBuildableObject();

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