mirror of
https://github.com/librenms/librenms.git
synced 2024-09-22 10:58:42 +00:00
Merge pull request #1789 from tuxis-ie/add-proxmox-app
Add proxmox monitoring.
This commit is contained in:
commit
b9bf61bbf6
23
doc/Extensions/Proxmox.md
Normal file
23
doc/Extensions/Proxmox.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Proxmox graphing
|
||||
|
||||
It is possible to create graphs of the Proxmox VMs that run on your monitored machines. Currently, only trafficgraphs are created. One for each interface on each VM. Possibly, IO grahps will be added later on.
|
||||
|
||||
The ultimate goal is to be able to create traffic bills for VMs, no matter on which physical machine that VM runs.
|
||||
|
||||
### Enabling Proxmox graphs
|
||||
|
||||
To enable Proxmox graphs, do the following:
|
||||
|
||||
In config.php, enable Proxmox:
|
||||
```php
|
||||
$config['enable_proxmox'] = 1
|
||||
```
|
||||
|
||||
Then, install librenms-agent on the machines running Proxmox, and enable the Proxmox-plugin using:
|
||||
```bash
|
||||
mk_enplug proxmox
|
||||
```
|
||||
|
||||
Then, enable the unix-agent on the machines running Proxmox.
|
||||
|
||||
You should now see an application in LibreNMS, as well as a new menu-item in the topmenu, allowing you to choose which cluster you want to look at.
|
50
html/includes/application/proxmox.inc.php
Normal file
50
html/includes/application/proxmox.inc.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Mark Schouten <mark@tuxis.nl>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 dated June,
|
||||
* 1991.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* See http://www.gnu.org/licenses/gpl.txt for the full license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetch all VM's in a Proxmox Cluster
|
||||
* @param string $c Clustername
|
||||
* @return array An array with all the VM's on this cluster
|
||||
*/
|
||||
function proxmox_cluster_vms($c) {
|
||||
return dbFetchRows("SELECT * FROM proxmox WHERE cluster = ? ORDER BY vmid", array($c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all VM's on a Proxmox node
|
||||
* @param integer $n device_id
|
||||
* @return array An array with all the VM's on this node
|
||||
*/
|
||||
function proxmox_node_vms($n) {
|
||||
return dbFetchRows("SELECT * FROM proxmox WHERE device_id = ? ORDER BY vmid", array($n));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all info about a Proxmox VM
|
||||
* @param integer $vmid Proxmox VM ID
|
||||
* @param string $c Clustername
|
||||
* @return array An array with all info of this VM on this cluster, including ports
|
||||
*/
|
||||
function proxmox_vm_info($vmid, $c) {
|
||||
$vm = dbFetchRow("SELECT pm.*, d.hostname AS host, d.device_id FROM proxmox pm, devices d WHERE pm.device_id = d.device_id AND pm.vmid = ? AND pm.cluster = ?", array($vmid, $c));
|
||||
$appid = dbFetchRow("SELECT app_id FROM applications WHERE device_id = ? AND app_type = ?", array($vm['device_id'], 'proxmox'));
|
||||
|
||||
$vm['ports'] = dbFetchRows("SELECT * FROM proxmox_ports WHERE vm_id = ?", array($vm['id']));
|
||||
$vm['app_id'] = $appid['app_id'];
|
||||
return $vm;
|
||||
}
|
@ -12,6 +12,35 @@
|
||||
* @copyright (C) 2013 LibreNMS Group
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compare $t with the value of $vars[$v], if that exists
|
||||
* @param string $v Name of the var to test
|
||||
* @param string $t Value to compare $vars[$v] to
|
||||
* @return boolean true, if values are the same, false if $vars[$v] is unset or values differ
|
||||
*/
|
||||
function var_eq($v, $t) {
|
||||
global $vars;
|
||||
if (isset($vars[$v]) && $vars[$v] == $t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of $vars[$v], if it exists
|
||||
* @param string $v Name of the var to get
|
||||
* @return string|boolean The value of $vars[$v] if it exists, false if it does not exist
|
||||
*/
|
||||
function var_get($v) {
|
||||
global $vars;
|
||||
if (isset($vars[$v])) {
|
||||
return $vars[$v];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function data_uri($file, $mime) {
|
||||
$contents = file_get_contents($file);
|
||||
|
@ -3,7 +3,12 @@
|
||||
if (is_numeric($vars['id']) && ($auth || application_permitted($vars['id']))) {
|
||||
$app = get_application_by_id($vars['id']);
|
||||
$device = device_by_id_cache($app['device_id']);
|
||||
$title = generate_device_link($device);
|
||||
$title .= $graph_subtype;
|
||||
if ($app['app_type'] != 'proxmox') {
|
||||
$title = generate_device_link($device);
|
||||
$title .= $graph_subtype;
|
||||
}
|
||||
else {
|
||||
$title = $vars['port'].'@'.$vars['hostname'].' on '.generate_device_link($device);
|
||||
}
|
||||
$auth = true;
|
||||
}
|
||||
|
30
html/includes/graphs/application/proxmox_traffic.inc.php
Normal file
30
html/includes/graphs/application/proxmox_traffic.inc.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Mark Schouten <mark@tuxis.nl>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 dated June,
|
||||
* 1991.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* See http://www.gnu.org/licenses/gpl.txt for the full license
|
||||
*/
|
||||
|
||||
require 'includes/graphs/common.inc.php';
|
||||
|
||||
$proxmox_rrd = $config['rrd_dir'].'/proxmox/'.$vars['cluster'].'/'.$vars['vmid'].'_netif_'.$vars['port'].'.rrd';
|
||||
|
||||
if (is_file($proxmox_rrd)) {
|
||||
$rrd_filename = $proxmox_rrd;
|
||||
}
|
||||
|
||||
$ds_in = 'INOCTETS';
|
||||
$ds_out = 'OUTOCTETS';
|
||||
|
||||
require 'includes/graphs/generic_data.inc.php';
|
@ -360,21 +360,32 @@ foreach (array_keys($menu_sensors) as $item) {
|
||||
</li>
|
||||
<?php
|
||||
|
||||
$app_count = dbFetchCell("SELECT COUNT(`app_id`) FROM `applications`");
|
||||
$app_list = dbFetchRows("SELECT DISTINCT(`app_type`) AS `app_type` FROM `applications` ORDER BY `app_type`");
|
||||
|
||||
if ($_SESSION['userlevel'] >= '5' && ($app_count) > "0") {
|
||||
if ($_SESSION['userlevel'] >= '5' && count($app_list) > "0") {
|
||||
?>
|
||||
<li class="dropdown">
|
||||
<a href="apps/" class="dropdown-toggle" data-hover="dropdown" data-toggle="dropdown"><i class="fa fa-tasks fa-fw fa-lg fa-nav-icons"></i> Apps</a>
|
||||
<ul class="dropdown-menu">
|
||||
<?php
|
||||
|
||||
$app_list = dbFetchRows("SELECT `app_type` FROM `applications` GROUP BY `app_type` ORDER BY `app_type`");
|
||||
foreach ($app_list as $app) {
|
||||
if (isset($app['app_type'])) {
|
||||
$app_i_list = dbFetchRows("SELECT DISTINCT(`app_instance`) FROM `applications` WHERE `app_type` = ? ORDER BY `app_instance`", array($app['app_type']));
|
||||
$image = $config['html_dir']."/images/icons/".$app['app_type'].".png";
|
||||
$icon = (file_exists($image) ? $app['app_type'] : "apps");
|
||||
echo('<li><a href="apps/app='.$app['app_type'].'/"><i class="fa fa-angle-double-right fa-fw fa-lg"></i> '.nicecase($app['app_type']).' </a></li>');
|
||||
if (count($app_i_list) > 1) {
|
||||
echo '<li class="dropdown-submenu">';
|
||||
echo '<a href="apps/app='.$app['app_type'].'/"><i class="fa fa-server fa-fw fa-lg"></i> '.nicecase($app['app_type']).' </a>';
|
||||
echo '<ul class="dropdown-menu scrollable-menu">';
|
||||
foreach ($app_i_list as $instance) {
|
||||
echo ' <li><a href="apps/app='.$app['app_type'].'/instance='.$instance['app_instance'].'/"><i class="fa fa-angle-double-right fa-fw fa-lg"></i> ' . nicecase($instance['app_instance']) . '</a></li>';
|
||||
}
|
||||
echo '</ul></li>';
|
||||
}
|
||||
else {
|
||||
echo('<li><a href="apps/app='.$app['app_type'].'/"><i class="fa fa-angle-double-right fa-fw fa-lg"></i> '.nicecase($app['app_type']).' </a></li>');
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
60
html/pages/apps/proxmox.inc.php
Normal file
60
html/pages/apps/proxmox.inc.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
require_once('includes/application/proxmox.inc.php');
|
||||
$graphs['proxmox'] = array(
|
||||
'netif'
|
||||
);
|
||||
|
||||
$pmxcl = dbFetchRows("SELECT DISTINCT(`app_instance`) FROM `applications` WHERE `app_type` = ?", array('proxmox'));
|
||||
|
||||
|
||||
print_optionbar_start();
|
||||
|
||||
echo "<span style='font-weight: bold;'>Proxmox</span> » ";
|
||||
|
||||
unset($sep);
|
||||
|
||||
foreach ($pmxcl as $pmxc) {
|
||||
if (isset($sep)) {
|
||||
echo $sep;
|
||||
};
|
||||
|
||||
if (var_eq('instance', $pmxc['app_instance'])) {
|
||||
echo "<span class='pagemenu-selected'>";
|
||||
}
|
||||
|
||||
echo generate_link(nicecase($pmxc['app_instance']), array('page' => 'apps', 'app' => 'proxmox', 'instance' => $pmxc['app_instance']));
|
||||
|
||||
if (var_eq('instance', $pmxc['app_instance'])) {
|
||||
echo '</span>';
|
||||
}
|
||||
|
||||
$sep = ' | ';
|
||||
}
|
||||
|
||||
print_optionbar_end();
|
||||
|
||||
echo '<div class="container"><div class="row"><div class="col-md-12">';
|
||||
|
||||
if (!isset($vars['instance'])) {
|
||||
echo 'Select a cluster:';
|
||||
echo '<ul>';
|
||||
foreach ($pmxcl as $pmxc) {
|
||||
echo '<li>'.generate_link(nicecase($pmxc['app_instance']), array('page' => 'apps', 'app' => 'proxmox', 'instance' => $pmxc['app_instance'])).'</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
elseif (!isset($vars['vmid'])) {
|
||||
echo '<ul>';
|
||||
foreach (proxmox_cluster_vms(var_get('instance')) as $pmxvm) {
|
||||
echo '<li>'.generate_link($pmxvm['vmid']." (".$pmxvm['description'].")", array('page' => 'apps', 'app' => 'proxmox', 'instance' => var_get('instance'), 'vmid' => $pmxvm['vmid'])).'</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
else {
|
||||
include("pages/apps/proxmox/vm.inc.php");
|
||||
}
|
||||
|
||||
echo '</div></div></div>';
|
||||
|
||||
$pagetitle[] = 'Proxmox';
|
32
html/pages/apps/proxmox/vm.inc.php
Normal file
32
html/pages/apps/proxmox/vm.inc.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
$vm = proxmox_vm_info(var_get('vmid'), var_get('instance'));
|
||||
|
||||
$graphs = array(
|
||||
'proxmox_traffic' => 'Traffic',
|
||||
);
|
||||
|
||||
foreach ($vm['ports'] as $port) {
|
||||
foreach ($graphs as $key => $text) {
|
||||
$graph_type = 'proxmox_traffic';
|
||||
|
||||
$graph_array['height'] = '100';
|
||||
$graph_array['width'] = '215';
|
||||
$graph_array['to'] = $config['time']['now'];
|
||||
$graph_array['id'] = $vm['app_id'];
|
||||
$graph_array['device_id'] = $vm['device_id'];
|
||||
$graph_array['type'] = 'application_'.$key;
|
||||
$graph_array['port'] = $port['port'];
|
||||
$graph_array['vmid'] = $vm['vmid'];
|
||||
$graph_array['cluster'] = $vm['cluster'];
|
||||
$graph_array['hostname'] = $vm['description'];
|
||||
|
||||
echo '<h3>'.$text.' '.$port['port'].'@'.$vm['description'].'</h3>';
|
||||
|
||||
echo "<tr bgcolor='$row_colour'><td colspan=5>";
|
||||
|
||||
include 'includes/print-graphrow.inc.php';
|
||||
|
||||
echo '</td></tr>';
|
||||
}
|
||||
}
|
58
html/pages/device/apps/proxmox.inc.php
Normal file
58
html/pages/device/apps/proxmox.inc.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Mark Schouten <mark@tuxis.nl>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 dated June,
|
||||
* 1991.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* See http://www.gnu.org/licenses/gpl.txt for the full license
|
||||
*/
|
||||
|
||||
include('includes/application/proxmox.inc.php');
|
||||
|
||||
if (!isset($config['enable_proxmox']) || !$config['enable_proxmox']) {
|
||||
print_error('Proxmox agent was discovered on this host. Please enable Proxmox in your config.');
|
||||
}
|
||||
else {
|
||||
|
||||
$graphs = array(
|
||||
'proxmox_traffic' => 'Traffic',
|
||||
);
|
||||
|
||||
foreach (proxmox_node_vms(var_get('device')) as $nvm) {
|
||||
$vm = proxmox_vm_info($nvm['vmid'], $nvm['cluster']);
|
||||
|
||||
foreach ($vm['ports'] as $port) {
|
||||
foreach ($graphs as $key => $text) {
|
||||
$graph_type = 'proxmox_traffic';
|
||||
|
||||
$graph_array['height'] = '100';
|
||||
$graph_array['width'] = '215';
|
||||
$graph_array['to'] = $config['time']['now'];
|
||||
$graph_array['id'] = $vm['app_id'];
|
||||
$graph_array['device_id'] = $vm['device_id'];
|
||||
$graph_array['type'] = 'application_'.$key;
|
||||
$graph_array['port'] = $port['port'];
|
||||
$graph_array['vmid'] = $vm['vmid'];
|
||||
$graph_array['cluster'] = $vm['cluster'];
|
||||
$graph_array['hostname'] = $vm['description'];
|
||||
|
||||
echo '<h3>'.$text.' '.$port['port'].'@'.$vm['description'].'</h3>';
|
||||
|
||||
echo "<tr bgcolor='$row_colour'><td colspan=5>";
|
||||
|
||||
include 'includes/print-graphrow.inc.php';
|
||||
|
||||
echo '</td></tr>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
93
includes/polling/applications/proxmox.inc.php
Normal file
93
includes/polling/applications/proxmox.inc.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
if (isset($config['enable_proxmox']) && $config['enable_proxmox'] && !empty($agent_data['app']['proxmox'])) {
|
||||
$proxmox = $agent_data['app']['proxmox'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a port on a Proxmox VM exists
|
||||
* @param string $p Port name
|
||||
* @param string $c Clustername
|
||||
* @param integer $i VM ID
|
||||
* @return integer|boolean The port-ID if the port exists, false if it doesn't exist
|
||||
*/
|
||||
function proxmox_port_exists($i, $c, $p) {
|
||||
if ($row = dbFetchRow("SELECT pmp.id FROM proxmox_ports pmp, proxmox pm WHERE pm.id = pmp.vm_id AND pmp.port = ? AND pm.cluster = ? AND pm.vmid = ?", array($p, $c, $i))) {
|
||||
return $row['id'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a Proxmox VM exists
|
||||
* @param integer $i VM ID
|
||||
* @param string $c Clustername
|
||||
* @param array $pmxcache Reference to the Proxmox VM Cache
|
||||
* @return boolean true if the VM exists, false if it doesn't
|
||||
*/
|
||||
function proxmox_vm_exists($i, $c, &$pmxcache) {
|
||||
|
||||
if (isset($pmxcache[$c][$i]) && $pmxcache[$c][$i] > 0) {
|
||||
return true;
|
||||
}
|
||||
if ($row = dbFetchRow("SELECT id FROM proxmox WHERE vmid = ? AND cluster = ?", array($i, $c))) {
|
||||
$pmxcache[$c][$i] = (integer) $row['id'];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$pmxlines = explode("\n", $proxmox);
|
||||
|
||||
if (count($pmxlines) > 2) {
|
||||
$pmxcluster = array_shift($pmxlines);
|
||||
|
||||
$pmxcdir = join('/', array($config['rrd_dir'],'proxmox',$pmxcluster));
|
||||
if (!is_dir($pmxcdir)) {
|
||||
mkdir($pmxcdir, 0775, true);
|
||||
}
|
||||
|
||||
dbUpdate(array('device_id' => $device['device_id'], 'app_type' => 'proxmox', 'app_instance' => $pmxcluster), 'applications', '`device_id` = ? AND `app_type` = ?', array($device['device_id'], 'proxmox'));
|
||||
|
||||
$pmxcache = [];
|
||||
|
||||
foreach ($pmxlines as $vm) {
|
||||
list($vmid, $vmport, $vmpin, $vmpout, $vmdesc) = explode('/', $vm, 5);
|
||||
|
||||
$rrd_filename = join('/', array(
|
||||
$pmxcdir,
|
||||
$vmid.'_netif_'.$vmport.'.rrd'));
|
||||
|
||||
if (!is_file($rrd_filename)) {
|
||||
rrdtool_create(
|
||||
$rrd_filename,
|
||||
' --step 300 \
|
||||
DS:INOCTETS:DERIVE:600:0:12500000000 \
|
||||
DS:OUTOCTETS:DERIVE:600:0:12500000000 '.$config['rrd_rra']);
|
||||
}
|
||||
|
||||
rrdtool_update($rrd_filename, 'N:'.$vmpin.':'.$vmpout);
|
||||
if (proxmox_vm_exists($vmid, $pmxcluster, $pmxcache) === true) {
|
||||
dbUpdate(array('device_id' => $device['device_id'], 'last_seen' => array('NOW()'), 'description' => $vmdesc), 'proxmox', '`vmid` = ? AND `cluster` = ?', array($vmid, $pmxcluster));
|
||||
}
|
||||
else {
|
||||
$pmxcache[$pmxcluster][$vmid] = dbInsert(array('cluster' => $pmxcluster, 'vmid' => $vmid, 'description' => $vmdesc, 'device_id' => $device['device_id']), 'proxmox');
|
||||
}
|
||||
|
||||
if ($portid = proxmox_port_exists($vmid, $pmxcluster, $vmport) !== false) {
|
||||
dbUpdate(array('last_seen' => array('NOW()')), 'proxmox_ports', '`vm_id` = ? AND `port` = ?', array($pmxcache[$pmxcluster][$vmid], $vmport));
|
||||
}
|
||||
else {
|
||||
dbInsert(array('vm_id' => $pmxcache[$pmxcluster][$vmid], 'port' => $vmport), 'proxmox_ports');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unset($pmxlines);
|
||||
unset($pmxcluster);
|
||||
unset($pmxcdir);
|
||||
unset($proxmox);
|
||||
unset($pmxcache);
|
||||
}
|
@ -50,6 +50,7 @@ if ($device['os_group'] == 'unix') {
|
||||
"mysql",
|
||||
"nginx",
|
||||
"bind",
|
||||
"proxmox",
|
||||
"tinydns");
|
||||
|
||||
if (in_array($section, $agentapps)) {
|
||||
@ -99,7 +100,7 @@ if ($device['os_group'] == 'unix') {
|
||||
if (file_exists("includes/polling/applications/$key.inc.php")) {
|
||||
d_echo("Enabling $key for ".$device['hostname']." if not yet enabled\n");
|
||||
|
||||
if (in_array($key, array('apache', 'mysql', 'nginx'))) {
|
||||
if (in_array($key, array('apache', 'mysql', 'nginx', 'proxmox'))) {
|
||||
if (dbFetchCell('SELECT COUNT(*) FROM `applications` WHERE `device_id` = ? AND `app_type` = ?', array($device['device_id'], $key)) == '0') {
|
||||
echo "Found new application '$key'\n";
|
||||
dbInsert(array('device_id' => $device['device_id'], 'app_type' => $key), 'applications');
|
||||
|
3
sql-schema/067.sql
Normal file
3
sql-schema/067.sql
Normal file
@ -0,0 +1,3 @@
|
||||
CREATE TABLE `proxmox` ( `id` int(11) NOT NULL AUTO_INCREMENT, `device_id` int(11) NOT NULL DEFAULT '0', `vmid` int(11) NOT NULL, `cluster` varchar(255) NOT NULL, `description` varchar(255) DEFAULT NULL, `last_seen` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `cluster_vm` (`cluster`,`vmid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
CREATE TABLE `proxmox_ports` ( `id` int(11) NOT NULL AUTO_INCREMENT, `vm_id` int(11) NOT NULL, `port` varchar(10) NOT NULL, `last_seen` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `vm_port` (`vm_id`,`port`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
ALTER TABLE `applications` ADD COLUMN `app_instance` varchar(255) NOT NULL;
|
Loading…
Reference in New Issue
Block a user