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
|
||||
|
||||
### Виртуальные машины Proxmox
|
||||
- `pvesh` - утилита командной строки Proxmox VE
|
||||
- Доступ к `/etc/corosync/corosync.conf` или `/etc/pve/corosync.conf` для получения cluster_uuid
|
||||
- `pvesh` - утилита командной строки Proxmox VE (для хостовых систем)
|
||||
- Доступ к `/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
|
||||
```json
|
||||
{
|
||||
"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]
|
||||
}
|
||||
|
||||
// getClusterUUID получает cluster_uuid из corosync.conf
|
||||
// getClusterUUID получает cluster_uuid из различных источников
|
||||
func getClusterUUID() string {
|
||||
// Пробуем разные пути к corosync.conf
|
||||
paths := []string{
|
||||
// 1. Приоритет: переменная окружения
|
||||
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/pve/corosync.conf",
|
||||
"/var/lib/pve-cluster/corosync.conf",
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
for _, path := range corosyncPaths {
|
||||
if data, err := os.ReadFile(path); err == nil {
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
@ -332,21 +369,265 @@ func getClusterUUID() string {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Автоматическое определение через анализ системы
|
||||
if clusterUUID := detectClusterUUIDFromSystem(); clusterUUID != "" {
|
||||
return clusterUUID
|
||||
}
|
||||
|
||||
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
|
||||
func collectVMInfo(ctx context.Context) ([]map[string]any, error) {
|
||||
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") {
|
||||
return vms, nil
|
||||
}
|
||||
|
||||
// Получаем cluster_uuid
|
||||
clusterUUID := getClusterUUID()
|
||||
|
||||
// Получаем список всех нод
|
||||
out, err := run(ctx, "pvesh", "get", "/nodes", "--output-format", "json")
|
||||
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)
|
||||
}
|
||||
|
||||
// Список всех endpoints для сбора информации о кластере
|
||||
// Список endpoints для сбора информации о кластере (очищенный от лишних данных)
|
||||
clusterEndpoints := []string{
|
||||
"/cluster/config/nodes",
|
||||
"/cluster/status",
|
||||
"/cluster/config/totem",
|
||||
"/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/nextid",
|
||||
"/cluster/config/join",
|
||||
"/cluster/config/apiclient",
|
||||
"/cluster/config/totem",
|
||||
"/cluster/config/corosync",
|
||||
}
|
||||
|
||||
// Собираем данные со всех endpoints
|
||||
@ -609,6 +593,11 @@ func collectDetailedNodesInfo(ctx context.Context, clusterName, clusterUUID stri
|
||||
if hwInfo, err := getNodeHardwareInfo(ctx); err == nil {
|
||||
node["hardware"] = hwInfo
|
||||
}
|
||||
|
||||
// Информация о виртуальных машинах на ноде
|
||||
if vmInfo, err := getNodeVMInfo(ctx, nodeName); err == nil {
|
||||
node["vm_summary"] = vmInfo
|
||||
}
|
||||
} else {
|
||||
// Для офлайн нод заполняем пустыми значениями в правильном порядке
|
||||
node["corosync_ip"] = ""
|
||||
@ -633,6 +622,18 @@ func collectDetailedNodesInfo(ctx context.Context, clusterName, clusterUUID stri
|
||||
"threads": 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)
|
||||
@ -1327,13 +1328,6 @@ func calculateClusterResources(nodes []map[string]any, storages []map[string]any
|
||||
"online_total": 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{
|
||||
"total": 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)["online"] = onlineNodes
|
||||
|
||||
// Агрегируем данные по хранилищам (если переданы)
|
||||
if storages != nil {
|
||||
totalStorageSize := 0.0
|
||||
totalStorageUsed := 0.0
|
||||
totalStorageAvail := 0.0
|
||||
sharedStorageSize := 0.0
|
||||
localStorageSize := 0.0
|
||||
|
||||
for _, storage := range storages {
|
||||
if size, ok := storage["total_gb"].(float64); ok {
|
||||
totalStorageSize += size
|
||||
}
|
||||
if used, ok := storage["used_gb"].(float64); ok {
|
||||
totalStorageUsed += used
|
||||
}
|
||||
if avail, ok := storage["avail_gb"].(float64); ok {
|
||||
totalStorageAvail += avail
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Разделяем на shared и local
|
||||
if shared, ok := storage["shared"].(bool); ok && shared {
|
||||
if size, ok := storage["total_gb"].(float64); ok {
|
||||
sharedStorageSize += size
|
||||
// getNodeVMInfo получает краткую информацию о виртуальных машинах на ноде
|
||||
func getNodeVMInfo(ctx context.Context, nodeName string) (map[string]any, error) {
|
||||
result := 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,
|
||||
}
|
||||
} else {
|
||||
if size, ok := storage["total_gb"].(float64); ok {
|
||||
localStorageSize += size
|
||||
|
||||
// Проверяем наличие pvesh
|
||||
if _, err := exec.LookPath("pvesh"); err != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Получаем информацию о 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++
|
||||
}
|
||||
|
||||
// Подсчитываем ресурсы
|
||||
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["storage"].(map[string]any)["total_gb"] = totalStorageSize
|
||||
result["storage"].(map[string]any)["used_gb"] = totalStorageUsed
|
||||
result["storage"].(map[string]any)["avail_gb"] = totalStorageAvail
|
||||
result["storage"].(map[string]any)["shared_gb"] = sharedStorageSize
|
||||
result["storage"].(map[string]any)["local_gb"] = localStorageSize
|
||||
} else {
|
||||
// Если storages не переданы, устанавливаем нулевые значения
|
||||
result["storage"].(map[string]any)["total_gb"] = 0.0
|
||||
result["storage"].(map[string]any)["used_gb"] = 0.0
|
||||
result["storage"].(map[string]any)["avail_gb"] = 0.0
|
||||
result["storage"].(map[string]any)["shared_gb"] = 0.0
|
||||
result["storage"].(map[string]any)["local_gb"] = 0.0
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем информацию о контейнерах (LXC)
|
||||
ctOut, err := exec.CommandContext(ctx, "pvesh", "get", fmt.Sprintf("/nodes/%s/lxc", nodeName), "--output-format", "json").Output()
|
||||
if err == nil {
|
||||
var ctData []map[string]any
|
||||
if err := json.Unmarshal(ctOut, &ctData); err == nil {
|
||||
totalContainers := len(ctData)
|
||||
runningContainers := 0
|
||||
|
||||
for _, ct := range ctData {
|
||||
// Подсчитываем статус
|
||||
if status, ok := ct["status"].(string); ok && status == "running" {
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user