PageRenderTime 40ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/branches/1.2.0/cron-ng/jobs/Scaling.php

http://scalr.googlecode.com/
PHP | 297 lines | 209 code | 45 blank | 43 comment | 36 complexity | fb5bba6dea8445db6cb06a0c2cf04efa MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, GPL-3.0
  1. <?php
  2. class Scalr_Cronjob_Scaling extends Scalr_System_Cronjob_MultiProcess_DefaultWorker
  3. {
  4. static function getConfig () {
  5. return array(
  6. "description" => "Scaling process",
  7. "processPool" => array(
  8. "daemonize" => true,
  9. "preventParalleling" => true,
  10. "size" => 5
  11. ),
  12. //"distributed" => true,
  13. //"iniFile" => dirname(dirname(__FILE__)) . "/distributed.ini",
  14. //"electorCls" => "Scalr_System_Cronjob_Distributed_DataCenterElector",
  15. //"leaderTimeout" => 120000, // 2min
  16. "fileName" => __FILE__,
  17. "memoryLimit" => 128000
  18. );
  19. }
  20. private $logger;
  21. private $db;
  22. public function __construct() {
  23. $this->logger = LoggerManager::getLogger(__CLASS__);
  24. $this->db = Core::GetDBInstance();
  25. }
  26. function startForking ($workQueue) {
  27. // Reopen DB connection after daemonizing
  28. $this->db = Core::GetDBInstance(null, true);
  29. }
  30. function startChild () {
  31. // Reopen DB connection in child
  32. $this->db = Core::GetDBInstance(null, true);
  33. // Reconfigure observers;
  34. Scalr::ReconfigureObservers();
  35. }
  36. function enqueueWork ($workQueue)
  37. {
  38. $this->logger->info("Fetching completed farms...");
  39. $rows = $this->db->GetAll("SELECT farms.*, clients.isactive FROM farms
  40. INNER JOIN clients ON clients.id = farms.clientid WHERE clients.isactive='1' AND farms.status=?",
  41. array(FARM_STATUS::RUNNING)
  42. );
  43. $this->logger->info("Found ".count($rows)." farms.");
  44. foreach ($rows as $row)
  45. {
  46. $workQueue->put($row["id"]);
  47. }
  48. }
  49. function handleWork ($farmId)
  50. {
  51. $farminfo = $this->db->GetRow("SELECT farms.*, clients.isactive FROM farms
  52. INNER JOIN clients ON clients.id = farms.clientid WHERE farms.id = ?", array($farmId));
  53. $GLOBALS["SUB_TRANSACTIONID"] = abs(crc32(posix_getpid().$farmId));
  54. $GLOBALS["LOGGER_FARMID"] = $farminfo["id"];
  55. $this->logger->info("[{$GLOBALS["SUB_TRANSACTIONID"]}] Begin polling farm (ID: {$farminfo['id']}, Name: {$farminfo['name']}, Status: {$farminfo['status']})");
  56. $farm_roles = $this->db->GetAll("SELECT ami_id, id, replace_to_ami FROM farm_roles WHERE farmid=? ORDER BY launch_index ASC", array($farminfo['id']));
  57. $Client = Client::Load($farminfo['clientid']);
  58. // Get AmazonEC2 Object
  59. $AmazonEC2Client = AmazonEC2::GetInstance(AWSRegions::GetAPIURL($farminfo['region']));
  60. $AmazonEC2Client->SetAuthKeys($Client->AWSPrivateKey, $Client->AWSCertificate);
  61. $farminfo = $this->db->GetRow("SELECT * FROM farms WHERE id=?", array($farminfo['id']));
  62. if ($farminfo['status'] != FARM_STATUS::RUNNING)
  63. {
  64. $this->logger->warn("[FarmID: {$farminfo['id']}] Farm terminated. There is no need to scale it.");
  65. return;
  66. }
  67. foreach ($farm_roles as $farm_ami)
  68. {
  69. $DBFarmRole = DBFarmRole::LoadByID($farm_ami['id']);
  70. if ($DBFarmRole->ReplaceToAMI != '')
  71. {
  72. $this->logger->warn("[FarmID: {$farminfo['id']}] Role '{$DBFarmRole->GetRoleName()}' being synchronized. This role will not be scalled.");
  73. continue;
  74. }
  75. // Get polling interval in seconds
  76. $polling_interval = $DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_POLLING_INTERVAL)*60;
  77. $dt_last_polling = $DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_LAST_POLLING_TIME);
  78. if ($dt_last_polling && $dt_last_polling+$polling_interval > time())
  79. {
  80. $this->logger->info("Polling interval: every {$polling_interval} seconds");
  81. continue;
  82. }
  83. // Set Last polling time
  84. $DBFarmRole->SetSetting(DBFarmRole::SETTING_SCALING_LAST_POLLING_TIME, time());
  85. // Get current count of running and pending instances.
  86. $this->logger->info(sprintf("Processing role '%s'", $DBFarmRole->GetRoleName()));
  87. $RoleScalingManager = new RoleScalingManager($DBFarmRole);
  88. foreach ($RoleScalingManager->GetEnabledAlgos() as $ScalingAlgo)
  89. {
  90. $this->logger->info(sprintf("Checking %s scaling algorithm...", get_class($ScalingAlgo)));
  91. $res = $ScalingAlgo->MakeDecision($DBFarmRole);
  92. $this->logger->info(sprintf("%s result: %s", get_class($ScalingAlgo), $res));
  93. if ($res == ScalingAlgo::STOP_SCALING)
  94. {
  95. exit();
  96. }
  97. if ($res == ScalingAlgo::NOOP)
  98. {
  99. //TODO:
  100. }
  101. elseif ($res == ScalingAlgo::DOWNSCALE)
  102. {
  103. /*
  104. Timeout instance's count decrease. Decreases instance?s count after scaling
  105. resolution ?the spare instances are running? for selected timeout interval
  106. from scaling EditOptions
  107. */
  108. // We have to check timeout limits before new scaling (downscaling) process will be initiated
  109. if($DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_DOWNSCALE_TIMEOUT_ENABLED))
  110. { // if the farm timeout is exceeded
  111. // checking timeout interval.
  112. $last_down_scale_data_time = strtotime($DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_DOWNSCALE_DATETIME, false));
  113. $timeout_interval = $DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_DOWNSCALE_TIMEOUT);
  114. // check the time interval to continue scaling or cancel it...
  115. if(time() - $last_down_scale_data_time < $timeout_interval*60)
  116. {
  117. // if the launch time is too small to terminate smth in this role -> go to the next role in foreach()
  118. $this->logger->info(new FarmLogMessage($farminfo['id'],
  119. sprintf("The running time is too small to terminate any instance in farm %s, role %s",
  120. $farminfo['name'],
  121. $instanceinfo['role_name']
  122. )
  123. ));
  124. continue;
  125. }
  126. } // end Timeout instance's count decrease
  127. $sort = ($DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_KEEP_OLDEST) == 1) ? 'DESC' : 'ASC';
  128. $instances = $this->db->GetAll("SELECT * FROM farm_instances WHERE state = ? AND farm_roleid=? ORDER BY dtadded {$sort}",
  129. array(INSTANCE_STATE::RUNNING, $DBFarmRole->ID)
  130. );
  131. $got_valid_instance = false;
  132. // Select instance that will be terminated
  133. //
  134. // * Instances ordered by uptime (oldest wil be choosen)
  135. // * Instance cannot be mysql master
  136. // * Choose the one that was rebundled recently
  137. while (!$got_valid_instance && count($instances) > 0)
  138. {
  139. $item = array_shift($instances);
  140. $instanceinfo = $item;
  141. // Exclude db master
  142. if ($instanceinfo["isdbmaster"] != 1)
  143. {
  144. /*
  145. * We do not want to delete the most recently synced instance. Because of LA fluctuation.
  146. * I.e. LA may skyrocket during sync and drop dramatically after sync.
  147. */
  148. if ($instanceinfo["dtlastsync"] != 0)
  149. {
  150. $chk_sync_time = $this->db->GetOne("SELECT id FROM farm_instances
  151. WHERE dtlastsync > {$instanceinfo['dtlastsync']}
  152. AND farm_roleid='{$instanceinfo['farm_roleid']}' AND state != '".INSTANCE_STATE::TERMINATED."'");
  153. if ($chk_sync_time)
  154. $got_valid_instance = true;
  155. }
  156. else
  157. $got_valid_instance = true;
  158. }
  159. }
  160. if ($instanceinfo && $got_valid_instance)
  161. {
  162. $this->logger->info(sprintf("Instance '%s' selected for termination...", $instanceinfo['instance_id']));
  163. $allow_terminate = false;
  164. // Shutdown an instance just before a full hour running
  165. $response = $AmazonEC2Client->DescribeInstances($instanceinfo['instance_id']);
  166. if ($response && $response->reservationSet->item)
  167. {
  168. $launch_time = strtotime($response->reservationSet->item->instancesSet->item->launchTime);
  169. $time = 3600 - (time() - $launch_time) % 3600;
  170. // Terminate instance in < 10 minutes for full hour.
  171. if ($time <= 600)
  172. $allow_terminate = true;
  173. else
  174. {
  175. $timeout = round(($time - 600) / 60, 1);
  176. $this->logger->info(new FarmLogMessage($farminfo['id'], sprintf("Farm %s, role %s scaling down. Instance '%s' will be terminated in %s minutes. Launch time: %s",
  177. $farminfo['name'],
  178. $instanceinfo['role_name'],
  179. $instanceinfo['instance_id'],
  180. $timeout,
  181. $response->reservationSet->item->instancesSet->item->launchTime
  182. )));
  183. }
  184. }
  185. //
  186. if ($allow_terminate)
  187. {
  188. try
  189. {
  190. $this->logger->info(new FarmLogMessage($farminfo['id'],
  191. sprintf("Scheduled termination for instance %s (%s). It will be terminated in 3 minutes.",
  192. $instanceinfo["instance_id"],
  193. $instanceinfo["external_ip"]
  194. )
  195. ));
  196. Scalr::FireEvent($farminfo['id'], new BeforeHostTerminateEvent(DBInstance::LoadByID($instanceinfo['id'], false)));
  197. }
  198. catch (Exception $e)
  199. {
  200. $this->logger->fatal(sprintf("Cannot terminate %s: %s",
  201. $farminfo['id'],
  202. $instanceinfo['instance_id'],
  203. $e->getMessage()
  204. ));
  205. }
  206. }
  207. }
  208. else
  209. $this->logger->warn(sprintf("Scalr unable to determine what instance it should terminate. Skipping..."));
  210. break;
  211. }
  212. elseif ($res == ScalingAlgo::UPSCALE)
  213. {
  214. /*
  215. Timeout instance's count increase. Increases instance's count after
  216. scaling resolution ?need more instances? for selected timeout interval
  217. from scaling EditOptions
  218. */
  219. if($DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_UPSCALE_TIMEOUT_ENABLED))
  220. {
  221. // if the farm timeout is exceeded
  222. // checking timeout interval.
  223. $last_up_scale_data_time = strtotime($DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_UPSCALE_DATETIME, false));
  224. $timeout_interval = $DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_UPSCALE_TIMEOUT);
  225. // check the time interval to continue scaling or cancel it...
  226. if(time() - $last_up_scale_data_time < $timeout_interval*60)
  227. {
  228. // if the launch time is too small to terminate smth in this role -> go to the next role in foreach()
  229. $this->logger->info(new FarmLogMessage($farminfo['id'],
  230. sprintf("The last scaling time interval is too small to start a new instance in farm %s, role %s",
  231. $farminfo['name'],
  232. $instanceinfo['role_name']
  233. )
  234. ));
  235. continue;
  236. }
  237. }// end Timeout instance's count increase
  238. $farminfo = $this->db->GetRow("SELECT * FROM farms WHERE id=?", array($farminfo['id']));
  239. if ($farminfo['status'] != FARM_STATUS::RUNNING)
  240. {
  241. $this->logger->warn("[FarmID: {$farminfo['id']}] Farm terminated. There is no need to scale it.");
  242. break;
  243. }
  244. $instance_id = Scalr::RunInstance($DBFarmRole, false, false, true);
  245. if ($instance_id)
  246. $this->logger->info(new FarmLogMessage($farminfo['id'], sprintf("Starting new instance. InstanceID = %s.", $instance_id)));
  247. break;
  248. }
  249. }
  250. }
  251. }
  252. }