1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Add a "daily routine" trigger clock for backups, etc.

Summary: Ref T6881. Before implementing subscriptions, I'm going to vet triggers by using them to do backups. Each instance will get a daily trigger for backups, and that should give us a smaller-scale test to catch issues and limitations, with more opportunities for something to go wrong since it fires more often.

Test Plan: Added unit tests.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T6881

Differential Revision: https://secure.phabricator.com/D11427
This commit is contained in:
epriestley 2015-01-19 16:54:23 -08:00
parent 53b06408f4
commit a988a1a043
4 changed files with 219 additions and 66 deletions

View file

@ -8,10 +8,10 @@
return array(
'names' => array(
'core.pkg.css' => '8d1c0f87',
'core.pkg.js' => 'd60876a7',
'core.pkg.js' => '7923c2e6',
'darkconsole.pkg.js' => '8ab24e01',
'differential.pkg.css' => '8af45893',
'differential.pkg.js' => 'f437e70e',
'differential.pkg.js' => 'dad3622f',
'diffusion.pkg.css' => '591664fa',
'diffusion.pkg.js' => 'bfc0737b',
'maniphest.pkg.css' => 'e34dfbec',
@ -166,10 +166,10 @@ return array(
'rsrc/externals/javelin/core/__tests__/install.js' => 'c432ee85',
'rsrc/externals/javelin/core/__tests__/stratcom.js' => '88bf7313',
'rsrc/externals/javelin/core/__tests__/util.js' => 'e251703d',
'rsrc/externals/javelin/core/init.js' => '8c4e8f8b',
'rsrc/externals/javelin/core/init_node.js' => 'c234aded',
'rsrc/externals/javelin/core/init.js' => '76e1fd61',
'rsrc/externals/javelin/core/init_node.js' => '77350e4d',
'rsrc/externals/javelin/core/install.js' => '05270951',
'rsrc/externals/javelin/core/util.js' => '93cc50d6',
'rsrc/externals/javelin/core/util.js' => 'bdcfee9e',
'rsrc/externals/javelin/docs/Base.js' => '74676256',
'rsrc/externals/javelin/docs/onload.js' => 'e819c479',
'rsrc/externals/javelin/ext/fx/Color.js' => '7e41274a',
@ -186,11 +186,11 @@ return array(
'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '6c2b09a2',
'rsrc/externals/javelin/ext/view/ViewVisitor.js' => 'efe49472',
'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => 'f92d7bcb',
'rsrc/externals/javelin/ext/view/__tests__/View.js' => '6450b38b',
'rsrc/externals/javelin/ext/view/__tests__/View.js' => 'bda69c40',
'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5',
'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '6ea96ac9',
'rsrc/externals/javelin/lib/Cookie.js' => '62dfea03',
'rsrc/externals/javelin/lib/DOM.js' => 'c8fd8db2',
'rsrc/externals/javelin/lib/Cookie.js' => '6b3dcf44',
'rsrc/externals/javelin/lib/DOM.js' => 'c4569c05',
'rsrc/externals/javelin/lib/History.js' => 'c60f4327',
'rsrc/externals/javelin/lib/JSON.js' => '69adf288',
'rsrc/externals/javelin/lib/Leader.js' => '9330f91b',
@ -215,7 +215,7 @@ return array(
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '8b3fd187',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '2818f5ce',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => 'e3b841c8',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '316b8fa1',
'rsrc/externals/raphael/g.raphael.js' => '40dde778',
'rsrc/externals/raphael/g.raphael.line.js' => '40da039e',
@ -362,7 +362,7 @@ return array(
'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d',
'rsrc/js/application/differential/behavior-comment-preview.js' => '6932def3',
'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1',
'rsrc/js/application/differential/behavior-dropdown-menus.js' => 'e33d4bc5',
'rsrc/js/application/differential/behavior-dropdown-menus.js' => '3bc14668',
'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '00861799',
'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492',
'rsrc/js/application/differential/behavior-populate.js' => 'bdb3e4d0',
@ -410,7 +410,7 @@ return array(
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
'rsrc/js/application/releeph/releeph-request-state-change.js' => '3a1a4060',
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'ab836011',
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f',
'rsrc/js/application/repository/repository-crossreference.js' => 'f9539603',
'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08',
@ -444,7 +444,7 @@ return array(
'rsrc/js/core/ShapedRequest.js' => '7cbe244b',
'rsrc/js/core/TextAreaUtils.js' => '5c93c52c',
'rsrc/js/core/Title.js' => '5c1c758c',
'rsrc/js/core/ToolTip.js' => '1d298e3a',
'rsrc/js/core/ToolTip.js' => '031d4411',
'rsrc/js/core/behavior-active-nav.js' => 'e379b58e',
'rsrc/js/core/behavior-audio-source.js' => '59b251eb',
'rsrc/js/core/behavior-autofocus.js' => '7319e029',
@ -564,7 +564,7 @@ return array(
'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18',
'javelin-behavior-differential-comment-jump' => '4fdb476d',
'javelin-behavior-differential-diff-radios' => 'e1ff79b1',
'javelin-behavior-differential-dropdown-menus' => 'e33d4bc5',
'javelin-behavior-differential-dropdown-menus' => '3bc14668',
'javelin-behavior-differential-edit-inline-comments' => '00861799',
'javelin-behavior-differential-feedback-preview' => '6932def3',
'javelin-behavior-differential-keyboard-navigation' => '2c426492',
@ -635,7 +635,7 @@ return array(
'javelin-behavior-project-create' => '065227cc',
'javelin-behavior-refresh-csrf' => '7814b593',
'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf',
'javelin-behavior-releeph-request-state-change' => '3a1a4060',
'javelin-behavior-releeph-request-state-change' => 'ab836011',
'javelin-behavior-releeph-request-typeahead' => 'de2e896f',
'javelin-behavior-remarkup-preview' => 'f7379f45',
'javelin-behavior-reorder-applications' => '76b9fc3e',
@ -650,9 +650,9 @@ return array(
'javelin-behavior-view-placeholder' => '47830651',
'javelin-behavior-workflow' => '0a3f3021',
'javelin-color' => '7e41274a',
'javelin-cookie' => '62dfea03',
'javelin-cookie' => '6b3dcf44',
'javelin-diffusion-locate-file-source' => 'b42eddc7',
'javelin-dom' => 'c8fd8db2',
'javelin-dom' => 'c4569c05',
'javelin-dynval' => 'f6555212',
'javelin-event' => '85ea0626',
'javelin-fx' => '54b612ba',
@ -660,7 +660,7 @@ return array(
'javelin-install' => '05270951',
'javelin-json' => '69adf288',
'javelin-leader' => '9330f91b',
'javelin-magical-init' => '8c4e8f8b',
'javelin-magical-init' => '76e1fd61',
'javelin-mask' => '8a41885b',
'javelin-reactor' => '2b8de964',
'javelin-reactor-dom' => 'c90a04fc',
@ -677,10 +677,10 @@ return array(
'javelin-typeahead-normalizer' => '6f7a9da8',
'javelin-typeahead-ondemand-source' => '8b3fd187',
'javelin-typeahead-preloaded-source' => '54f314a0',
'javelin-typeahead-source' => '2818f5ce',
'javelin-typeahead-source' => 'e3b841c8',
'javelin-typeahead-static-source' => '316b8fa1',
'javelin-uri' => '6eff08aa',
'javelin-util' => '93cc50d6',
'javelin-util' => 'bdcfee9e',
'javelin-vector' => 'cc1bd0b0',
'javelin-view' => '0f764c35',
'javelin-view-html' => 'fe287620',
@ -738,7 +738,7 @@ return array(
'phabricator-standard-page-view' => '2c96cfb5',
'phabricator-textareautils' => '5c93c52c',
'phabricator-title' => '5c1c758c',
'phabricator-tooltip' => '1d298e3a',
'phabricator-tooltip' => '031d4411',
'phabricator-transaction-view-css' => '5d0cae25',
'phabricator-ui-example-css' => '528b19de',
'phabricator-uiexample-javelin-view' => 'd4a14807',
@ -834,6 +834,12 @@ return array(
'029a133d' => array(
'aphront-dialog-view-css',
),
'031d4411' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
),
'03d6ed07' => array(
'javelin-behavior',
'javelin-stratcom',
@ -938,12 +944,6 @@ return array(
'javelin-util',
'phabricator-keyboard-shortcut-manager',
),
'1d298e3a' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
),
'1def2711' => array(
'javelin-install',
'javelin-dom',
@ -956,12 +956,6 @@ return array(
'javelin-workflow',
'javelin-util',
),
'2818f5ce' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'2926fff2' => array(
'javelin-behavior',
'javelin-dom',
@ -1029,14 +1023,6 @@ return array(
'javelin-json',
'phabricator-prefab',
),
'3a1a4060' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-keyboard-shortcut',
),
'3ab51e2c' => array(
'javelin-behavior',
'javelin-behavior-device',
@ -1045,6 +1031,18 @@ return array(
'javelin-dom',
'javelin-magical-init',
),
'3bc14668' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'phabricator-phtize',
'changeset-view-manager',
),
'3d51a746' => array(
'javelin-behavior',
'javelin-dom',
@ -1228,10 +1226,6 @@ return array(
'javelin-magical-init',
'javelin-util',
),
'62dfea03' => array(
'javelin-install',
'javelin-util',
),
'6453c869' => array(
'javelin-install',
'javelin-dom',
@ -1251,6 +1245,10 @@ return array(
'69adf288' => array(
'javelin-install',
),
'6b3dcf44' => array(
'javelin-install',
'javelin-util',
),
'6c2b09a2' => array(
'javelin-install',
'javelin-util',
@ -1562,6 +1560,14 @@ return array(
'javelin-util',
'phabricator-prefab',
),
'ab836011' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-keyboard-shortcut',
),
'ad7a69ca' => array(
'javelin-install',
'javelin-util',
@ -1650,6 +1656,13 @@ return array(
'javelin-util',
'phabricator-shaped-request',
),
'c4569c05' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
'c51ae228' => array(
'javelin-behavior',
'javelin-util',
@ -1663,13 +1676,6 @@ return array(
'javelin-uri',
'javelin-util',
),
'c8fd8db2' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
'c90a04fc' => array(
'javelin-dom',
'javelin-dynval',
@ -1788,18 +1794,6 @@ return array(
'javelin-workflow',
'javelin-vector',
),
'e33d4bc5' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'phabricator-phtize',
'changeset-view-manager',
),
'e379b58e' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1807,6 +1801,12 @@ return array(
'javelin-dom',
'javelin-uri',
),
'e3b841c8' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'e4cc26b3' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -1596,6 +1596,7 @@ phutil_register_library_map(array(
'PhabricatorDaemonTasksTableView' => 'applications/daemon/view/PhabricatorDaemonTasksTableView.php',
'PhabricatorDaemonsApplication' => 'applications/daemon/application/PhabricatorDaemonsApplication.php',
'PhabricatorDaemonsSetupCheck' => 'applications/config/check/PhabricatorDaemonsSetupCheck.php',
'PhabricatorDailyRoutineTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorDailyRoutineTriggerClock.php',
'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php',
'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php',
'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php',
@ -4801,6 +4802,7 @@ phutil_register_library_map(array(
'PhabricatorDaemonTasksTableView' => 'AphrontView',
'PhabricatorDaemonsApplication' => 'PhabricatorApplication',
'PhabricatorDaemonsSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDailyRoutineTriggerClock' => 'PhabricatorTriggerClock',
'PhabricatorDashboard' => array(
'PhabricatorDashboardDAO',
'PhabricatorApplicationTransactionInterface',

View file

@ -0,0 +1,57 @@
<?php
/**
* Triggers a daily routine, like server backups.
*
* This clock triggers events every 24 hours, using UTC. It does not use a
* locale, and is intended for technical processes like backing up a server
* every night.
*
* Because UTC does not have daylight savings, the local hour when this event
* occurs will change over the course of the year. For example, from the
* perspective of a user in California, it might run backups at 3AM in the
* winter and 2AM in the summer. This is desirable for maintenance processes,
* but problematic for some human processes. Use a different clock if you're
* triggering a human-oriented event.
*
* The clock uses the time of day of the `start` epoch to calculate the time
* of day of the next event, so you can change the time of day when the event
* occurs by adjusting the `start` time of day.
*/
final class PhabricatorDailyRoutineTriggerClock
extends PhabricatorTriggerClock {
public function validateProperties(array $properties) {
PhutilTypeSpec::checkMap(
$properties,
array(
'start' => 'int',
));
}
public function getNextEventEpoch($last_epoch, $is_reschedule) {
$start_epoch = $this->getProperty('start');
if (!$last_epoch) {
$last_epoch = $start_epoch;
}
$start = new DateTime('@'.$start_epoch);
$last = new DateTime('@'.$last_epoch);
// NOTE: We're choosing the date from the last event, but the time of day
// from the start event. This allows callers to change when the event
// occurs by updating the trigger's start parameter.
$ymd = $last->format('Y-m-d');
$hms = $start->format('G:i:s');
$next = new DateTime("{$ymd} {$hms} UTC");
// Add a day.
// NOTE: DateInterval doesn't exist until PHP 5.3.0, and we currently
// target PHP 5.2.3.
$next->modify('+1 day');
return (int)$next->format('U');
}
}

View file

@ -30,6 +30,90 @@ final class PhabricatorTriggerClockTestCase extends PhabricatorTestCase {
pht('Should never trigger.'));
}
public function testDailyRoutineTriggerClockDaylightSavings() {
// These dates are selected to cross daylight savings in PST; they should
// be unaffected.
$start = strtotime('2015-03-05 16:17:18 UTC');
$clock = new PhabricatorDailyRoutineTriggerClock(
array(
'start' => $start,
));
$expect_list = array(
'2015-03-06 16:17:18',
'2015-03-07 16:17:18',
'2015-03-08 16:17:18',
'2015-03-09 16:17:18',
'2015-03-10 16:17:18',
);
$this->expectClock($clock, $expect_list, pht('Daily Routine (PST)'));
}
public function testDailyRoutineTriggerClockLeapSecond() {
// These dates cross the leap second on June 30, 2012. There has never
// been a negative leap second, so we can't test that yet.
$start = strtotime('2012-06-28 23:59:59 UTC');
$clock = new PhabricatorDailyRoutineTriggerClock(
array(
'start' => $start,
));
$expect_list = array(
'2012-06-29 23:59:59',
'2012-06-30 23:59:59',
'2012-07-01 23:59:59',
'2012-07-02 23:59:59',
);
$this->expectClock($clock, $expect_list, pht('Daily Routine (Leap)'));
}
public function testCDailyRoutineTriggerClockAdjustTimeOfDay() {
// In this case, we're going to update the time of day on the clock and
// make sure it keeps track of the date but adjusts the time.
$start = strtotime('2015-01-15 6:07:08 UTC');
$clock = new PhabricatorDailyRoutineTriggerClock(
array(
'start' => $start,
));
$expect_list = array(
'2015-01-16 6:07:08',
'2015-01-17 6:07:08',
'2015-01-18 6:07:08',
);
$last_epoch = $this->expectClock(
$clock,
$expect_list,
pht('Daily Routine (Pre-Adjust)'));
// Now, change the time of day.
$new_start = strtotime('2015-01-08 1:23:45 UTC');
$clock = new PhabricatorDailyRoutineTriggerClock(
array(
'start' => $new_start,
));
$expect_list = array(
'2015-01-19 1:23:45',
'2015-01-20 1:23:45',
'2015-01-21 1:23:45',
);
$this->expectClock(
$clock,
$expect_list,
pht('Daily Routine (Post-Adjust)'),
$last_epoch);
}
public function testSubscriptionTriggerClock() {
$start = strtotime('2014-01-31 2:34:56 UTC');
@ -76,7 +160,15 @@ final class PhabricatorTriggerClockTestCase extends PhabricatorTestCase {
'2016-03-31 2:34:56',
);
$last_epoch = null;
$this->expectClock($clock, $expect_list, pht('Billing Cycle'));
}
private function expectClock(
PhabricatorTriggerClock $clock,
array $expect_list,
$clock_name,
$last_epoch = null) {
foreach ($expect_list as $cycle => $expect) {
$next_epoch = $clock->getNextEventEpoch(
$last_epoch,
@ -84,11 +176,13 @@ final class PhabricatorTriggerClockTestCase extends PhabricatorTestCase {
$this->assertEqual(
$expect,
id(new DateTime('@'.$next_epoch))->format('Y-m-d g:i:s'),
pht('Billing cycle %s.', $cycle));
id(new DateTime('@'.$next_epoch))->format('Y-m-d G:i:s'),
pht('%s (%s)', $clock_name, $cycle));
$last_epoch = $next_epoch;
}
return $last_epoch;
}
}