UsageUpdate.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. <?php
  2. /* * ********************************************************************
  3. * ProxmoxVPS product developed. (Apr 19, 2019)
  4. * *
  5. *
  6. * CREATED BY MODULESGARDEN -> http://modulesgarden.com
  7. * CONTACT -> contact@modulesgarden.com
  8. *
  9. *
  10. * This software is furnished under a license and may be used and copied
  11. * only in accordance with the terms of such license and with the
  12. * inclusion of the above copyright notice. This software or any other
  13. * copies thereof may not be provided or otherwise made available to any
  14. * other person. No title to and ownership of the software is hereby
  15. * transferred.
  16. *
  17. *
  18. * ******************************************************************** */
  19. namespace ModulesGarden\ProxmoxAddon\App\Cron;
  20. use MGProvision\Proxmox\v2\VmFactory;
  21. use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\NetworkRate\MaximumVmRateJob;
  22. use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\NetworkRate\MinimumVmRateJob;
  23. use ModulesGarden\ProxmoxAddon\App\Models\ModuleSetting;
  24. use ModulesGarden\ProxmoxAddon\App\Models\ProductConfiguration;
  25. use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
  26. use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
  27. use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
  28. use ModulesGarden\ProxmoxAddon\App\Services\Utility;
  29. use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
  30. use ModulesGarden\ProxmoxAddon\Core\Api\Whmcs;
  31. use ModulesGarden\ProxmoxAddon\Core\CommandLine\Command;
  32. use ModulesGarden\ProxmoxAddon\Core\CommandLine\Hypervisor;
  33. use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Admins;
  34. use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Server;
  35. use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
  36. use Symfony\Component\Console\Input\InputInterface;
  37. use Symfony\Component\Console\Output\OutputInterface;
  38. use Symfony\Component\Console\Style\SymfonyStyle;
  39. use function ModulesGarden\ProxmoxAddon\Core\Helper\queue;
  40. use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
  41. use ModulesGarden\ProxmoxAddon\App\Enum\Vps;
  42. use ModulesGarden\ProxmoxAddon\App\Enum\Cloud;
  43. /**
  44. * Description of Users
  45. *
  46. * @author Pawel Kopec <pawelk@modulesgardne.com>
  47. */
  48. class UsageUpdate extends Command
  49. {
  50. use WhmcsParams;
  51. use ProductService;
  52. use ApiService;
  53. /**
  54. * Command name
  55. * @var string
  56. */
  57. protected $name = 'update-server-usage';
  58. /**
  59. * Command description
  60. * @var string
  61. */
  62. protected $description = 'Updating Disk & Bandwidth Usage Stat';
  63. /**
  64. * Command help text
  65. * @var string
  66. */
  67. protected $help = '';
  68. protected function process(InputInterface $input, OutputInterface $output, SymfonyStyle $io)
  69. {
  70. $io->title('Update Server Usage: Starting');
  71. if (!function_exists('ModuleBuildParams'))
  72. {
  73. require_once ROOTDIR . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . "modulefunctions.php";
  74. }
  75. //Update hosting date 0000-00-00 00:00:00 to now
  76. $this->updateEmptyLastUpdate();
  77. //init bandwidth reset date to run daily cron job
  78. $this->updateEmptyBandwidthResetDate();
  79. //Run daily
  80. if($this->isDailyJob()){
  81. //Reset BW
  82. $this->resetBandwidth();
  83. $this->unsuspendOverageUsage();
  84. $this->endDailyJob();
  85. }
  86. //Update hosting bandwidth
  87. $h = (new Hosting())->getTable();
  88. $s = (new Server())->getTable();
  89. $hostings = Hosting::select("{$h}.id", "{$h}.packageid", "{$h}.lastupdate", "{$h}.bwusage", "{$h}.bwlimit", "{$h}.regdate")
  90. ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
  91. ->where("{$h}.domainstatus", "Active")
  92. ->whereIn("{$s}.type", ["proxmoxVPS", "ProxmoxCloudVps"])
  93. ->orderBy("{$h}.server");
  94. $i = 0;
  95. /**
  96. * @var Hosting $hosting
  97. */
  98. foreach ($hostings->get() as $hosting)
  99. {
  100. $i++;
  101. $output->writeln(sprintf("Synchronize hosting: %s", $hosting->id));
  102. try
  103. {
  104. $params = \ModuleBuildParams($hosting->id);
  105. sl("whmcsParams")->setParams($params);
  106. unset($this->vm, $this->api, $this->configuration, $this->productId, $this->hostingId);
  107. $this->api();
  108. if ($params['moduletype'] == "proxmoxVPS"){
  109. if (!$params['customfields'][Vps\CustomField::VMID])
  110. {
  111. throw new \Exception("Custom Field \"vmid\" is empty");
  112. }
  113. if (!$params['customfields'][Vps\CustomField::NODE])
  114. {
  115. throw new \Exception("Custom Field \"node\" is empty");
  116. }
  117. $status = $this->vm()->status();
  118. $rrData = $this->vm()->rrdData(["timeframe" => "hour", "cf" => "AVERAGE"]);
  119. $bandwidth = 0;
  120. foreach ($rrData as $k => $rrd)
  121. {
  122. if ($rrd['time'] < $hosting->getLastUpdate()->getTimestamp())
  123. {
  124. continue;
  125. }
  126. $total = ((float)$rrd['netin'] + (float)$rrd['netout']);
  127. if ($total > 1000000000)
  128. {
  129. $total = 0;
  130. }
  131. $bandwidth += ($total * 57.3);
  132. $bandwithTime = date("Y-m-d H:i:s", $rrd['time']);
  133. }
  134. if (!$bandwithTime)
  135. {
  136. continue;
  137. }
  138. $diskUsage = (int)$status['disk'];
  139. Utility::unitFormat($bandwidth, "bytes", "mb");
  140. Utility::unitFormat($diskUsage, "bytes", "mb");
  141. $hosting->bwusage += $bandwidth;
  142. $hosting->update(
  143. [
  144. "diskusage" => $diskUsage,
  145. "bwusage" => $hosting->bwusage,
  146. "lastupdate" => $bandwithTime
  147. ]
  148. );
  149. $rate = null;
  150. if ($this->getWhmcsConfigOption(Vps\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) && $this->getWhmcsConfigOption(Vps\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) !="-1")
  151. {
  152. $rate = $this->getWhmcsConfigOption(Vps\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate());
  153. }
  154. //Minimum VM Network Rate
  155. if ($hosting->isBandwidthOverageUsage() && $this->configuration()->getMinimumRate() &&
  156. $this->vm()->getNetworkDevices($this->configuration()->getBridge())[0] &&
  157. $this->vm()->getNetworkDevices($this->configuration()->getBridge())[0]->getRate() != $this->configuration()->getMinimumRate())
  158. {
  159. queue(MinimumVmRateJob::class, [], null, "hosting", $hosting->id);
  160. //Maximum VM Network Rate
  161. } else if (!$hosting->isBandwidthOverageUsage() &&
  162. $this->configuration()->getMinimumRate() &&
  163. $this->vm()->getNetworkDevices($this->configuration()->getBridge())[0] &&
  164. $this->vm()->getNetworkDevices($this->configuration()->getBridge())[0]->getRate() != $rate) {
  165. queue(MaximumVmRateJob::class, [], null, "hosting", $hosting->id);
  166. }
  167. //Suspend On Bandwidth Overage
  168. if ($this->configuration()->isSuspendOnBandwidthOverage() && $hosting->domainstatus != "Suspended" && $hosting->isBandwidthOverageUsage())
  169. {
  170. $localApi = new Whmcs(new Admins());
  171. $localApi->call("modulesuspend", ["accountid" => $hosting->id, "suspendreason" => "Overage Usage"]);
  172. $output->writeln(sprintf("Hosting: %s has been suspended, reanson: Overage Usage", $hosting->id));
  173. }
  174. $output->writeln(sprintf("Hosting: %s has been synchronized, bandwith usage: %s MB", $hosting->id, $bandwidth));
  175. }
  176. if ($params['moduletype'] == "ProxmoxCloudVps"){
  177. $bandwidth = 0;
  178. $diskUsage = 0;
  179. $rate = null;
  180. if ($this->getWhmcsConfigOption(Cloud\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) && $this->getWhmcsConfigOption(Cloud\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate()) !="-1")
  181. {
  182. $rate = $this->getWhmcsConfigOption(Cloud\ConfigurableOption::NETWORK_RATE, $this->configuration()->getRate());
  183. }
  184. $vservers = VmModel::ofHostingId($hosting->id);
  185. foreach ($vservers->get() as $vserver)
  186. {
  187. $vm = (new VmFactory())->fromVmModel($vserver);
  188. $status = $vm->status();
  189. $diskUsage += (int)$status['disk'];
  190. $rrData = $vm->rrdData(["timeframe" => "hour", "cf" => "AVERAGE"]);
  191. foreach ($rrData as $k => $rrd)
  192. {
  193. if ($rrd['time'] < $hosting->getLastUpdate()->getTimestamp())
  194. {
  195. continue;
  196. }
  197. $total = ((float)$rrd['netin'] + (float)$rrd['netout']);
  198. if ($total > 1000000000)
  199. {
  200. $total = 0;
  201. }
  202. $bandwidth += ($total * 57.3);
  203. $bandwithTime = date("Y-m-d H:i:s", $rrd['time']);
  204. }
  205. if (!$bandwithTime)
  206. {
  207. continue;
  208. }
  209. //Minimum VM Network Rate
  210. if ($hosting->isBandwidthOverageUsage() && $this->configuration()->getMinimumRate() &&
  211. $vm->getNetworkDevices($this->configuration()->getBridge())[0] &&
  212. $vm->getNetworkDevices($this->configuration()->getBridge())[0]->getRate() != $this->configuration()->getMinimumRate())
  213. {
  214. queue(\ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\NetworkRate\MinimumVmRateJob::class, [], null, "hosting", $hosting->id, $vserver->id);
  215. //Maximum VM Network Rate
  216. } else if (!$hosting->isBandwidthOverageUsage() &&
  217. $this->configuration()->getMinimumRate() &&
  218. $vm->getNetworkDevices($this->configuration()->getBridge())[0] &&
  219. $vm->getNetworkDevices($this->configuration()->getBridge())[0]->getRate() != $rate) {
  220. queue(\ModulesGarden\ProxmoxAddon\App\Jobs\Cloud\NetworkRate\MaximumVmRateJob::class, [], null, "hosting", $hosting->id, $vserver->id);
  221. }
  222. }
  223. Utility::unitFormat($bandwidth, "bytes", "mb");
  224. Utility::unitFormat($diskUsage, "bytes", "mb");
  225. $hosting->bwusage += $bandwidth;
  226. $hosting->update(
  227. [
  228. "diskusage" => $diskUsage,
  229. "bwusage" => $hosting->bwusage,
  230. "lastupdate" => $bandwithTime
  231. ]
  232. );
  233. //Suspend On Bandwidth Overage
  234. if ($this->configuration()->isSuspendOnBandwidthOverage() && $hosting->domainstatus != "Suspended" && $hosting->isBandwidthOverageUsage())
  235. {
  236. $localApi = new Whmcs(new Admins());
  237. $localApi->call("modulesuspend", ["accountid" => $hosting->id, "suspendreason" => "Overage Usage"]);
  238. $output->writeln(sprintf("Hosting: %s has been suspended, reanson: Overage Usage", $hosting->id));
  239. }
  240. $output->writeln(sprintf("Hosting: %s has been synchronized, bandwith usage: %s MB", $hosting->id, $bandwidth));
  241. }
  242. }
  243. catch (\Exception $ex)
  244. {
  245. $msg = $ex->getMessage();
  246. if ($hosting)
  247. {
  248. $msg = sprintf("Hosting Id #%s, %s", $hosting->id, $ex->getMessage());
  249. }
  250. $io->error($msg);
  251. }
  252. (new Hypervisor($this->getName(), $input->getOptions()))
  253. ->ping();
  254. }
  255. $output->writeln("");
  256. $io->success([
  257. sprintf("Synchronize hostings: %s Entries Processed.", $i),
  258. "Update Server Usage: Done"
  259. ]);
  260. }
  261. private function updateEmptyLastUpdate()
  262. {
  263. $h = 'tblhosting';
  264. Hosting::ofProxmoxVpsAndProxmoxCloud()
  265. ->where("{$h}.domainstatus", 'Active')
  266. ->where("{$h}.lastupdate", "0000-00-00 00:00:00")
  267. ->update(["{$h}.lastupdate" => Hosting::raw('NOW()')]);
  268. return $this;
  269. }
  270. private function updateEmptyBandwidthResetDate()
  271. {
  272. if (ModuleSetting::bandwidthResetDate()->count())
  273. {
  274. return $this;
  275. }
  276. $setting = new ModuleSetting();
  277. $setting->setting = "bandwidthResetDate";
  278. $setting->value = date("Y-m-d H:i:s");
  279. $setting->save();
  280. return $this;
  281. }
  282. private function isDailyJob(){
  283. return ModuleSetting::bandwidthResetDate()->whereRaw("DATE(`value`) <= DATE_SUB(NOW(),INTERVAL 24 HOUR) ")->count() > 0;
  284. }
  285. private function endDailyJob(){
  286. $setting = ModuleSetting::bandwidthResetDate()->firstOrFail();
  287. // save last rest bandwidth date
  288. $setting->value = date("Y-m-d H:i:s");
  289. $setting->save();
  290. }
  291. private function resetBandwidth()
  292. {
  293. $productIds = ProductConfiguration::ofSetting('resetUsageFirstDayOfMonth')
  294. ->where("value", "like", '\"on\"')
  295. ->pluck("product_id")
  296. ->all();
  297. $h = 'tblhosting';
  298. Hosting::ofProxmoxVpsAndProxmoxCloud()
  299. ->whereIn("{$h}.domainstatus", ["Active", "Suspended"])
  300. ->whereNotIn("{$h}.packageid", $productIds)
  301. ->whereRaw(" DAY({$h}.regdate) = DAY(NOW()) ")
  302. ->update(["{$h}.bwusage" => 0]);
  303. //Reset Usage First Day Of Month
  304. if(date("d")=="01"){
  305. Hosting::ofProxmoxVpsAndProxmoxCloud()
  306. ->whereIn("{$h}.domainstatus", ["Active", "Suspended"])
  307. ->whereIn("{$h}.packageid", $productIds)
  308. ->update(["{$h}.bwusage" => 0]);
  309. }
  310. }
  311. private function unsuspendOverageUsage(){
  312. //get products where option suspendOnBandwidthOverage is on
  313. $productIds = ProductConfiguration::ofSetting('suspendOnBandwidthOverage')
  314. ->where("value", "like", '\"on\"')
  315. ->pluck("product_id")
  316. ->all();
  317. if(empty( $productIds)){
  318. return;
  319. }
  320. //get hosting where domainstatus is Suspended and registration day is now and suspendreason is Overage Usage
  321. $h = (new Hosting())->getTable();
  322. $s = (new Server())->getTable();
  323. $hostings = Hosting::select("{$h}.id", "{$h}.packageid", "{$h}.lastupdate", "{$h}.bwusage", "{$h}.bwlimit", "{$h}.regdate")
  324. ->rightJoin($s, "{$h}.server", '=', "{$s}.id")
  325. ->where("{$h}.domainstatus", "Suspended")
  326. ->where("{$h}.suspendreason", "Overage Usage")
  327. ->whereIn("{$h}.packageid", $productIds)
  328. ->where("{$s}.type", "proxmoxVPS")
  329. ->whereRaw(" DAY({$h}.regdate) = DAY(NOW()) ");
  330. $i = 0;
  331. /**
  332. * @var Hosting $hosting
  333. */
  334. foreach ($hostings->get() as $hosting)
  335. {
  336. try{
  337. $localApi = new Whmcs(new Admins());
  338. $localApi->call("ModuleUnsuspend", ["accountid" => $hosting->id]);
  339. } catch (\Exception $ex)
  340. {
  341. echo sprintf("Error: %s, Hosting ID: #%s",$ex->getMessage(), $hosting->id);
  342. }
  343. }
  344. }
  345. }