PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/branches/1.2.0/observers/class.ElasticIPsEventObserver.php

http://scalr.googlecode.com/
PHP | 360 lines | 267 code | 49 blank | 44 comment | 28 complexity | 8e693389e60f56dd83dc95246e7ba84a MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, GPL-3.0
  1. <?php
  2. class ElasticIPsEventObserver extends EventObserver
  3. {
  4. public $ObserverName = 'Elastic IPs';
  5. function __construct()
  6. {
  7. parent::__construct();
  8. $this->Crypto = Core::GetInstance("Crypto", CONFIG::$CRYPTOKEY);
  9. }
  10. /**
  11. * Return new instance of AmazonEC2 object
  12. *
  13. * @return AmazonEC2
  14. */
  15. private function GetAmazonEC2ClientObject($region)
  16. {
  17. // Get ClientID from database;
  18. $clientid = $this->DB->GetOne("SELECT clientid FROM farms WHERE id=?", array($this->FarmID));
  19. // Get Client Object
  20. $Client = Client::Load($clientid);
  21. // Return new instance of AmazonEC2 object
  22. $AmazonEC2Client = AmazonEC2::GetInstance(AWSRegions::GetAPIURL($region));
  23. $AmazonEC2Client->SetAuthKeys($Client->AWSPrivateKey, $Client->AWSCertificate);
  24. return $AmazonEC2Client;
  25. }
  26. /**
  27. * Release used elastic IPs if farm terminated
  28. *
  29. * @param FarmTerminatedEvent $event
  30. */
  31. public function OnFarmTerminated(FarmTerminatedEvent $event)
  32. {
  33. $this->Logger->info(sprintf(_("Keep elastic IPs: %s"), $event->KeepElasticIPs));
  34. if ($event->KeepElasticIPs == 1)
  35. return;
  36. $farminfo = $this->DB->GetRow("SELECT * FROM farms WHERE id=?", array($this->FarmID));
  37. $ips = $this->DB->GetAll("SELECT * FROM elastic_ips WHERE farmid=?", array($this->FarmID));
  38. if (count($ips) > 0)
  39. {
  40. $EC2Client = $this->GetAmazonEC2ClientObject($farminfo['region']);
  41. foreach ($ips as $ip)
  42. {
  43. try
  44. {
  45. $EC2Client->ReleaseAddress($ip["ipaddress"]);
  46. }
  47. catch(Exception $e)
  48. {
  49. if (!stristr($e->getMessage(), "does not belong to you"))
  50. {
  51. $this->Logger->error(sprintf(_("Cannot release elastic IP %s from farm %s: %s"),
  52. $ip['ipaddress'], $farminfo['name'], $e->getMessage()
  53. ));
  54. continue;
  55. }
  56. }
  57. $this->DB->Execute("DELETE FROM elastic_ips WHERE ipaddress=?", array($ip['ipaddress']));
  58. }
  59. }
  60. }
  61. /**
  62. * Check Elastic IP availability
  63. *
  64. */
  65. private function CheckElasticIP($ipaddress, $farminfo)
  66. {
  67. $EC2Client = $this->GetAmazonEC2ClientObject($farminfo['region']);
  68. $this->Logger->debug(sprintf(_("Checking IP: %s"), $ipaddress));
  69. $DescribeAddressesType = new DescribeAddressesType();
  70. $DescribeAddressesType->AddAddress($ipaddress);
  71. try
  72. {
  73. $info = $EC2Client->DescribeAddresses($DescribeAddressesType);
  74. if ($info && $info->addressesSet->item)
  75. return true;
  76. else
  77. return false;
  78. }
  79. catch(Exception $e)
  80. {
  81. return false;
  82. }
  83. }
  84. /**
  85. * Allocate and Assign Elastic IP to instance if role use it.
  86. *
  87. * @param HostUpEvent $event
  88. */
  89. public function OnHostUp(HostUpEvent $event)
  90. {
  91. $farminfo = $this->DB->GetRow("SELECT * FROM farms WHERE id=?", array($this->FarmID));
  92. $DBFarmRole = $event->DBInstance->GetDBFarmRoleObject();
  93. if (!$DBFarmRole->GetSetting(DBFarmRole::SETTING_AWS_USE_ELASIC_IPS))
  94. return;
  95. // Check for already allocated and free elastic IP in database
  96. $ip = $this->DB->GetRow("SELECT * FROM elastic_ips WHERE farmid=? AND ((farm_roleid=? AND instance_index='{$event->DBInstance->Index}') OR instance_id = ?)",
  97. array($this->FarmID, $DBFarmRole->ID, $event->DBInstance->InstanceID)
  98. );
  99. $this->Logger->debug(sprintf(_("IP for replace: %s"), $ip['ipaddress']));
  100. //
  101. // Check IP address
  102. //
  103. if ($ip['ipaddress'])
  104. {
  105. if (!$this->CheckElasticIP($ip['ipaddress'], $farminfo))
  106. {
  107. $this->Logger->warn(new FarmLogMessage(
  108. $this->FarmID,
  109. sprintf(_("Elastic IP '%s' does not belong to you. Allocating new one."),
  110. $ip['ipaddress']
  111. )
  112. ));
  113. $this->DB->Execute("DELETE FROM elastic_ips WHERE ipaddress=?", array($ip['ipaddress']));
  114. $ip = false;
  115. }
  116. }
  117. // If free IP not found we mus allocate new IP
  118. if (!$ip)
  119. {
  120. $this->Logger->debug(sprintf(_("Farm role: %s, %s, %s"),
  121. $DBFarmRole->GetRoleName(), $DBFarmRole->AMIID, $DBFarmRole->ID
  122. ));
  123. $alocated_ips = $this->DB->GetOne("SELECT COUNT(*) FROM elastic_ips WHERE farm_roleid=?",
  124. array($DBFarmRole->ID)
  125. );
  126. $this->Logger->debug(sprintf(_("Allocated IPs: %s, MaxInstances: %s"),
  127. $alocated_ips, $DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_MAX_INSTANCES)
  128. ));
  129. // Check elastic IPs limit. We cannot allocate more than 'Max instances' option for role
  130. if ($alocated_ips < $DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_MAX_INSTANCES))
  131. {
  132. $EC2Client = $this->GetAmazonEC2ClientObject($farminfo['region']);
  133. try
  134. {
  135. // Alocate new IP address
  136. $address = $EC2Client->AllocateAddress();
  137. }
  138. catch (Exception $e)
  139. {
  140. $this->Logger->error(new FarmLogMessage(
  141. $this->FarmID,
  142. sprintf(_("Cannot allocate new elastic ip for instance '%s': %s"),
  143. $event->DBInstance->InstanceID,
  144. $e->getMessage()
  145. )
  146. ));
  147. return;
  148. }
  149. // Add allocated IP address to database
  150. $this->DB->Execute("INSERT INTO elastic_ips SET farmid=?, farm_roleid=?, ipaddress=?, state='0', instance_id='', clientid=?, instance_index=?",
  151. array($this->FarmID, $DBFarmRole->ID, $address->publicIp, $farminfo['clientid'], $event->DBInstance->Index)
  152. );
  153. $ip['ipaddress'] = $address->publicIp;
  154. $this->Logger->info(new FarmLogMessage(
  155. $this->FarmID,
  156. sprintf(_("Allocated new IP: %s"),
  157. $ip['ipaddress']
  158. )
  159. ));
  160. // Waiting...
  161. $this->Logger->debug(_("Waiting 5 seconds..."));
  162. sleep(5);
  163. }
  164. else
  165. $this->Logger->fatal(_("Limit for elastic IPs reached. Check zomby records in database."));
  166. }
  167. // If we have ip address
  168. if ($ip['ipaddress'])
  169. {
  170. if (!$EC2Client)
  171. $EC2Client = $this->GetAmazonEC2ClientObject($farminfo['region']);
  172. $assign_retries = 1;
  173. try
  174. {
  175. while (true)
  176. {
  177. try
  178. {
  179. // Associate elastic ip address with instance
  180. $EC2Client->AssociateAddress($event->DBInstance->InstanceID, $ip['ipaddress']);
  181. }
  182. catch(Exception $e)
  183. {
  184. if (!stristr($e->getMessage(), "does not belong to you") || $assign_retries == 3)
  185. throw new Exception($e->getMessage());
  186. else
  187. {
  188. // Waiting...
  189. $this->Logger->debug(_("Waiting 2 seconds..."));
  190. sleep(2);
  191. $assign_retries++;
  192. continue;
  193. }
  194. }
  195. break;
  196. }
  197. }
  198. catch(Exception $e)
  199. {
  200. $this->Logger->error(new FarmLogMessage(
  201. $this->FarmID,
  202. sprintf(_("Cannot associate elastic ip with instance: %s"),
  203. $e->getMessage()
  204. )
  205. ));
  206. return;
  207. }
  208. $this->Logger->info("IP: {$ip['ipaddress']} assigned to instance '{$event->DBInstance->InstanceID}'");
  209. // Update leastic IPs table
  210. $this->DB->Execute("UPDATE elastic_ips SET state='1', instance_id=? WHERE ipaddress=?",
  211. array($event->DBInstance->InstanceID, $ip['ipaddress'])
  212. );
  213. // Update instance info in database
  214. $this->DB->Execute("UPDATE farm_instances SET external_ip=?, isipchanged='1', isactive='0' WHERE id=?",
  215. array($ip['ipaddress'], $event->DBInstance->ID)
  216. );
  217. Scalr::FireEvent($this->FarmID, new IPAddressChangedEvent($event->DBInstance, $ip['ipaddress']));
  218. }
  219. else
  220. {
  221. $this->Logger->fatal(new FarmLogMessage(
  222. $this->FarmID,
  223. sprintf(_("Cannot allocate elastic ip address for instance %s on farm %s"),
  224. $event->DBInstance->InstanceID,
  225. $farminfo['name']
  226. )
  227. ));
  228. }
  229. }
  230. /**
  231. * Release IP address when instance terminated
  232. *
  233. * @param HostDownEvent $event
  234. */
  235. public function OnHostDown(HostDownEvent $event)
  236. {
  237. if ($event->DBInstance->IsRebootLaunched == 1)
  238. return;
  239. $farminfo = $this->DB->GetRow("SELECT * FROM farms WHERE id=?", array($this->FarmID));
  240. try
  241. {
  242. $DBFarmRole = $event->DBInstance->GetDBFarmRoleObject();
  243. }
  244. catch(Exception $e)
  245. {
  246. //
  247. }
  248. if ($DBFarmRole)
  249. {
  250. // Count already allocate elastic IPS for role
  251. $alocated_ips = $this->DB->GetOne("SELECT COUNT(*) FROM elastic_ips WHERE farm_roleid=?",
  252. array($DBFarmRole->ID)
  253. );
  254. // If number of allocated IPs more than 'Max instances' option for role, we must release elastic IP
  255. if ($alocated_ips > $DBFarmRole->GetSetting(DBFarmRole::SETTING_SCALING_MAX_INSTANCES))
  256. {
  257. $ip = $this->DB->GetRow("SELECT * FROM elastic_ips WHERE instance_index=? AND farm_roleid=?",
  258. array($event->DBInstance->Index, $DBFarmRole->ID)
  259. );
  260. if ($ip['state'] == 0)
  261. {
  262. try
  263. {
  264. $EC2Client = $this->GetAmazonEC2ClientObject($farminfo['region']);
  265. $EC2Client->ReleaseAddress($ip['ipaddress']);
  266. $this->DB->Execute("DELETE FROM elastic_ips WHERE ipaddress=?", array($ip['ipaddress']));
  267. $this->Logger->warn(sprintf(_("Unused elastic IP address: %s released."), $ip['ipaddress']));
  268. }
  269. catch(Exception $e)
  270. {
  271. $this->Logger->error(new FarmLogMessage(
  272. $this->FarmID,
  273. sprintf(_("Cannot release unused elastic ip: %s"),
  274. $e->getMessage()
  275. )
  276. ));
  277. return;
  278. }
  279. }
  280. }
  281. }
  282. else
  283. {
  284. $ips = $this->DB->GetAll("SELECT * FROM elastic_ips WHERE farm_roleid=?", array($event->DBInstance->FarmRoleID));
  285. foreach ($ips as $ip)
  286. {
  287. if ($ip['ipaddress'])
  288. {
  289. try
  290. {
  291. $EC2Client = $this->GetAmazonEC2ClientObject($farminfo['region']);
  292. $EC2Client->ReleaseAddress($ip['ipaddress']);
  293. $this->DB->Execute("DELETE FROM elastic_ips WHERE ipaddress=?", array($ip['ipaddress']));
  294. $this->Logger->warn(sprintf(_("Unused elastic IP address: %s released."), $ip['ipaddress']));
  295. }
  296. catch(Exception $e)
  297. {
  298. $this->Logger->error(new FarmLogMessage(
  299. $this->FarmID,
  300. sprintf(_("Cannot release unused elastic ip: %s"),
  301. $e->getMessage()
  302. )
  303. ));
  304. return;
  305. }
  306. }
  307. }
  308. }
  309. }
  310. }
  311. ?>