http://modulesgarden.com * CONTACT -> contact@modulesgarden.com * * * This software is furnished under a license and may be used and copied * only in accordance with the terms of such license and with the * inclusion of the above copyright notice. This software or any other * copies thereof may not be provided or otherwise made available to any * other person. No title to and ownership of the software is hereby * transferred. * * * ******************************************************************** */ namespace ModulesGarden\ProxmoxAddon\App\Services; use MGProvision\Proxmox\v2\models\Kvm; use MGProvision\Proxmox\v2\models\Lxc; use ModulesGarden\ProxmoxAddon\App\Models\NodeSetting; use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Hosting; use ModulesGarden\ProxmoxAddon\Core\Models\ModuleSettings\Model as Settings; /** * Description of LoadBalancerService * * @author Pawel Kopec */ class LoadBalancerService { protected $nodesResurces = []; private $serverId; /** * * @var \MGProvision\Proxmox\v2\Api */ private $api; private $usage = false; private $filter = []; private $excludeVmid; private $vmsWeight; private $ramWeight; private $diskWeight; private $cpuWeight; function __construct($serverId) { $this->serverId = $serverId; } public function getExcludeVmid() { return $this->excludeVmid; } public function setExcludeVmid($excludeVmid) { $this->excludeVmid = $excludeVmid; return $this; } /** * @return LoadBalancerService */ public function findByVmCreate() { //Node settings $this->nodesResurces = []; $query = NodeSetting::ofServer($this->serverId)->ofSetting('vmCreate')->ofValue(1); foreach ($query->get() as $entity) { $this->nodesResurces[] = $this->format($entity); } return clone $this; } private function format(NodeSetting $entity) { $nodesResurce = [ 'node' => $entity->node, 'vmCreate' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('vmCreate')->value('value'), 'vmsMax' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('vmsMax')->value('value'), 'vmsWeight' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('vmsWeight')->value('value'), 'vmsUsed' => 0, 'vmsFree' => null, 'cpuMax' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('cpuMax')->value('value'), 'cpuWeight' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('cpuWeight')->value('value'), 'cpuUsed' => 0, 'cpuFree' => null, 'diskUsed' => 0, 'diskMax' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('diskMax')->value('value'), 'diskWeight' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('diskWeight')->value('value'), 'diskFree' => null, 'ramMax' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('ramMax')->value('value'), 'ramWeight' => NodeSetting::ofServer($this->serverId)->ofNode($entity->node)->ofSetting('ramWeight')->value('value'), 'ramUsed' => 0, 'ramFree' => 0, ]; if ($nodesResurce['diskMax']) { $nodesResurce['diskMax'] = $nodesResurce['diskMax'] * pow(1024, 3); } if ($nodesResurce['ramMax']) { $nodesResurce['ramMax'] = $nodesResurce['ramMax'] * pow(1024, 3); } return $nodesResurce; } /** * @return LoadBalancerService */ public function findByNode($node, $vmCreate = true) { $this->filter['node'] = $node; $this->usage = false; //Node settings $this->nodesResurces = []; $query = NodeSetting::ofServer($this->serverId) ->ofNode($node) ->ofSetting('vmCreate'); if ($vmCreate) { $query->ofValue(1); } if(!$query->count()){ $entity = new NodeSetting(); $entity->node = $node; $entity->server_id = $this->serverId; $entity->setting = 'vmsMax'; $entity->value = ""; $this->nodesResurces[] = $this->format($entity); } foreach ($query->get() as $entity) { $this->nodesResurces[] = $this->format($entity); } return clone $this; } /** * @return LoadBalancerService */ public function findNotUser($userId) { $this->filter['notUserId'] = $userId; foreach ($this->nodesResurces as $k => $nodeResurce) { if ($this->hasNode($userId, $nodeResurce['node'])) { unset($this->nodesResurces[$k]); } } return clone $this; } private function hasNode($userId, $node) { $total = Hosting::ofUserId($userId) ->ofServerId($this->serverId) ->active() ->ofCustomFieldNode($node) ->count(); try { $total += Hosting::ofUserId($userId) ->ofServerId($this->serverId) ->active() ->ofvServerNode($node) ->count(); } catch (\Exception $ex) {//Table does not exist } return $total > 0; } /** * @return LoadBalancerService */ public function findByRam($bytes) { $this->filter['ram'] = $bytes; $this->usage(); foreach ($this->nodesResurces as $k => $nodeResurce) { if (!is_null($nodeResurce['ramFree']) && $nodeResurce['ramFree'] < $bytes) { unset($this->nodesResurces[$k]); } } return clone $this; } private function usage() { if ($this->usage) { return true; } if (!$this->api instanceof \MGProvision\Proxmox\v2\Api) { throw new \Exception("API instance is not initialized"); } $nodes = []; foreach ($this->api->get("/nodes") as $node) { if ($node['status'] != "online") { continue; } $nodes[] = $node['node']; } foreach ($this->nodesResurces as $k => &$nodeResurce) { if (!in_array($nodeResurce['node'], $nodes)) { continue; } //kvm $qemu = $this->api->get("/nodes/{$nodeResurce['node']}/qemu"); foreach ($qemu as $record) { if ($record['template'] || ($this->excludeVmid && $this->excludeVmid == $record['vmid']) || !$this->hasVmid($record['vmid'])) { continue; } $vm = new Kvm($nodeResurce['node'], $record['vmid']); $vm->setApi($this->api); foreach ($vm->getHardDisks() as $hardDisk) { $nodeResurce['diskUsed'] += $hardDisk->getBytes(); } $nodeResurce['ramUsed'] += $record['maxmem']; $nodeResurce['vmsUsed']++; $nodeResurce['cpuUsed'] += $record['cpus']; } unset($qemu, $record, $vm); //lxc $lxc = $this->api->get("/nodes/{$nodeResurce['node']}/lxc"); foreach ($lxc as $record) { if ($record['template'] || ($this->excludeVmid && $this->excludeVmid == $record['vmid']) || !$this->hasVmid($record['vmid'])) { continue; } $vm = new Lxc($nodeResurce['node'], $record['vmid']); $vm->setApi($this->api); foreach ($vm->getMounPoints()->fetch() as $mounPoint) { /*@var $mounPoint MountPoint */ $nodeResurce['diskUsed'] += $mounPoint->getBytes(); } $nodeResurce['ramUsed'] += $record['maxmem']; $nodeResurce['vmsUsed']++; $nodeResurce['cpuUsed'] += $record['cpus']; } unset($lxc, $record, $vm); } //calculate free resurces foreach ($this->nodesResurces as $k => &$nodeResurce) { $nodeResurce['ramFree'] = $nodeResurce['ramMax'] - $nodeResurce['ramUsed']; $nodeResurce['vmsFree'] = $nodeResurce['vmsMax'] - $nodeResurce['vmsUsed']; $nodeResurce['cpuFree'] = $nodeResurce['cpuMax'] - $nodeResurce['cpuUsed']; $nodeResurce['diskFree'] = $nodeResurce['diskMax'] - $nodeResurce['diskUsed']; } $this->usage = true; } private function hasVmid($vmid) { $total = Hosting::ofServerId($this->serverId) ->ofStatus(['Active', 'Suspended']) ->ofCustomFielVmid($vmid) ->count(); try { $total += Hosting::ofServerId($this->serverId) ->active() ->ofvServerVmid($vmid) ->count(); } catch (\Exception $ex) {//Table does not exist } return $total > 0; } /** * @return LoadBalancerService */ public function findByDisk($bytes) { $this->filter['disk'] = $bytes; $this->usage(); foreach ($this->nodesResurces as $k => $nodeResurce) { if (!is_null($nodeResurce['diskFree']) && $nodeResurce['diskFree'] < $bytes) { unset($this->nodesResurces[$k]); } } return clone $this; } /** * @return LoadBalancerService */ public function findByCpu($cpu) { $this->filter['cpu'] = $cpu; $this->usage(); foreach ($this->nodesResurces as $k => $nodeResurce) { if (!is_null($nodeResurce['cpuFree']) && $nodeResurce['cpuFree'] < $cpu) { unset($this->nodesResurces[$k]); } } return clone $this; } /** * @return LoadBalancerService */ public function findByVms($number) { $this->filter['vms'] = $number; $this->usage(); foreach ($this->nodesResurces as $k => $nodeResurce) { if (!is_null($nodeResurce['vmsFree']) && $nodeResurce['vmsFree'] < $number) { unset($this->nodesResurces[$k]); } } return clone $this; } public function getApi() { return $this->api; } public function setApi(\MGProvision\Proxmox\v2\Api $api) { $this->api = $api; return $this; } public function getNodesResurces() { $this->usage(); return $this->nodesResurces; } public function count() { return count($this->nodesResurces); } public function isEmpty() { return empty($this->nodesResurces); } public function nodeLowLoad() { $this->usage(); $weight = Settings::where("setting", 'vmsWeight')->value("value"); $this->vmsWeight = $weight ? $weight : 1; $weight = Settings::where("setting", 'cpuWeight')->value("value"); $this->cpuWeight = $weight ? $weight : 1; $weight = Settings::where("setting", 'ramWeight')->value("value"); $this->ramWeight = $weight ? $weight : 1; $weight = Settings::where("setting", 'diskWeight')->value("value"); $this->diskWeight = $weight ? $weight : 1; $nodes = []; //By ram $nodes = $this->nodesUsage($nodes, $this->orderByRamUsage(), $this->ramWeight); //By cpu $nodes = $this->nodesUsage($nodes, $this->orderByCpuUsage(), $this->cpuWeight); //By Disk $nodes = $this->nodesUsage($nodes, $this->orderByDiskUsage(), $this->diskWeight); //By Vms $nodes = $this->nodesUsage($nodes, $this->orderByVmsUsage(), $this->vmsWeight); if (empty($nodes)) { throw new \Exception("Load Balancer: Cannot find node with free resources"); } asort($nodes); $nodes = array_reverse($nodes); return key($nodes); } private function nodesUsage($nodes, $values, $weight = 1) { foreach (array_reverse($values) as $index => $nodeName) { if (!array_key_exists($nodeName, $nodes)) { $nodes[$nodeName] = 0; } $nodes[$nodeName] += ($index + 1) * $weight; // we don't want zero value so we add "1" } return $nodes; } /** * @return LoadBalancerService */ public function orderByRamUsage() { return $this->orderBy('ramUsed'); } private function orderBy($column) { $this->usage(); $column = array_column($this->nodesResurces, $column); array_multisort($column, SORT_ASC, $this->nodesResurces); $nodes = []; foreach ($column as $k => $v) { $nodes[] = $this->nodesResurces[$k]['node']; } return $nodes; } public function orderByCpuUsage() { return $this->orderBy('ramUsed'); } public function orderByDiskUsage() { return $this->orderBy('vmsUsed'); } public function orderByVmsUsage() { return $this->orderBy('vmsUsed'); } }