LoadBalancerService.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <?php
  2. /* * ********************************************************************
  3. * ProxmoxVPS product developed. (Jan 16, 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\Services;
  20. use MGProvision\Proxmox\v2\models\Kvm;
  21. use MGProvision\Proxmox\v2\models\Lxc;
  22. use ModulesGarden\ProxmoxAddon\App\Models\NodeSetting;
  23. use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting;
  24. use ModulesGarden\ProxmoxAddon\Core\Models\ModuleSettings\Model as Settings;
  25. /**
  26. * Description of LoadBalancerService
  27. *
  28. * @author Pawel Kopec <pawelk@modulesgardne.com>
  29. */
  30. class LoadBalancerService
  31. {
  32. protected $nodesResurces = [];
  33. private $serverId;
  34. /**
  35. *
  36. * @var \MGProvision\Proxmox\v2\Api
  37. */
  38. private $api;
  39. private $usage = false;
  40. private $filter = [];
  41. private $excludeVmid;
  42. private $vmsWeight;
  43. private $ramWeight;
  44. private $diskWeight;
  45. private $cpuWeight;
  46. function __construct($serverId)
  47. {
  48. $this->serverId = $serverId;
  49. }
  50. public function getExcludeVmid()
  51. {
  52. return $this->excludeVmid;
  53. }
  54. public function setExcludeVmid($excludeVmid)
  55. {
  56. $this->excludeVmid = $excludeVmid;
  57. return $this;
  58. }
  59. public function findByNodes(array $nodes){
  60. $this->filter['nodes'] = $nodes;
  61. return $this;
  62. }
  63. /**
  64. * @return LoadBalancerService
  65. */
  66. public function findByVmCreate()
  67. {
  68. //Node settings
  69. $this->nodesResurces = [];
  70. $query = NodeSetting::ofServer($this->serverId);
  71. if($this->filter['nodes']){
  72. $query->ofNodes($this->filter['nodes']);
  73. }
  74. $query->ofSetting('vmCreate')->ofValue(1);
  75. foreach ($query->get() as $entity)
  76. {
  77. $this->nodesResurces[] = $this->format($entity);
  78. }
  79. return clone $this;
  80. }
  81. private function format(NodeSetting $entity)
  82. {
  83. $nodesResurce = [
  84. 'node' => $entity->node,
  85. 'vmCreate' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('vmCreate')->value('value'),
  86. 'vmsMax' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('vmsMax')->value('value'),
  87. 'vmsWeight' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('vmsWeight')->value('value'),
  88. 'vmsUsed' => 0,
  89. 'vmsFree' => null,
  90. 'cpuMax' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('cpuMax')->value('value'),
  91. 'cpuWeight' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('cpuWeight')->value('value'),
  92. 'cpuUsed' => 0,
  93. 'cpuFree' => null,
  94. 'diskUsed' => 0,
  95. 'diskMax' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('diskMax')->value('value'),
  96. 'diskWeight' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('diskWeight')->value('value'),
  97. 'diskFree' => null,
  98. 'ramMax' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('ramMax')->value('value'),
  99. 'ramWeight' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('ramWeight')->value('value'),
  100. 'ramUsed' => 0,
  101. 'ramFree' => 0,
  102. ];
  103. if ($nodesResurce['diskMax'])
  104. {
  105. $nodesResurce['diskMax'] = $nodesResurce['diskMax'] * pow(1024, 3);
  106. }
  107. if ($nodesResurce['ramMax'])
  108. {
  109. $nodesResurce['ramMax'] = $nodesResurce['ramMax'] * pow(1024, 3);
  110. }
  111. return $nodesResurce;
  112. }
  113. /**
  114. * @return LoadBalancerService
  115. */
  116. public function findByNode($node, $vmCreate = true)
  117. {
  118. $this->filter['node'] = $node;
  119. $this->usage = false;
  120. //Node settings
  121. $this->nodesResurces = [];
  122. $query = NodeSetting::ofServer($this->serverId)
  123. ->ofNode($node)
  124. ->ofSetting('vmCreate');
  125. if ($vmCreate)
  126. {
  127. $query->ofValue(1);
  128. }
  129. if(!$query->count()){
  130. $entity = new NodeSetting();
  131. $entity->node = $node;
  132. $entity->server_id = $this->serverId;
  133. $entity->setting = 'vmsMax';
  134. $entity->value = "";
  135. $this->nodesResurces[] = $this->format($entity);
  136. }
  137. foreach ($query->get() as $entity)
  138. {
  139. $this->nodesResurces[] = $this->format($entity);
  140. }
  141. return clone $this;
  142. }
  143. /**
  144. * @return LoadBalancerService
  145. */
  146. public function findNotUser($userId)
  147. {
  148. $this->filter['notUserId'] = $userId;
  149. foreach ($this->nodesResurces as $k => $nodeResurce)
  150. {
  151. if ($this->hasNode($userId, $nodeResurce['node']))
  152. {
  153. unset($this->nodesResurces[$k]);
  154. }
  155. }
  156. return clone $this;
  157. }
  158. private function hasNode($userId, $node)
  159. {
  160. $total = Hosting::ofUserId($userId)
  161. ->ofServerId($this->serverId)
  162. ->active()
  163. ->ofCustomFieldNode($node)
  164. ->count();
  165. try
  166. {
  167. $total += Hosting::ofUserId($userId)
  168. ->ofServerId($this->serverId)
  169. ->active()
  170. ->ofvServerNode($node)
  171. ->count();
  172. }
  173. catch (\Exception $ex)
  174. {//Table does not exist
  175. }
  176. return $total > 0;
  177. }
  178. /**
  179. * @return LoadBalancerService
  180. */
  181. public function findByRam($bytes)
  182. {
  183. $this->filter['ram'] = $bytes;
  184. $this->usage();
  185. foreach ($this->nodesResurces as $k => $nodeResurce)
  186. {
  187. if (!is_null($nodeResurce['ramFree']) && $nodeResurce['ramFree'] < $bytes)
  188. {
  189. unset($this->nodesResurces[$k]);
  190. }
  191. }
  192. return clone $this;
  193. }
  194. private function usage()
  195. {
  196. if ($this->usage)
  197. {
  198. return true;
  199. }
  200. if (!$this->api instanceof \MGProvision\Proxmox\v2\Api)
  201. {
  202. throw new \Exception("API instance is not initialized");
  203. }
  204. $nodes = [];
  205. foreach ($this->api->get("/nodes") as $node)
  206. {
  207. if ($node['status'] != "online")
  208. {
  209. continue;
  210. }
  211. $nodes[] = $node['node'];
  212. }
  213. foreach ($this->nodesResurces as $k => &$nodeResurce)
  214. {
  215. if (!in_array($nodeResurce['node'], $nodes))
  216. {
  217. continue;
  218. }
  219. //kvm
  220. $qemu = $this->api->get("/nodes/{$nodeResurce['node']}/qemu");
  221. foreach ($qemu as $record)
  222. {
  223. if ($record['template'] || ($this->excludeVmid && $this->excludeVmid == $record['vmid']) || !$this->hasVmid($record['vmid']))
  224. {
  225. continue;
  226. }
  227. $vm = new Kvm($nodeResurce['node'], $record['vmid']);
  228. $vm->setApi($this->api);
  229. foreach ($vm->getHardDisks() as $hardDisk)
  230. {
  231. $nodeResurce['diskUsed'] += $hardDisk->getBytes();
  232. }
  233. $nodeResurce['ramUsed'] += $record['maxmem'];
  234. $nodeResurce['vmsUsed']++;
  235. $nodeResurce['cpuUsed'] += $record['cpus'];
  236. }
  237. unset($qemu, $record, $vm);
  238. //lxc
  239. $lxc = $this->api->get("/nodes/{$nodeResurce['node']}/lxc");
  240. foreach ($lxc as $record)
  241. {
  242. if ($record['template'] || ($this->excludeVmid && $this->excludeVmid == $record['vmid']) || !$this->hasVmid($record['vmid']))
  243. {
  244. continue;
  245. }
  246. $vm = new Lxc($nodeResurce['node'], $record['vmid']);
  247. $vm->setApi($this->api);
  248. foreach ($vm->getMounPoints()->fetch() as $mounPoint)
  249. {
  250. /*@var $mounPoint MountPoint */
  251. $nodeResurce['diskUsed'] += $mounPoint->getBytes();
  252. }
  253. $nodeResurce['ramUsed'] += $record['maxmem'];
  254. $nodeResurce['vmsUsed']++;
  255. $nodeResurce['cpuUsed'] += $record['cpus'];
  256. }
  257. unset($lxc, $record, $vm);
  258. }
  259. //calculate free resurces
  260. foreach ($this->nodesResurces as $k => &$nodeResurce)
  261. {
  262. $nodeResurce['ramFree'] = $nodeResurce['ramMax'] - $nodeResurce['ramUsed'];
  263. $nodeResurce['vmsFree'] = $nodeResurce['vmsMax'] - $nodeResurce['vmsUsed'];
  264. $nodeResurce['cpuFree'] = $nodeResurce['cpuMax'] - $nodeResurce['cpuUsed'];
  265. $nodeResurce['diskFree'] = $nodeResurce['diskMax'] - $nodeResurce['diskUsed'];
  266. }
  267. $this->usage = true;
  268. }
  269. private function hasVmid($vmid)
  270. {
  271. $total = Hosting::ofServerId($this->serverId)
  272. ->ofStatus(['Active', 'Suspended'])
  273. ->ofCustomFielVmid($vmid)
  274. ->count();
  275. try
  276. {
  277. $total += Hosting::ofServerId($this->serverId)
  278. ->active()
  279. ->ofvServerVmid($vmid)
  280. ->count();
  281. }
  282. catch (\Exception $ex)
  283. {//Table does not exist
  284. }
  285. return $total > 0;
  286. }
  287. /**
  288. * @return LoadBalancerService
  289. */
  290. public function findByDisk($bytes)
  291. {
  292. $this->filter['disk'] = $bytes;
  293. $this->usage();
  294. foreach ($this->nodesResurces as $k => $nodeResurce)
  295. {
  296. if (!is_null($nodeResurce['diskFree']) && $nodeResurce['diskFree'] < $bytes)
  297. {
  298. unset($this->nodesResurces[$k]);
  299. }
  300. }
  301. return clone $this;
  302. }
  303. /**
  304. * @return LoadBalancerService
  305. */
  306. public function findByCpu($cpu)
  307. {
  308. $this->filter['cpu'] = $cpu;
  309. $this->usage();
  310. foreach ($this->nodesResurces as $k => $nodeResurce)
  311. {
  312. if (!is_null($nodeResurce['cpuFree']) && $nodeResurce['cpuFree'] < $cpu)
  313. {
  314. unset($this->nodesResurces[$k]);
  315. }
  316. }
  317. return clone $this;
  318. }
  319. /**
  320. * @return LoadBalancerService
  321. */
  322. public function findByVms($number)
  323. {
  324. $this->filter['vms'] = $number;
  325. $this->usage();
  326. foreach ($this->nodesResurces as $k => $nodeResurce)
  327. {
  328. if (!is_null($nodeResurce['vmsFree']) && $nodeResurce['vmsFree'] < $number)
  329. {
  330. unset($this->nodesResurces[$k]);
  331. }
  332. }
  333. return clone $this;
  334. }
  335. public function getApi()
  336. {
  337. return $this->api;
  338. }
  339. public function setApi(\MGProvision\Proxmox\v2\Api $api)
  340. {
  341. $this->api = $api;
  342. return $this;
  343. }
  344. public function getNodesResurces()
  345. {
  346. $this->usage();
  347. return $this->nodesResurces;
  348. }
  349. public function count()
  350. {
  351. return count($this->nodesResurces);
  352. }
  353. public function isEmpty()
  354. {
  355. return empty($this->nodesResurces);
  356. }
  357. public function nodeLowLoad()
  358. {
  359. $this->usage();
  360. $weight = Settings::where("setting", 'vmsWeight')->value("value");
  361. $this->vmsWeight = $weight ? $weight : 1;
  362. $weight = Settings::where("setting", 'cpuWeight')->value("value");
  363. $this->cpuWeight = $weight ? $weight : 1;
  364. $weight = Settings::where("setting", 'ramWeight')->value("value");
  365. $this->ramWeight = $weight ? $weight : 1;
  366. $weight = Settings::where("setting", 'diskWeight')->value("value");
  367. $this->diskWeight = $weight ? $weight : 1;
  368. $nodes = [];
  369. //By ram
  370. $nodes = $this->nodesUsage($nodes, $this->orderByRamUsage(), $this->ramWeight);
  371. //By cpu
  372. $nodes = $this->nodesUsage($nodes, $this->orderByCpuUsage(), $this->cpuWeight);
  373. //By Disk
  374. $nodes = $this->nodesUsage($nodes, $this->orderByDiskUsage(), $this->diskWeight);
  375. //By Vms
  376. $nodes = $this->nodesUsage($nodes, $this->orderByVmsUsage(), $this->vmsWeight);
  377. if (empty($nodes))
  378. {
  379. throw new \Exception("Load Balancer: Cannot find node with free resources");
  380. }
  381. asort($nodes);
  382. $nodes = array_reverse($nodes);
  383. return key($nodes);
  384. }
  385. private function nodesUsage($nodes, $values, $weight = 1)
  386. {
  387. foreach (array_reverse($values) as $index => $nodeName)
  388. {
  389. if (!array_key_exists($nodeName, $nodes))
  390. {
  391. $nodes[$nodeName] = 0;
  392. }
  393. $nodes[$nodeName] += ($index + 1) * $weight; // we don't want zero value so we add "1"
  394. }
  395. return $nodes;
  396. }
  397. /**
  398. * @return LoadBalancerService
  399. */
  400. public function orderByRamUsage()
  401. {
  402. return $this->orderBy('ramUsed');
  403. }
  404. private function orderBy($column)
  405. {
  406. $this->usage();
  407. $column = array_column($this->nodesResurces, $column);
  408. array_multisort($column, SORT_ASC, $this->nodesResurces);
  409. $nodes = [];
  410. foreach ($column as $k => $v)
  411. {
  412. $nodes[] = $this->nodesResurces[$k]['node'];
  413. }
  414. return $nodes;
  415. }
  416. public function orderByCpuUsage()
  417. {
  418. return $this->orderBy('ramUsed');
  419. }
  420. public function orderByDiskUsage()
  421. {
  422. return $this->orderBy('vmsUsed');
  423. }
  424. public function orderByVmsUsage()
  425. {
  426. return $this->orderBy('vmsUsed');
  427. }
  428. }