功能:扩展云管资源运维、账号授权及监控管理
This commit is contained in:
1 parent
eef485306f
commit
6134d49c16
87 files changed
+29498
-126
No files matched your search
@@ -0,0 +1,147 @@
|
||||
package proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VMDisk struct {
|
||||
Key string `json:"key"`
|
||||
Storage string `json:"storage"`
|
||||
SizeGiB float64 `json:"sizeGiB"`
|
||||
}
|
||||
type VMHardware struct {
|
||||
Cores int `json:"cores"`
|
||||
Sockets int `json:"sockets"`
|
||||
MemoryMiB int `json:"memoryMiB"`
|
||||
Digest string `json:"digest"`
|
||||
Disks []VMDisk `json:"disks"`
|
||||
PowerState string `json:"powerState"`
|
||||
Blockers []string `json:"blockers"`
|
||||
Balloon int `json:"-"`
|
||||
}
|
||||
|
||||
func ConfigInt(config map[string]any, key string, fallback int) int {
|
||||
value, ok := config[key]
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
n, err := strconv.Atoi(fmt.Sprint(value))
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return n
|
||||
}
|
||||
func ReadHardware(config map[string]any) (VMHardware, error) {
|
||||
out := VMHardware{Cores: ConfigInt(config, "cores", 1), Sockets: ConfigInt(config, "sockets", 1), MemoryMiB: ConfigInt(config, "memory", 512), Balloon: ConfigInt(config, "balloon", 0), Disks: []VMDisk{}, Blockers: []string{}}
|
||||
out.Digest, _ = config["digest"].(string)
|
||||
if out.Digest == "" {
|
||||
return out, errors.New("配置摘要缺失")
|
||||
}
|
||||
if ConfigInt(config, "template", 0) == 1 {
|
||||
out.Blockers = append(out.Blockers, "模板不能在桌面规格页修改")
|
||||
}
|
||||
if lock := config["lock"]; lock != nil && fmt.Sprint(lock) != "" {
|
||||
out.Blockers = append(out.Blockers, "虚拟机处于 PVE 锁定状态")
|
||||
}
|
||||
for key, value := range config {
|
||||
if strings.HasPrefix(key, "numa") && key != "numa" {
|
||||
out.Blockers = append(out.Blockers, "自定义 NUMA 拓扑请在 PVE 维护")
|
||||
}
|
||||
if !regexp.MustCompile(`^(scsi|sata|virtio|ide)\d+$`).MatchString(key) {
|
||||
continue
|
||||
}
|
||||
text := fmt.Sprint(value)
|
||||
if strings.Contains(text, "media=cdrom") || strings.Contains(text, "cloudinit") {
|
||||
continue
|
||||
}
|
||||
volume := strings.Split(text, ",")[0]
|
||||
storage, _, ok := strings.Cut(volume, ":")
|
||||
if !ok || storage == "" {
|
||||
continue
|
||||
}
|
||||
for _, part := range strings.Split(text, ",") {
|
||||
if !strings.HasPrefix(part, "size=") {
|
||||
continue
|
||||
}
|
||||
size := strings.TrimPrefix(part, "size=")
|
||||
if len(size) == 0 {
|
||||
continue
|
||||
}
|
||||
factor := float64(1)
|
||||
if i := strings.IndexByte("KMGT", size[len(size)-1]); i >= 0 {
|
||||
factor = math.Pow(1024, float64(i+1))
|
||||
size = size[:len(size)-1]
|
||||
}
|
||||
n, e := strconv.ParseFloat(size, 64)
|
||||
if e == nil && n > 0 && !math.IsInf(n, 0) && !math.IsNaN(n) {
|
||||
out.Disks = append(out.Disks, VMDisk{key, storage, n * factor / (1 << 30)})
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(out.Disks, func(i, j int) bool { return out.Disks[i].Key < out.Disks[j].Key })
|
||||
return out, nil
|
||||
}
|
||||
func (c *Client) Hardware(ctx context.Context, node string, vmid int) (VMHardware, map[string]any, error) {
|
||||
var config map[string]any
|
||||
path := fmt.Sprintf("/nodes/%s/qemu/%d", url.PathEscape(node), vmid)
|
||||
err := c.request(ctx, "GET", path+"/config?current=1", nil, &config)
|
||||
if err != nil {
|
||||
return VMHardware{}, nil, err
|
||||
}
|
||||
hardware, err := ReadHardware(config)
|
||||
if err != nil {
|
||||
return hardware, config, err
|
||||
}
|
||||
hardware.PowerState, err = c.Power(ctx, node, vmid)
|
||||
if err != nil {
|
||||
return hardware, config, err
|
||||
}
|
||||
var pending []map[string]any
|
||||
err = c.request(ctx, "GET", path+"/pending", nil, &pending)
|
||||
if err != nil {
|
||||
return hardware, config, err
|
||||
}
|
||||
for _, item := range pending {
|
||||
if _, ok := item["pending"]; ok || ConfigInt(item, "delete", 0) > 0 {
|
||||
hardware.Blockers = append(hardware.Blockers, "存在待应用配置,请先在 PVE 处理")
|
||||
break
|
||||
}
|
||||
}
|
||||
return hardware, config, nil
|
||||
}
|
||||
func (c *Client) ConfigureHardware(ctx context.Context, node string, vmid, cores, sockets, memory int, digest string, current map[string]any) (string, error) {
|
||||
form := url.Values{"digest": {digest}}
|
||||
cpuChanged := cores != ConfigInt(current, "cores", 1) || sockets != ConfigInt(current, "sockets", 1)
|
||||
if cpuChanged {
|
||||
form.Set("cores", strconv.Itoa(cores))
|
||||
form.Set("sockets", strconv.Itoa(sockets))
|
||||
}
|
||||
if memory != ConfigInt(current, "memory", 512) {
|
||||
form.Set("memory", strconv.Itoa(memory))
|
||||
}
|
||||
if _, ok := current["vcpus"]; ok && cpuChanged {
|
||||
form.Set("vcpus", strconv.Itoa(cores*sockets))
|
||||
}
|
||||
var upid string
|
||||
err := c.request(ctx, "POST", fmt.Sprintf("/nodes/%s/qemu/%d/config", url.PathEscape(node), vmid), form, &upid)
|
||||
if err == nil && upid == "" {
|
||||
err = errors.New("PVE did not return a configuration task ID")
|
||||
}
|
||||
return upid, err
|
||||
}
|
||||
func (c *Client) ResizeDisk(ctx context.Context, node string, vmid int, disk string, sizeGiB int, digest string) (string, error) {
|
||||
var upid string
|
||||
err := c.request(ctx, "PUT", fmt.Sprintf("/nodes/%s/qemu/%d/resize", url.PathEscape(node), vmid), url.Values{"disk": {disk}, "size": {strconv.Itoa(sizeGiB) + "G"}, "digest": {digest}}, &upid)
|
||||
if err == nil && upid == "" {
|
||||
err = errors.New("PVE did not return a resize task ID")
|
||||
}
|
||||
return upid, err
|
||||
}
|
||||
Reference in new issue
Block a user