mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-17 23:03:49 +01:00
feat(proxmox): better node-level routes auto-discovery with pointer VMID
- Add BaseURL field to Client for node-level route configuration - Change VMID from int to *int to support three states: - nil: auto-discover node or VM from hostname/IP/alias - 0: node-level route (direct to Proxmox node API) - >0: LXC/QEMU resource route with container control - Change Service string to Services []string for multi-service support - Implement proper node-level route handling: HTTPS scheme, hostname from node BaseURL, default port 8006 - Move initial UpdateResources call to Init before starting loop - Move proxmox auto-discovery earlier in route validation BREAKING: NodeConfig.VMID is now a pointer type; NodeConfig.Service renamed to Services (backward compatible via alias)
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -21,6 +22,7 @@ type Client struct {
|
||||
*proxmox.Client
|
||||
*proxmox.Cluster
|
||||
Version *proxmox.Version
|
||||
BaseURL *url.URL
|
||||
// id -> resource; id: lxc/<vmid> or qemu/<vmid>
|
||||
resources map[string]*VMResource
|
||||
resourcesMu sync.RWMutex
|
||||
@@ -44,6 +46,11 @@ func NewClient(baseUrl string, opts ...proxmox.Option) *Client {
|
||||
}
|
||||
|
||||
func (c *Client) UpdateClusterInfo(ctx context.Context) (err error) {
|
||||
baseURL, err := url.Parse(c.Client.GetBaseURL())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.BaseURL = baseURL
|
||||
c.Version, err = c.Client.Version(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -96,6 +96,15 @@ func (c *Config) Init(ctx context.Context) gperr.Error {
|
||||
return gperr.New("failed to fetch proxmox cluster info").With(err)
|
||||
}
|
||||
|
||||
{
|
||||
reqCtx, reqCtxCancel := context.WithTimeout(ctx, ResourcePollInterval)
|
||||
err := c.client.UpdateResources(reqCtx)
|
||||
reqCtxCancel()
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("cluster", c.client.Cluster.Name).Msg("[proxmox] failed to update resources")
|
||||
}
|
||||
}
|
||||
|
||||
go c.updateResourcesLoop(ctx)
|
||||
return nil
|
||||
}
|
||||
@@ -106,15 +115,6 @@ func (c *Config) updateResourcesLoop(ctx context.Context) {
|
||||
|
||||
log.Trace().Str("cluster", c.client.Cluster.Name).Msg("[proxmox] starting resources update loop")
|
||||
|
||||
{
|
||||
reqCtx, reqCtxCancel := context.WithTimeout(ctx, ResourcePollInterval)
|
||||
err := c.client.UpdateResources(reqCtx)
|
||||
reqCtxCancel()
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("cluster", c.client.Cluster.Name).Msg("[proxmox] failed to update resources")
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
)
|
||||
|
||||
type NodeConfig struct {
|
||||
Node string `json:"node" validate:"required"`
|
||||
VMID int `json:"vmid" validate:"required"`
|
||||
VMName string `json:"vmname,omitempty"`
|
||||
Service string `json:"service,omitempty"`
|
||||
Node string `json:"node"`
|
||||
VMID *int `json:"vmid"` // unset: auto discover; explicit 0: node-level route; >0: lxc/qemu resource route
|
||||
VMName string `json:"vmname,omitempty"`
|
||||
Services []string `json:"services,omitempty" aliases:"service"`
|
||||
} // @name ProxmoxNodeConfig
|
||||
|
||||
type Node struct {
|
||||
|
||||
@@ -185,21 +185,66 @@ func (r *Route) validate() gperr.Error {
|
||||
if r.Proxmox != nil && r.Idlewatcher != nil {
|
||||
r.Idlewatcher.Proxmox = &types.ProxmoxConfig{
|
||||
Node: r.Proxmox.Node,
|
||||
VMID: r.Proxmox.VMID,
|
||||
}
|
||||
if r.Proxmox.VMID != nil {
|
||||
r.Idlewatcher.Proxmox.VMID = *r.Proxmox.VMID
|
||||
}
|
||||
}
|
||||
|
||||
if r.Proxmox == nil && r.Idlewatcher != nil && r.Idlewatcher.Proxmox != nil {
|
||||
r.Proxmox = &proxmox.NodeConfig{
|
||||
Node: r.Idlewatcher.Proxmox.Node,
|
||||
VMID: r.Idlewatcher.Proxmox.VMID,
|
||||
VMID: &r.Idlewatcher.Proxmox.VMID,
|
||||
}
|
||||
}
|
||||
|
||||
if (r.Proxmox == nil || r.Proxmox.Node == "" || r.Proxmox.VMID == nil) && r.Container == nil {
|
||||
proxmoxProviders := config.WorkingState.Load().Value().Providers.Proxmox
|
||||
if len(proxmoxProviders) > 0 {
|
||||
// it's fine if ip is nil
|
||||
hostname := r.Host
|
||||
ip := net.ParseIP(hostname)
|
||||
for _, p := range proxmoxProviders {
|
||||
// First check if hostname, IP, or alias matches a node (node-level route)
|
||||
if nodeName := p.Client().ReverseLookupNode(hostname, ip, r.Alias); nodeName != "" {
|
||||
zero := 0
|
||||
if r.Proxmox == nil {
|
||||
r.Proxmox = &proxmox.NodeConfig{}
|
||||
}
|
||||
r.Proxmox.Node = nodeName
|
||||
r.Proxmox.VMID = &zero
|
||||
r.Proxmox.VMName = ""
|
||||
log.Info().
|
||||
Str("node", nodeName).
|
||||
Msgf("found proxmox node for route %q", r.Alias)
|
||||
break
|
||||
}
|
||||
|
||||
// Then check if hostname, IP, or alias matches a VM resource
|
||||
resource, _ := p.Client().ReverseLookupResource(ip, hostname, r.Alias)
|
||||
if resource != nil {
|
||||
vmid := int(resource.VMID)
|
||||
if r.Proxmox == nil {
|
||||
r.Proxmox = &proxmox.NodeConfig{}
|
||||
}
|
||||
r.Proxmox.Node = resource.Node
|
||||
r.Proxmox.VMID = &vmid
|
||||
r.Proxmox.VMName = resource.Name
|
||||
log.Info().
|
||||
Str("node", resource.Node).
|
||||
Int("vmid", int(resource.VMID)).
|
||||
Str("vmname", resource.Name).
|
||||
Msgf("found proxmox resource for route %q", r.Alias)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if r.Proxmox != nil {
|
||||
nodeName := r.Proxmox.Node
|
||||
vmid := r.Proxmox.VMID
|
||||
if nodeName == "" {
|
||||
if nodeName == "" || vmid == nil {
|
||||
return gperr.Errorf("node (proxmox node name) is required")
|
||||
}
|
||||
|
||||
@@ -208,9 +253,19 @@ func (r *Route) validate() gperr.Error {
|
||||
return gperr.Errorf("proxmox node %s not found in pool", nodeName)
|
||||
}
|
||||
|
||||
// Node-level route (VMID = 0) - no container control needed
|
||||
if vmid > 0 {
|
||||
res, err := node.Client().GetResource("lxc", vmid)
|
||||
// Node-level route (VMID = 0)
|
||||
if *vmid == 0 {
|
||||
r.Scheme = route.SchemeHTTPS
|
||||
if r.Host == DefaultHost {
|
||||
r.Host = node.Client().BaseURL.Hostname()
|
||||
}
|
||||
port, _ := strconv.Atoi(node.Client().BaseURL.Port())
|
||||
if port == 0 {
|
||||
port = 8006
|
||||
}
|
||||
r.Port.Proxy = port
|
||||
} else {
|
||||
res, err := node.Client().GetResource("lxc", *vmid)
|
||||
if err != nil {
|
||||
return gperr.Wrap(err) // ErrResourceNotFound
|
||||
}
|
||||
@@ -235,14 +290,14 @@ func (r *Route) validate() gperr.Error {
|
||||
l := log.With().Str("container", containerName).Logger()
|
||||
|
||||
l.Info().Msg("checking if container is running")
|
||||
running, err := node.LXCIsRunning(ctx, vmid)
|
||||
running, err := node.LXCIsRunning(ctx, *vmid)
|
||||
if err != nil {
|
||||
return gperr.New("failed to check container state").With(err)
|
||||
}
|
||||
|
||||
if !running {
|
||||
l.Info().Msg("starting container")
|
||||
if err := node.LXCAction(ctx, vmid, proxmox.LXCStart); err != nil {
|
||||
if err := node.LXCAction(ctx, *vmid, proxmox.LXCStart); err != nil {
|
||||
return gperr.New("failed to start container").With(err)
|
||||
}
|
||||
}
|
||||
@@ -336,45 +391,6 @@ func (r *Route) validate() gperr.Error {
|
||||
}
|
||||
}
|
||||
|
||||
if r.Proxmox == nil && r.Container == nil && r.ProxyURL != nil {
|
||||
proxmoxProviders := config.WorkingState.Load().Value().Providers.Proxmox
|
||||
if len(proxmoxProviders) > 0 {
|
||||
// it's fine if ip is nil
|
||||
hostname := r.ProxyURL.Hostname()
|
||||
ip := net.ParseIP(hostname)
|
||||
for _, p := range config.WorkingState.Load().Value().Providers.Proxmox {
|
||||
// First check if hostname, IP, or alias matches a node (node-level route)
|
||||
if nodeName := p.Client().ReverseLookupNode(hostname, ip, r.Alias); nodeName != "" {
|
||||
r.Proxmox = &proxmox.NodeConfig{
|
||||
Node: nodeName,
|
||||
VMID: 0, // node-level route, no specific VM
|
||||
VMName: "",
|
||||
}
|
||||
log.Info().
|
||||
Str("node", nodeName).
|
||||
Msgf("found proxmox node for route %q", r.Alias)
|
||||
break
|
||||
}
|
||||
|
||||
// Then check if hostname, IP, or alias matches a VM resource
|
||||
resource, _ := p.Client().ReverseLookupResource(ip, hostname, r.Alias)
|
||||
if resource != nil {
|
||||
r.Proxmox = &proxmox.NodeConfig{
|
||||
Node: resource.Node,
|
||||
VMID: int(resource.VMID),
|
||||
VMName: resource.Name,
|
||||
}
|
||||
log.Info().
|
||||
Str("node", resource.Node).
|
||||
Int("vmid", int(resource.VMID)).
|
||||
Str("vmname", resource.Name).
|
||||
Msgf("found proxmox resource for route %q", r.Alias)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !r.UseHealthCheck() && (r.UseLoadBalance() || r.UseIdleWatcher()) {
|
||||
errs.Adds("cannot disable healthcheck when loadbalancer or idle watcher is enabled")
|
||||
}
|
||||
@@ -556,11 +572,11 @@ func (r *Route) References() []string {
|
||||
}
|
||||
|
||||
if r.Proxmox != nil {
|
||||
if r.Proxmox.Service != "" && r.Proxmox.Service != aliasRef {
|
||||
if len(r.Proxmox.Services) > 0 && r.Proxmox.Services[0] != aliasRef {
|
||||
if r.Proxmox.VMName != aliasRef {
|
||||
return []string{r.Proxmox.VMName, aliasRef, r.Proxmox.Service}
|
||||
return []string{r.Proxmox.VMName, aliasRef, r.Proxmox.Services[0]}
|
||||
}
|
||||
return []string{r.Proxmox.Service, aliasRef}
|
||||
return []string{r.Proxmox.Services[0], aliasRef}
|
||||
} else {
|
||||
if r.Proxmox.VMName != aliasRef {
|
||||
return []string{r.Proxmox.VMName, aliasRef}
|
||||
|
||||
Reference in New Issue
Block a user