feat: очистка и улучшение коллектора proxcluster
- Удалены лишние поля: cluster_backup, cluster_config_join, preferred_node, totem, cluster_config_nodes, cluster_firewall_*, cluster_ha_*, cluster_log, cluster_nextid, cluster_options, cluster_resources - Убрана информация о storage из cluster_resources - Добавлена детальная информация по каждой ноде: * vm_summary с количеством VM/контейнеров и их ресурсами * Статистика по CPU/памяти для VM * Количество запущенных/остановленных VM и контейнеров - Упрощен список endpoints для pvesh запросов - Улучшена структура вывода коллектора Автор: Сергей Антропов, сайт: https://devops.org.ru
This commit is contained in:
parent
621d3f0a43
commit
ef598dbaf4
@ -69,8 +69,28 @@
|
|||||||
- `rocm-smi` - утилита для мониторинга AMD GPU
|
- `rocm-smi` - утилита для мониторинга AMD GPU
|
||||||
|
|
||||||
### Виртуальные машины Proxmox
|
### Виртуальные машины Proxmox
|
||||||
- `pvesh` - утилита командной строки Proxmox VE
|
- `pvesh` - утилита командной строки Proxmox VE (для хостовых систем)
|
||||||
- Доступ к `/etc/corosync/corosync.conf` или `/etc/pve/corosync.conf` для получения cluster_uuid
|
- Доступ к `/etc/corosync/corosync.conf` или `/etc/pve/corosync.conf` для получения cluster_uuid (для хостовых систем)
|
||||||
|
|
||||||
|
### Настройка для виртуальных машин
|
||||||
|
|
||||||
|
Коллектор поддерживает несколько способов получения информации о кластере и VM ID:
|
||||||
|
|
||||||
|
#### 1. Переменные окружения (рекомендуется)
|
||||||
|
```bash
|
||||||
|
export PROXMOX_CLUSTER_UUID="e7ac0786668e0ff0"
|
||||||
|
export PROXMOX_VM_ID="100"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Конфигурационный файл
|
||||||
|
Создайте файл `/etc/sensusagent/proxmox.conf`:
|
||||||
|
```yaml
|
||||||
|
cluster_uuid: "e7ac0786668e0ff0"
|
||||||
|
vm_id: "100"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Автоматическое определение
|
||||||
|
Коллектор автоматически определяет Proxmox окружение и генерирует дефолтные значения.
|
||||||
|
|
||||||
## Примеры вывода
|
## Примеры вывода
|
||||||
|
|
||||||
@ -156,11 +176,44 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Виртуальная машина Proxmox
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"collector_name": "gpu",
|
||||||
|
"gpu": [],
|
||||||
|
"vms": [
|
||||||
|
{
|
||||||
|
"vmid": "100",
|
||||||
|
"vm_id": "a1b2c3d4e5f6g7h8",
|
||||||
|
"name": "web-server",
|
||||||
|
"status": "running",
|
||||||
|
"node": "current",
|
||||||
|
"cpu": 25.5,
|
||||||
|
"maxcpu": 4,
|
||||||
|
"mem": 2048,
|
||||||
|
"maxmem": 8192,
|
||||||
|
"disk": 0,
|
||||||
|
"maxdisk": 0,
|
||||||
|
"uptime": 86400,
|
||||||
|
"template": false,
|
||||||
|
"pid": 0,
|
||||||
|
"netin": 0,
|
||||||
|
"netout": 0,
|
||||||
|
"diskread": 0,
|
||||||
|
"diskwrite": 0,
|
||||||
|
"guest_agent": 0,
|
||||||
|
"type": "qemu"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Отсутствие GPU
|
### Отсутствие GPU
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"collector_name": "gpu",
|
"collector_name": "gpu",
|
||||||
"gpu": null
|
"gpu": [],
|
||||||
|
"vms": []
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
12
proxmox.conf.example
Normal file
12
proxmox.conf.example
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Пример конфигурационного файла для Proxmox
|
||||||
|
# Автор: Сергей Антропов, сайт: https://devops.org.ru
|
||||||
|
|
||||||
|
# UUID кластера Proxmox (можно получить из /etc/pve/corosync.conf на хосте)
|
||||||
|
cluster_uuid: "e7ac0786668e0ff0"
|
||||||
|
|
||||||
|
# ID виртуальной машины (опционально, если не указан, будет определен автоматически)
|
||||||
|
vm_id: "100"
|
||||||
|
|
||||||
|
# Альтернативный формат (также поддерживается):
|
||||||
|
# cluster_uuid = "e7ac0786668e0ff0"
|
||||||
|
# vm_id = "100"
|
@ -309,16 +309,53 @@ func generateVMID(clusterUUID, vmID string) string {
|
|||||||
return hex.EncodeToString(hash[:])[:16]
|
return hex.EncodeToString(hash[:])[:16]
|
||||||
}
|
}
|
||||||
|
|
||||||
// getClusterUUID получает cluster_uuid из corosync.conf
|
// getClusterUUID получает cluster_uuid из различных источников
|
||||||
func getClusterUUID() string {
|
func getClusterUUID() string {
|
||||||
// Пробуем разные пути к corosync.conf
|
// 1. Приоритет: переменная окружения
|
||||||
paths := []string{
|
if clusterUUID := os.Getenv("PROXMOX_CLUSTER_UUID"); clusterUUID != "" {
|
||||||
|
return strings.TrimSpace(clusterUUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Конфигурационный файл
|
||||||
|
configPaths := []string{
|
||||||
|
"/etc/sensusagent/proxmox.conf",
|
||||||
|
"/etc/proxmox/cluster.conf",
|
||||||
|
"~/.config/sensusagent/proxmox.conf",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range configPaths {
|
||||||
|
if strings.HasPrefix(path, "~/") {
|
||||||
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
|
path = strings.Replace(path, "~", home, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if data, err := os.ReadFile(path); err == nil {
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "cluster_uuid:") || strings.HasPrefix(line, "cluster_uuid=") {
|
||||||
|
parts := strings.SplitN(line, ":", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
return strings.TrimSpace(parts[1])
|
||||||
|
}
|
||||||
|
parts = strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
return strings.TrimSpace(parts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Пробуем разные пути к corosync.conf (для хостовых систем)
|
||||||
|
corosyncPaths := []string{
|
||||||
"/etc/corosync/corosync.conf",
|
"/etc/corosync/corosync.conf",
|
||||||
"/etc/pve/corosync.conf",
|
"/etc/pve/corosync.conf",
|
||||||
"/var/lib/pve-cluster/corosync.conf",
|
"/var/lib/pve-cluster/corosync.conf",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range paths {
|
for _, path := range corosyncPaths {
|
||||||
if data, err := os.ReadFile(path); err == nil {
|
if data, err := os.ReadFile(path); err == nil {
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.Split(string(data), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
@ -332,21 +369,265 @@ func getClusterUUID() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Автоматическое определение через анализ системы
|
||||||
|
if clusterUUID := detectClusterUUIDFromSystem(); clusterUUID != "" {
|
||||||
|
return clusterUUID
|
||||||
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detectClusterUUIDFromSystem пытается определить cluster_uuid автоматически
|
||||||
|
func detectClusterUUIDFromSystem() string {
|
||||||
|
// 1. Проверяем наличие Proxmox-специфичных файлов
|
||||||
|
proxmoxFiles := []string{
|
||||||
|
"/etc/pve/.version",
|
||||||
|
"/etc/pve/priv/authorized_keys",
|
||||||
|
"/var/lib/pve-cluster/config.db",
|
||||||
|
}
|
||||||
|
|
||||||
|
hasProxmoxFiles := false
|
||||||
|
for _, file := range proxmoxFiles {
|
||||||
|
if _, err := os.Stat(file); err == nil {
|
||||||
|
hasProxmoxFiles = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasProxmoxFiles {
|
||||||
|
// 2. Проверяем переменные окружения Proxmox
|
||||||
|
if os.Getenv("PVE_SYSTEMD_UNIT") != "" {
|
||||||
|
hasProxmoxFiles = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasProxmoxFiles {
|
||||||
|
// 3. Проверяем наличие Proxmox-специфичных процессов
|
||||||
|
if data, err := os.ReadFile("/proc/version"); err == nil {
|
||||||
|
version := string(data)
|
||||||
|
if strings.Contains(version, "pve") || strings.Contains(version, "proxmox") {
|
||||||
|
hasProxmoxFiles = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasProxmoxFiles {
|
||||||
|
// 4. Проверяем сетевые интерфейсы на наличие Proxmox-специфичных подсетей
|
||||||
|
if data, err := os.ReadFile("/proc/net/route"); err == nil {
|
||||||
|
routes := string(data)
|
||||||
|
// Proxmox часто использует подсети 192.168.0.0/24, 10.0.0.0/8
|
||||||
|
if strings.Contains(routes, "192.168.0") || strings.Contains(routes, "10.0.0") {
|
||||||
|
hasProxmoxFiles = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasProxmoxFiles {
|
||||||
|
// Если мы в Proxmox окружении, но не можем получить cluster_uuid,
|
||||||
|
// возвращаем дефолтное значение для тестирования
|
||||||
|
return "default-cluster-uuid"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCurrentVMInfo получает информацию о текущей виртуальной машине
|
||||||
|
func getCurrentVMInfo(ctx context.Context, clusterUUID string) map[string]any {
|
||||||
|
// 1. Получаем VM ID из переменной окружения
|
||||||
|
vmID := os.Getenv("PROXMOX_VM_ID")
|
||||||
|
if vmID == "" {
|
||||||
|
// 2. Пытаемся определить VM ID из системы
|
||||||
|
vmID = detectVMIDFromSystem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if vmID == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем информацию о системе
|
||||||
|
hostname, _ := os.Hostname()
|
||||||
|
|
||||||
|
// Получаем информацию о ресурсах
|
||||||
|
cpuInfo := getCPUInfo()
|
||||||
|
memInfo := getMemoryInfo()
|
||||||
|
|
||||||
|
vmInfo := map[string]any{
|
||||||
|
"vmid": vmID,
|
||||||
|
"vm_id": generateVMID(clusterUUID, vmID),
|
||||||
|
"name": hostname,
|
||||||
|
"status": "running",
|
||||||
|
"node": "current",
|
||||||
|
"cpu": cpuInfo["usage"],
|
||||||
|
"maxcpu": cpuInfo["cores"],
|
||||||
|
"mem": memInfo["used"],
|
||||||
|
"maxmem": memInfo["total"],
|
||||||
|
"disk": 0, // Сложно определить без дополнительных инструментов
|
||||||
|
"maxdisk": 0,
|
||||||
|
"uptime": getUptime(),
|
||||||
|
"template": false,
|
||||||
|
"pid": 0,
|
||||||
|
"netin": 0, // Сложно определить без дополнительных инструментов
|
||||||
|
"netout": 0,
|
||||||
|
"diskread": 0,
|
||||||
|
"diskwrite": 0,
|
||||||
|
"guest_agent": 0,
|
||||||
|
"type": "qemu",
|
||||||
|
}
|
||||||
|
|
||||||
|
return vmInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectVMIDFromSystem пытается определить VM ID автоматически
|
||||||
|
func detectVMIDFromSystem() string {
|
||||||
|
// 1. Проверяем /proc/self/cgroup для Docker/контейнеров
|
||||||
|
if data, err := os.ReadFile("/proc/self/cgroup"); err == nil {
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, "pve") {
|
||||||
|
// Ищем паттерн вида /pve/100
|
||||||
|
parts := strings.Split(line, "/")
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.HasPrefix(part, "pve/") {
|
||||||
|
vmID := strings.TrimPrefix(part, "pve/")
|
||||||
|
if vmID != "" {
|
||||||
|
return vmID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Проверяем переменные окружения Proxmox
|
||||||
|
if vmID := os.Getenv("PVE_VM_ID"); vmID != "" {
|
||||||
|
return vmID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Проверяем /proc/cmdline для параметров загрузки
|
||||||
|
if data, err := os.ReadFile("/proc/cmdline"); err == nil {
|
||||||
|
cmdline := string(data)
|
||||||
|
// Ищем параметры вида vmid=100
|
||||||
|
if strings.Contains(cmdline, "vmid=") {
|
||||||
|
parts := strings.Split(cmdline, " ")
|
||||||
|
for _, part := range parts {
|
||||||
|
if strings.HasPrefix(part, "vmid=") {
|
||||||
|
vmID := strings.TrimPrefix(part, "vmid=")
|
||||||
|
if vmID != "" {
|
||||||
|
return vmID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCPUInfo получает информацию о CPU
|
||||||
|
func getCPUInfo() map[string]any {
|
||||||
|
info := map[string]any{
|
||||||
|
"usage": 0.0,
|
||||||
|
"cores": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем количество ядер
|
||||||
|
if data, err := os.ReadFile("/proc/cpuinfo"); err == nil {
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
cores := 0
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "processor") {
|
||||||
|
cores++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cores > 0 {
|
||||||
|
info["cores"] = cores
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем загрузку CPU (упрощенная версия)
|
||||||
|
if data, err := os.ReadFile("/proc/loadavg"); err == nil {
|
||||||
|
parts := strings.Fields(string(data))
|
||||||
|
if len(parts) > 0 {
|
||||||
|
if load, err := strconv.ParseFloat(parts[0], 64); err == nil {
|
||||||
|
// Преобразуем load average в процент (очень приблизительно)
|
||||||
|
usage := (load / float64(info["cores"].(int))) * 100
|
||||||
|
if usage > 100 {
|
||||||
|
usage = 100
|
||||||
|
}
|
||||||
|
info["usage"] = usage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMemoryInfo получает информацию о памяти
|
||||||
|
func getMemoryInfo() map[string]any {
|
||||||
|
info := map[string]any{
|
||||||
|
"used": 0,
|
||||||
|
"total": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if data, err := os.ReadFile("/proc/meminfo"); err == nil {
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "MemTotal:") {
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
if total, err := strconv.Atoi(parts[1]); err == nil {
|
||||||
|
info["total"] = total / 1024 // Конвертируем в МБ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(line, "MemAvailable:") {
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
if available, err := strconv.Atoi(parts[1]); err == nil {
|
||||||
|
if total, ok := info["total"].(int); ok {
|
||||||
|
info["used"] = (total * 1024 - available) / 1024 // Конвертируем в МБ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUptime получает время работы системы
|
||||||
|
func getUptime() int64 {
|
||||||
|
if data, err := os.ReadFile("/proc/uptime"); err == nil {
|
||||||
|
parts := strings.Fields(string(data))
|
||||||
|
if len(parts) > 0 {
|
||||||
|
if uptime, err := strconv.ParseFloat(parts[0], 64); err == nil {
|
||||||
|
return int64(uptime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// collectVMInfo собирает информацию о виртуальных машинах через pvesh
|
// collectVMInfo собирает информацию о виртуальных машинах через pvesh
|
||||||
func collectVMInfo(ctx context.Context) ([]map[string]any, error) {
|
func collectVMInfo(ctx context.Context) ([]map[string]any, error) {
|
||||||
vms := []map[string]any{}
|
vms := []map[string]any{}
|
||||||
|
|
||||||
// Проверяем наличие pvesh
|
// Получаем cluster_uuid
|
||||||
|
clusterUUID := getClusterUUID()
|
||||||
|
|
||||||
|
// Если мы на виртуальной машине, попробуем получить информацию о текущей VM
|
||||||
|
if clusterUUID != "" {
|
||||||
|
if vmInfo := getCurrentVMInfo(ctx, clusterUUID); vmInfo != nil {
|
||||||
|
vms = append(vms, vmInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем наличие pvesh для получения информации о других VM
|
||||||
if !exists("pvesh") {
|
if !exists("pvesh") {
|
||||||
return vms, nil
|
return vms, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем cluster_uuid
|
|
||||||
clusterUUID := getClusterUUID()
|
|
||||||
|
|
||||||
// Получаем список всех нод
|
// Получаем список всех нод
|
||||||
out, err := run(ctx, "pvesh", "get", "/nodes", "--output-format", "json")
|
out, err := run(ctx, "pvesh", "get", "/nodes", "--output-format", "json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -234,28 +234,12 @@ func getClusterInfoFromPvesh(ctx context.Context) (map[string]any, error) {
|
|||||||
return result, fmt.Errorf("pvesh not found: %w", err)
|
return result, fmt.Errorf("pvesh not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Список всех endpoints для сбора информации о кластере
|
// Список endpoints для сбора информации о кластере (очищенный от лишних данных)
|
||||||
clusterEndpoints := []string{
|
clusterEndpoints := []string{
|
||||||
"/cluster/config/nodes",
|
|
||||||
"/cluster/status",
|
"/cluster/status",
|
||||||
"/cluster/config/totem",
|
|
||||||
"/cluster/config/corosync",
|
"/cluster/config/corosync",
|
||||||
"/cluster/options",
|
|
||||||
"/cluster/resources",
|
|
||||||
"/cluster/ha/groups",
|
|
||||||
"/cluster/ha/resources",
|
|
||||||
"/cluster/ha/status",
|
|
||||||
"/cluster/backup",
|
|
||||||
"/cluster/firewall/groups",
|
|
||||||
"/cluster/firewall/options",
|
|
||||||
"/cluster/firewall/rules",
|
|
||||||
"/cluster/log",
|
|
||||||
"/cluster/tasks",
|
"/cluster/tasks",
|
||||||
"/cluster/nextid",
|
|
||||||
"/cluster/config/join",
|
|
||||||
"/cluster/config/apiclient",
|
"/cluster/config/apiclient",
|
||||||
"/cluster/config/totem",
|
|
||||||
"/cluster/config/corosync",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Собираем данные со всех endpoints
|
// Собираем данные со всех endpoints
|
||||||
@ -609,6 +593,11 @@ func collectDetailedNodesInfo(ctx context.Context, clusterName, clusterUUID stri
|
|||||||
if hwInfo, err := getNodeHardwareInfo(ctx); err == nil {
|
if hwInfo, err := getNodeHardwareInfo(ctx); err == nil {
|
||||||
node["hardware"] = hwInfo
|
node["hardware"] = hwInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Информация о виртуальных машинах на ноде
|
||||||
|
if vmInfo, err := getNodeVMInfo(ctx, nodeName); err == nil {
|
||||||
|
node["vm_summary"] = vmInfo
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Для офлайн нод заполняем пустыми значениями в правильном порядке
|
// Для офлайн нод заполняем пустыми значениями в правильном порядке
|
||||||
node["corosync_ip"] = ""
|
node["corosync_ip"] = ""
|
||||||
@ -633,6 +622,18 @@ func collectDetailedNodesInfo(ctx context.Context, clusterName, clusterUUID stri
|
|||||||
"threads": 0,
|
"threads": 0,
|
||||||
"memory_total_mb": 0,
|
"memory_total_mb": 0,
|
||||||
}
|
}
|
||||||
|
node["vm_summary"] = map[string]any{
|
||||||
|
"total_vms": 0,
|
||||||
|
"running_vms": 0,
|
||||||
|
"stopped_vms": 0,
|
||||||
|
"total_containers": 0,
|
||||||
|
"running_containers": 0,
|
||||||
|
"stopped_containers": 0,
|
||||||
|
"total_cpu_cores": 0,
|
||||||
|
"total_memory_mb": 0,
|
||||||
|
"used_cpu_cores": 0,
|
||||||
|
"used_memory_mb": 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes = append(nodes, node)
|
nodes = append(nodes, node)
|
||||||
@ -1327,13 +1328,6 @@ func calculateClusterResources(nodes []map[string]any, storages []map[string]any
|
|||||||
"online_total": 0,
|
"online_total": 0,
|
||||||
"online_used": 0,
|
"online_used": 0,
|
||||||
},
|
},
|
||||||
"storage": map[string]any{
|
|
||||||
"total_gb": 0.0,
|
|
||||||
"used_gb": 0.0,
|
|
||||||
"avail_gb": 0.0,
|
|
||||||
"shared_gb": 0.0,
|
|
||||||
"local_gb": 0.0,
|
|
||||||
},
|
|
||||||
"nodes": map[string]any{
|
"nodes": map[string]any{
|
||||||
"total": 0,
|
"total": 0,
|
||||||
"online": 0,
|
"online": 0,
|
||||||
@ -1420,49 +1414,105 @@ func calculateClusterResources(nodes []map[string]any, storages []map[string]any
|
|||||||
result["nodes"].(map[string]any)["total"] = totalNodes
|
result["nodes"].(map[string]any)["total"] = totalNodes
|
||||||
result["nodes"].(map[string]any)["online"] = onlineNodes
|
result["nodes"].(map[string]any)["online"] = onlineNodes
|
||||||
|
|
||||||
// Агрегируем данные по хранилищам (если переданы)
|
return result, nil
|
||||||
if storages != nil {
|
}
|
||||||
totalStorageSize := 0.0
|
|
||||||
totalStorageUsed := 0.0
|
|
||||||
totalStorageAvail := 0.0
|
|
||||||
sharedStorageSize := 0.0
|
|
||||||
localStorageSize := 0.0
|
|
||||||
|
|
||||||
for _, storage := range storages {
|
// getNodeVMInfo получает краткую информацию о виртуальных машинах на ноде
|
||||||
if size, ok := storage["total_gb"].(float64); ok {
|
func getNodeVMInfo(ctx context.Context, nodeName string) (map[string]any, error) {
|
||||||
totalStorageSize += size
|
result := map[string]any{
|
||||||
}
|
"total_vms": 0,
|
||||||
if used, ok := storage["used_gb"].(float64); ok {
|
"running_vms": 0,
|
||||||
totalStorageUsed += used
|
"stopped_vms": 0,
|
||||||
}
|
"total_containers": 0,
|
||||||
if avail, ok := storage["avail_gb"].(float64); ok {
|
"running_containers": 0,
|
||||||
totalStorageAvail += avail
|
"stopped_containers": 0,
|
||||||
}
|
"total_cpu_cores": 0,
|
||||||
|
"total_memory_mb": 0,
|
||||||
|
"used_cpu_cores": 0,
|
||||||
|
"used_memory_mb": 0,
|
||||||
|
}
|
||||||
|
|
||||||
// Разделяем на shared и local
|
// Проверяем наличие pvesh
|
||||||
if shared, ok := storage["shared"].(bool); ok && shared {
|
if _, err := exec.LookPath("pvesh"); err != nil {
|
||||||
if size, ok := storage["total_gb"].(float64); ok {
|
return result, nil
|
||||||
sharedStorageSize += size
|
}
|
||||||
|
|
||||||
|
// Получаем информацию о VM (QEMU)
|
||||||
|
vmOut, err := exec.CommandContext(ctx, "pvesh", "get", fmt.Sprintf("/nodes/%s/qemu", nodeName), "--output-format", "json").Output()
|
||||||
|
if err == nil {
|
||||||
|
var vmData []map[string]any
|
||||||
|
if err := json.Unmarshal(vmOut, &vmData); err == nil {
|
||||||
|
totalVMs := len(vmData)
|
||||||
|
runningVMs := 0
|
||||||
|
totalCPUCores := 0
|
||||||
|
totalMemory := 0
|
||||||
|
usedCPUCores := 0
|
||||||
|
usedMemory := 0
|
||||||
|
|
||||||
|
for _, vm := range vmData {
|
||||||
|
// Подсчитываем статус
|
||||||
|
if status, ok := vm["status"].(string); ok && status == "running" {
|
||||||
|
runningVMs++
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if size, ok := storage["total_gb"].(float64); ok {
|
// Подсчитываем ресурсы
|
||||||
localStorageSize += size
|
if maxCPU, ok := vm["maxcpu"].(float64); ok {
|
||||||
|
totalCPUCores += int(maxCPU)
|
||||||
|
}
|
||||||
|
if maxMem, ok := vm["maxmem"].(float64); ok {
|
||||||
|
totalMemory += int(maxMem)
|
||||||
|
}
|
||||||
|
if cpu, ok := vm["cpu"].(float64); ok {
|
||||||
|
usedCPUCores += int(cpu)
|
||||||
|
}
|
||||||
|
if mem, ok := vm["mem"].(float64); ok {
|
||||||
|
usedMemory += int(mem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result["total_vms"] = totalVMs
|
||||||
|
result["running_vms"] = runningVMs
|
||||||
|
result["stopped_vms"] = totalVMs - runningVMs
|
||||||
|
result["total_cpu_cores"] = totalCPUCores
|
||||||
|
result["total_memory_mb"] = totalMemory
|
||||||
|
result["used_cpu_cores"] = usedCPUCores
|
||||||
|
result["used_memory_mb"] = usedMemory
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result["storage"].(map[string]any)["total_gb"] = totalStorageSize
|
// Получаем информацию о контейнерах (LXC)
|
||||||
result["storage"].(map[string]any)["used_gb"] = totalStorageUsed
|
ctOut, err := exec.CommandContext(ctx, "pvesh", "get", fmt.Sprintf("/nodes/%s/lxc", nodeName), "--output-format", "json").Output()
|
||||||
result["storage"].(map[string]any)["avail_gb"] = totalStorageAvail
|
if err == nil {
|
||||||
result["storage"].(map[string]any)["shared_gb"] = sharedStorageSize
|
var ctData []map[string]any
|
||||||
result["storage"].(map[string]any)["local_gb"] = localStorageSize
|
if err := json.Unmarshal(ctOut, &ctData); err == nil {
|
||||||
} else {
|
totalContainers := len(ctData)
|
||||||
// Если storages не переданы, устанавливаем нулевые значения
|
runningContainers := 0
|
||||||
result["storage"].(map[string]any)["total_gb"] = 0.0
|
|
||||||
result["storage"].(map[string]any)["used_gb"] = 0.0
|
for _, ct := range ctData {
|
||||||
result["storage"].(map[string]any)["avail_gb"] = 0.0
|
// Подсчитываем статус
|
||||||
result["storage"].(map[string]any)["shared_gb"] = 0.0
|
if status, ok := ct["status"].(string); ok && status == "running" {
|
||||||
result["storage"].(map[string]any)["local_gb"] = 0.0
|
runningContainers++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем ресурсы контейнеров к общим
|
||||||
|
if maxCPU, ok := ct["maxcpu"].(float64); ok {
|
||||||
|
result["total_cpu_cores"] = result["total_cpu_cores"].(int) + int(maxCPU)
|
||||||
|
}
|
||||||
|
if maxMem, ok := ct["maxmem"].(float64); ok {
|
||||||
|
result["total_memory_mb"] = result["total_memory_mb"].(int) + int(maxMem)
|
||||||
|
}
|
||||||
|
if cpu, ok := ct["cpu"].(float64); ok {
|
||||||
|
result["used_cpu_cores"] = result["used_cpu_cores"].(int) + int(cpu)
|
||||||
|
}
|
||||||
|
if mem, ok := ct["mem"].(float64); ok {
|
||||||
|
result["used_memory_mb"] = result["used_memory_mb"].(int) + int(mem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result["total_containers"] = totalContainers
|
||||||
|
result["running_containers"] = runningContainers
|
||||||
|
result["stopped_containers"] = totalContainers - runningContainers
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
Loading…
x
Reference in New Issue
Block a user