mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-11-13 08:06:06 +00:00
152 lines
4.7 KiB
Go
152 lines
4.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/joho/godotenv"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func LoadModelGPUUsage(ctx context.Context, req dto.ModelGPUUsageRequest) (dto.ModelGPUUsage, error) {
|
|
topology := LoadModelGPUDevices()
|
|
result := dto.ModelGPUUsage{Devices: []dto.ModelGPUUsageDevice{}, Warnings: topology.Warnings}
|
|
result.Warnings = append(result.Warnings, topology.VendorWarnings[req.ImageType]...)
|
|
values, err := godotenv.Unmarshal(req.Env)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
params := make(map[string]interface{}, len(values))
|
|
for key, value := range values {
|
|
params[key] = value
|
|
}
|
|
var project struct {
|
|
Services map[string]map[string]interface{} `yaml:"services"`
|
|
}
|
|
if err := yaml.Unmarshal([]byte(req.DockerCompose), &project); err != nil {
|
|
return result, err
|
|
}
|
|
selected, err := modelGPUUsageSelection(project.Services, params, req.ImageType, topology.Devices)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
for _, device := range topology.Devices {
|
|
if possible, ok := selected[device.ID]; ok {
|
|
result.Devices = append(result.Devices, dto.ModelGPUUsageDevice{ModelGPUDevice: device, Possible: possible, Deployments: []dto.ModelGPUDeployment{}})
|
|
}
|
|
}
|
|
if len(result.Devices) == 0 {
|
|
return result, nil
|
|
}
|
|
installs, err := appInstallRepo.ListBy(ctx)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
defer client.Close()
|
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
containers, err := client.ContainerList(ctx, container.ListOptions{All: true})
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
states := make(map[string]string)
|
|
for _, item := range containers {
|
|
for _, name := range item.Names {
|
|
states[strings.TrimPrefix(name, "/")] = item.State
|
|
}
|
|
}
|
|
for _, install := range installs {
|
|
if install.ID == req.ExcludeInstallID {
|
|
continue
|
|
}
|
|
var project struct {
|
|
Services map[string]map[string]interface{} `yaml:"services"`
|
|
}
|
|
if err := yaml.Unmarshal([]byte(install.DockerCompose), &project); err != nil {
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("%s: %v", install.Name, err))
|
|
continue
|
|
}
|
|
var env map[string]interface{}
|
|
if err := json.Unmarshal([]byte(install.Env), &env); err != nil {
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("%s: %v", install.Name, err))
|
|
continue
|
|
}
|
|
for serviceName, service := range project.Services {
|
|
devices, err := modelGPUUsageSelection(map[string]map[string]interface{}{serviceName: service}, env, req.ImageType, topology.Devices)
|
|
if err != nil {
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("%s/%s: %v", install.Name, serviceName, err))
|
|
continue
|
|
}
|
|
name := install.ContainerName
|
|
if raw, ok := service["container_name"].(string); ok {
|
|
name, err = resolveModelGPUValue(raw, env)
|
|
if err != nil {
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("%s: %v", install.Name, err))
|
|
continue
|
|
}
|
|
} else if len(project.Services) != 1 {
|
|
name = ""
|
|
}
|
|
status := states[name]
|
|
if status == "" {
|
|
status = "unknown"
|
|
}
|
|
for i := range result.Devices {
|
|
if possible, ok := devices[result.Devices[i].ID]; ok {
|
|
result.Devices[i].Deployments = append(result.Devices[i].Deployments, dto.ModelGPUDeployment{Name: install.Name + "/" + serviceName, Status: status, Possible: possible})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func modelGPUUsageSelection(services map[string]map[string]interface{}, params map[string]interface{}, imageType string, devices []dto.ModelGPUDevice) (map[string]bool, error) {
|
|
if len(services) == 0 {
|
|
return nil, fmt.Errorf("Docker Compose must contain services")
|
|
}
|
|
selected := map[string]bool{}
|
|
for _, service := range services {
|
|
config := ReadModelGPU(service, params, imageType)
|
|
if config.Error != "" {
|
|
return nil, fmt.Errorf("%s", config.Error)
|
|
}
|
|
if !config.Enabled {
|
|
continue
|
|
}
|
|
matched := false
|
|
for _, device := range devices {
|
|
if device.Type != imageType {
|
|
continue
|
|
}
|
|
matches := config.Mode == "all" || config.Mode == "count" || slices.Contains(config.DeviceIDs, device.ID)
|
|
if imageType == "nvidia" {
|
|
matches = matches || slices.Contains(config.DeviceIDs, "nvidia:"+device.UUID) || slices.Contains(config.DeviceIDs, "nvidia:"+strconv.Itoa(device.Index))
|
|
}
|
|
if matches {
|
|
matched = true
|
|
possible := config.Mode == "count"
|
|
if previous, exists := selected[device.ID]; !exists || previous {
|
|
selected[device.ID] = possible
|
|
}
|
|
}
|
|
}
|
|
if !matched {
|
|
return nil, fmt.Errorf("no detected %s GPU matches the deployment", imageType)
|
|
}
|
|
}
|
|
return selected, nil
|
|
}
|