feat(proxmox): enhance VM resource tracking with auto-discovery and cached IPs

- Add VMResource wrapper type with cached IP addresses for efficient lookups
- Implement concurrent IP fetching during resource updates (limited concurrency)
- Add ReverseLookupResource for discovering VMs by IP, hostname, or alias
- Prioritize interfaces API over config for IP retrieval (offline container fallback)
- Enable routes to auto-discover Proxmox resources when no explicit config provided
- Fix configuration type from value to pointer slice for correct proxmox client retrievel
- Ensure Proxmox providers are initialized before route validation
This commit is contained in:
yusing
2026-01-25 02:25:07 +08:00
parent 64e380cc40
commit a46573cab3
9 changed files with 145 additions and 21 deletions

View File

@@ -132,6 +132,10 @@ func (r Routes) Contains(alias string) bool {
}
func (r *Route) Validate() gperr.Error {
// wait for alias to be set
if r.Alias == "" {
return nil
}
// pcs := make([]uintptr, 1)
// runtime.Callers(2, pcs)
// f := runtime.FuncForPC(pcs[0])
@@ -182,22 +186,35 @@ func (r *Route) validate() gperr.Error {
r.Idlewatcher.Proxmox = r.Proxmox
}
if r.Idlewatcher != nil && r.Idlewatcher.Proxmox != nil {
node := r.Idlewatcher.Proxmox.Node
vmid := r.Idlewatcher.Proxmox.VMID
if node == "" {
if r.Proxmox == nil && r.Idlewatcher != nil && r.Idlewatcher.Proxmox != nil {
r.Proxmox = r.Idlewatcher.Proxmox
}
if r.Proxmox != nil {
nodeName := r.Proxmox.Node
vmid := r.Proxmox.VMID
if nodeName == "" {
return gperr.Errorf("node (proxmox node name) is required")
}
if vmid <= 0 {
return gperr.Errorf("vmid (lxc id) is required")
}
node, ok := proxmox.Nodes.Get(nodeName)
if !ok {
return gperr.Errorf("proxmox node %s not found in pool", node)
}
res, err := node.Client().GetResource("lxc", vmid)
if err != nil {
return gperr.Wrap(err) // ErrResourceNotFound
}
r.Proxmox.VMName = res.Name
if r.Host == DefaultHost {
containerName := r.Idlewatcher.ContainerName()
// get ip addresses of the vmid
node, ok := proxmox.Nodes.Get(node)
if !ok {
return gperr.Errorf("proxmox node %s not found in pool", node)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
@@ -317,6 +334,33 @@ func (r *Route) validate() gperr.Error {
}
}
if r.Proxmox == nil && r.Container == 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 {
resource, _ := p.Client().ReverseLookupResource(ip, hostname, r.Alias)
// reverse lookup resource by ip address, hostname or alias
if resource != nil {
r.Proxmox = &proxmox.NodeConfig{
Node: resource.Node,
VMID: int(resource.VMID),
VMName: resource.Name,
Service: r.Alias,
}
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")
}
@@ -496,6 +540,13 @@ func (r *Route) References() []string {
}
return []string{r.Container.Image.Name, aliasRef, r.Container.Image.Author}
}
if r.Proxmox != nil {
if r.Proxmox.VMName != r.Alias {
return []string{r.Proxmox.VMName, aliasRef, r.Proxmox.Service}
}
return []string{r.Proxmox.Service, aliasRef}
}
return []string{aliasRef}
}