SAAS-1229 - initial work on bandwidth limit within Airtime; overhaul TaskFactory to get tasks reflectively

This commit is contained in:
Duncan Sommerville 2015-11-19 16:08:25 -05:00
parent c328515f4b
commit 6c2d1f008b
4 changed files with 126 additions and 32 deletions

View File

@ -3,8 +3,7 @@
/** /**
* Class TaskManager * Class TaskManager
* *
* When adding a new task, the new AirtimeTask class will also need to be added * Background class for 'asynchronous' task management for Airtime stations
* as a class constant and to the array in TaskFactory
*/ */
final class TaskManager { final class TaskManager {
@ -34,8 +33,8 @@ final class TaskManager {
* Private constructor so class is uninstantiable * Private constructor so class is uninstantiable
*/ */
private function __construct() { private function __construct() {
foreach (array_keys(TaskFactory::$tasks) as $v) { foreach (TaskFactory::getTasks() as $k => $task) {
$this->_taskList[$v] = false; $this->_taskList[$task] = false;
} }
} }
@ -278,12 +277,12 @@ class ImportCleanupTask implements AirtimeTask {
/** /**
* Class StationPodcastTask * Class StationPodcastTask
* *
* Checks the Station podcast rollover timer and resets monthly allotted * Checks the Station podcast rollover timer and resets allotted
* downloads if enough time has passed (default: 1 month) * downloads if enough time has passed (default: 1 month)
*/ */
class StationPodcastTask implements AirtimeTask { class StationPodcastTask implements AirtimeTask {
const STATION_PODCAST_RESET_TIMER_SECONDS = 2.628e+6; // 1 month XXX: should we use datetime roll for this instead? const STATION_PODCAST_RESET_TIMER_SECONDS = 2.628e+6; // 1 month
/** /**
* Check whether or not the download counter for the station podcast should be reset * Check whether or not the download counter for the station podcast should be reset
@ -305,32 +304,74 @@ class StationPodcastTask implements AirtimeTask {
} }
/**
* TODO: this and the above StationPodcastTask should probably be unified since the
* behaviour is essentially identical
*
* Class BandwidthLimitTask
*
* Checks the bandwidth limit rollover timer and resets the allotted
* limit if enough time has passed (default: 1 month)
*/
class BandwidthLimitTask implements AirtimeTask {
const BANDWIDTH_LIMIT_RESET_TIMER_SECONDS = 2.628e+6;
/**
* Check whether the task should be run
*
* @return bool true if the task needs to be run, otherwise false
*/
public function shouldBeRun() {
$lastReset = Application_Model_Preference::getBandwidthLimitResetTimer();
return empty($lastReset) || (microtime(true) > ($lastReset + self::BANDWIDTH_LIMIT_RESET_TIMER_SECONDS));
}
/**
* Run the task
*
* @return void
*/
public function run() {
Application_Model_Preference::resetStationPodcastDownloadCounter();
Application_Model_Preference::setBandwidthLimitResetTimer(microtime(true));
}
}
/** /**
* Class TaskFactory Factory class to abstract task instantiation * Class TaskFactory Factory class to abstract task instantiation
*/ */
class TaskFactory { class TaskFactory {
/** /**
* PHP doesn't have ENUMs so declare them as constants * Check if the class with the given name implements AirtimeTask
* Task types - values don't really matter as long as they're unique *
* @param $c string class name
*
* @return bool true if the class $c implements AirtimeTask
*/ */
private static function _isTask($c) {
const UPGRADE = "upgrade"; $reflect = new ReflectionClass($c);
const CELERY = "celery"; $clazz = version_compare(phpversion(), '5.5.0', '<') ? "AirtimeTask" : AirtimeTask::class;
const PODCAST = "podcast"; return $reflect->implementsInterface($clazz);
const IMPORT = "import"; }
const STATION_PODCAST = "station-podcast";
/** /**
* @var array map of arbitrary identifiers to class names to be instantiated reflectively * Filter all declared classes to get all classes implementing the AirtimeTask interface
*
* @return array all classes implementing the AirtimeTask interface
*/ */
public static $tasks = array( private static function _getTaskClasses() {
"upgrade" => "UpgradeTask", return array_filter(get_declared_classes(), array(__CLASS__, "_isTask"));
"celery" => "CeleryTask", }
"podcast" => "PodcastTask",
"import" => "ImportCleanupTask", /**
"station-podcast" => "StationPodcastTask", * @return array
); */
public static function getTasks() {
return self::_getTaskClasses();
}
/** /**
* Get an AirtimeTask based on a task type * Get an AirtimeTask based on a task type
@ -341,7 +382,7 @@ class TaskFactory {
* task exists or is implemented * task exists or is implemented
*/ */
public static function getTask($task) { public static function getTask($task) {
return new self::$tasks[$task](); return new $task();
} }
} }

View File

@ -11,6 +11,8 @@ class ApiController extends Zend_Controller_Action
public function init() public function init()
{ {
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
//Ignore API key and session authentication for these APIs: //Ignore API key and session authentication for these APIs:
$ignoreAuth = array("live-info", $ignoreAuth = array("live-info",
@ -101,14 +103,6 @@ class ApiController extends Zend_Controller_Action
exit(); exit();
} }
public function pollCeleryAction() {
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
$taskManager = TaskManager::getInstance();
$taskManager->runTask(TaskFactory::CELERY);
}
public function versionAction() public function versionAction()
{ {
$this->_helper->json->sendJson( array( $this->_helper->json->sendJson( array(
@ -136,6 +130,35 @@ class ApiController extends Zend_Controller_Action
$this->_helper->json->sendJson(array()); $this->_helper->json->sendJson(array());
} }
/**
* Manually trigger the TaskManager task to poll for completed Celery tasks
*/
public function pollCeleryAction() {
$taskManager = TaskManager::getInstance();
$clazz = version_compare(phpversion(), '5.5.0', '<') ? get_class(new CeleryTask) : CeleryTask::class;
$taskManager->runTask($clazz);
}
/**
* TODO: move this function into a more generic analytics REST controller
*
* Update station bandwidth usage based on icecast log data
*/
public function bandwidthUsageAction() {
// FIXME: this function is using placeholder names
$bandwidthUsage = json_decode($this->getRequest()->getParam("bandwidth_usage"));
$usageBytes = 0;
foreach ($bandwidthUsage as $entry) {
Logging::info($entry);
// TODO: store the IP address for future use
$ts = strtotime($entry->timestamp);
if ($ts > Application_Model_Preference::getBandwidthLimitUpdateTimer()) {
$usageBytes += $entry->bytes;
}
}
Application_Model_Preference::incrementBandwidthLimitCounter($usageBytes);
}
//Used by the SaaS monitoring //Used by the SaaS monitoring
public function onAirLightAction() public function onAirLightAction()
{ {

View File

@ -64,7 +64,8 @@ class PageLayoutInitPlugin extends Zend_Controller_Plugin_Abstract
// We can't afford to wait 7 minutes to run an upgrade: users could // We can't afford to wait 7 minutes to run an upgrade: users could
// have several minutes of database errors while waiting for a // have several minutes of database errors while waiting for a
// schema change upgrade to happen after a deployment // schema change upgrade to happen after a deployment
$taskManager->runTask(TaskFactory::UPGRADE); $clazz = version_compare(phpversion(), '5.5.0', '<') ? get_class(new UpgradeTask) : UpgradeTask::class;
$taskManager->runTask($clazz);
// Piggyback the TaskManager onto API calls. This provides guaranteed consistency // Piggyback the TaskManager onto API calls. This provides guaranteed consistency
// (there is at least one API call made from pypo to Airtime every 7 minutes) and // (there is at least one API call made from pypo to Airtime every 7 minutes) and

View File

@ -1584,4 +1584,33 @@ class Application_Model_Preference
public static function setStationPodcastPrivacy($value) { public static function setStationPodcastPrivacy($value) {
self::setValue("station_podcast_privacy", $value); self::setValue("station_podcast_privacy", $value);
} }
public static function getBandwidthLimitCounter() {
return self::getValue("bandwidth_limit_counter");
}
public static function incrementBandwidthLimitCounter($value) {
$counter = intval(self::getValue("bandwidth_limit_counter"));
self::setValue("bandwidth_limit_counter", $counter + intval($value));
}
public static function resetBandwidthLimitCounter() {
self::setValue("bandwidth_limit_counter", 0);
}
public static function getBandwidthLimitResetTimer() {
return self::getValue("bandwidth_limit_reset_timer");
}
public static function setBandwidthLimitResetTimer($value) {
self::setValue("bandwidth_limit_reset_timer", $value);
}
public static function getBandwidthLimitUpdateTimer() {
return self::getValue("bandwidth_limit_reset_timer");
}
public static function setBandwidthLimitUpdateTimer() {
self::setValue("bandwidth_limit_reset_timer", microtime(true));
}
} }