GoDoxy/internal/proxmox/lxc.go
Yuzerion 57292f0fe8
feat: proxmox idlewatcher (#88)
* feat: idle sleep for proxmox LXCs

* refactor: replace deprecated docker api types

* chore(api): remove debug task list endpoint

* refactor: move servemux to gphttp/servemux; favicon.go to v1/favicon

* refactor: introduce Pool interface, move agent_pool to agent module

* refactor: simplify api code

* feat: introduce debug api

* refactor: remove net.URL and net.CIDR types, improved unmarshal handling

* chore: update Makefile for debug build tag, update README

* chore: add gperr.Unwrap method

* feat: relative time and duration formatting

* chore: add ROOT_DIR environment variable, refactor

* migration: move homepage override and icon cache to $BASE_DIR/data, add migration code

* fix: nil dereference on marshalling service health

* fix: wait for route deletion

* chore: enhance tasks debuggability

* feat: stdout access logger and MultiWriter

* fix(agent): remove agent properly on verify error

* fix(metrics): disk exclusion logic and added corresponding tests

* chore: update schema and prettify, fix package.json and Makefile

* fix: I/O buffer not being shrunk before putting back to pool

* feat: enhanced error handling module

* chore: deps upgrade

* feat: better value formatting and handling

---------

Co-authored-by: yusing <yusing@6uo.me>
2025-04-16 14:52:33 +08:00

236 lines
5.7 KiB
Go

package proxmox
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/luthermonson/go-proxmox"
)
type (
LXCAction string
LXCStatus string
statusOnly struct {
Status LXCStatus `json:"status"`
}
nameOnly struct {
Name string `json:"name"`
}
)
const (
LXCStart LXCAction = "start"
LXCShutdown LXCAction = "shutdown"
LXCSuspend LXCAction = "suspend"
LXCResume LXCAction = "resume"
LXCReboot LXCAction = "reboot"
)
const (
LXCStatusRunning LXCStatus = "running"
LXCStatusStopped LXCStatus = "stopped"
LXCStatusSuspended LXCStatus = "suspended" // placeholder, suspending lxc is experimental and the enum is undocumented
)
const (
proxmoxReqTimeout = 3 * time.Second
proxmoxTaskCheckInterval = 300 * time.Millisecond
)
func (n *Node) LXCAction(ctx context.Context, vmid int, action LXCAction) error {
var upid proxmox.UPID
if err := n.client.Post(ctx, fmt.Sprintf("/nodes/%s/lxc/%d/status/%s", n.name, vmid, action), nil, &upid); err != nil {
return err
}
task := proxmox.NewTask(upid, n.client)
checkTicker := time.NewTicker(proxmoxTaskCheckInterval)
defer checkTicker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-checkTicker.C:
if err := task.Ping(ctx); err != nil {
return err
}
if task.Status != proxmox.TaskRunning {
status, err := n.LXCStatus(ctx, vmid)
if err != nil {
return err
}
switch status {
case LXCStatusRunning:
if action == LXCStart {
return nil
}
case LXCStatusStopped:
if action == LXCShutdown {
return nil
}
case LXCStatusSuspended:
if action == LXCSuspend {
return nil
}
}
}
}
}
}
func (n *Node) LXCName(ctx context.Context, vmid int) (string, error) {
var name nameOnly
if err := n.client.Get(ctx, fmt.Sprintf("/nodes/%s/lxc/%d/status/current", n.name, vmid), &name); err != nil {
return "", err
}
return name.Name, nil
}
func (n *Node) LXCStatus(ctx context.Context, vmid int) (LXCStatus, error) {
var status statusOnly
if err := n.client.Get(ctx, fmt.Sprintf("/nodes/%s/lxc/%d/status/current", n.name, vmid), &status); err != nil {
return "", err
}
return status.Status, nil
}
func (n *Node) LXCIsRunning(ctx context.Context, vmid int) (bool, error) {
status, err := n.LXCStatus(ctx, vmid)
return status == LXCStatusRunning, err
}
func (n *Node) LXCIsStopped(ctx context.Context, vmid int) (bool, error) {
status, err := n.LXCStatus(ctx, vmid)
return status == LXCStatusStopped, err
}
func (n *Node) LXCSetShutdownTimeout(ctx context.Context, vmid int, timeout time.Duration) error {
return n.client.Put(ctx, fmt.Sprintf("/nodes/%s/lxc/%d/config", n.name, vmid), map[string]interface{}{
"startup": fmt.Sprintf("down=%.0f", timeout.Seconds()),
}, nil)
}
func parseCIDR(s string) net.IP {
if s == "" {
return nil
}
ip, _, err := net.ParseCIDR(s)
if err != nil {
return nil
}
return checkIPPrivate(ip)
}
func checkIPPrivate(ip net.IP) net.IP {
if ip == nil {
return nil
}
if ip.IsPrivate() {
return ip
}
return nil
}
func getIPFromNet(s string) (res []net.IP) { // name:...,bridge:...,gw=..,ip=...,ip6=...
if s == "" {
return nil
}
var i4, i6 net.IP
cidrIndex := strings.Index(s, "ip=")
if cidrIndex != -1 {
cidrIndex += 3
slash := strings.Index(s[cidrIndex:], "/")
if slash != -1 {
i4 = checkIPPrivate(net.ParseIP(s[cidrIndex : cidrIndex+slash]))
} else {
i4 = checkIPPrivate(net.ParseIP(s[cidrIndex:]))
}
}
cidr6Index := strings.Index(s, "ip6=")
if cidr6Index != -1 {
cidr6Index += 4
slash := strings.Index(s[cidr6Index:], "/")
if slash != -1 {
i6 = checkIPPrivate(net.ParseIP(s[cidr6Index : cidr6Index+slash]))
} else {
i6 = checkIPPrivate(net.ParseIP(s[cidr6Index:]))
}
}
if i4 != nil {
res = append(res, i4)
}
if i6 != nil {
res = append(res, i6)
}
return res
}
// LXCGetIPs returns the ip addresses of the container
// it first tries to get the ip addresses from the config
// if that fails, it gets the ip addresses from the interfaces
func (n *Node) LXCGetIPs(ctx context.Context, vmid int) (res []net.IP, err error) {
ips, err := n.LXCGetIPsFromConfig(ctx, vmid)
if err != nil {
return nil, err
}
if len(ips) > 0 {
return ips, nil
}
ips, err = n.LXCGetIPsFromInterfaces(ctx, vmid)
if err != nil {
return nil, err
}
return ips, nil
}
// LXCGetIPsFromConfig returns the ip addresses of the container from the config
func (n *Node) LXCGetIPsFromConfig(ctx context.Context, vmid int) (res []net.IP, err error) {
type Config struct {
Net0 string `json:"net0"`
Net1 string `json:"net1"`
Net2 string `json:"net2"`
}
var cfg Config
if err := n.client.Get(ctx, fmt.Sprintf("/nodes/%s/lxc/%d/config", n.name, vmid), &cfg); err != nil {
return nil, err
}
res = append(res, getIPFromNet(cfg.Net0)...)
res = append(res, getIPFromNet(cfg.Net1)...)
res = append(res, getIPFromNet(cfg.Net2)...)
return res, nil
}
// LXCGetIPsFromInterfaces returns the ip addresses of the container from the interfaces
// it will return nothing if the container is stopped
func (n *Node) LXCGetIPsFromInterfaces(ctx context.Context, vmid int) ([]net.IP, error) {
type Interface struct {
IPv4 string `json:"inet"`
IPv6 string `json:"inet6"`
Name string `json:"name"`
}
var res []Interface
if err := n.client.Get(ctx, fmt.Sprintf("/nodes/%s/lxc/%d/interfaces", n.name, vmid), &res); err != nil {
return nil, err
}
ips := make([]net.IP, 0)
for _, ip := range res {
if ip.Name == "lo" ||
strings.HasPrefix(ip.Name, "br-") ||
strings.HasPrefix(ip.Name, "veth") ||
strings.HasPrefix(ip.Name, "docker") {
continue
}
if ip := parseCIDR(ip.IPv4); ip != nil {
ips = append(ips, ip)
}
if ip := parseCIDR(ip.IPv6); ip != nil {
ips = append(ips, ip)
}
}
return ips, nil
}