Add LXC config metadata for guest drawers (#596)
This commit is contained in:
parent
f4ead79c82
commit
be85459db2
7 changed files with 218 additions and 0 deletions
|
|
@ -411,6 +411,9 @@ func (noopPVEClient) GetVMStatus(ctx context.Context, node string, vmid int) (*p
|
|||
func (noopPVEClient) GetContainerStatus(ctx context.Context, node string, vmid int) (*proxmox.Container, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (noopPVEClient) GetContainerConfig(ctx context.Context, node string, vmid int) (map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (noopPVEClient) GetClusterResources(ctx context.Context, resourceType string) ([]proxmox.ClusterResource, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ type PVEClientInterface interface {
|
|||
GetContainerSnapshots(ctx context.Context, node string, vmid int) ([]proxmox.Snapshot, error)
|
||||
GetVMStatus(ctx context.Context, node string, vmid int) (*proxmox.VMStatus, error)
|
||||
GetContainerStatus(ctx context.Context, node string, vmid int) (*proxmox.Container, error)
|
||||
GetContainerConfig(ctx context.Context, node string, vmid int) (map[string]interface{}, error)
|
||||
GetClusterResources(ctx context.Context, resourceType string) ([]proxmox.ClusterResource, error)
|
||||
IsClusterMember(ctx context.Context) (bool, error)
|
||||
GetVMFSInfo(ctx context.Context, node string, vmid int) ([]proxmox.VMFileSystem, error)
|
||||
|
|
@ -1864,6 +1865,7 @@ func (m *Monitor) enrichContainerMetadata(ctx context.Context, client PVEClientI
|
|||
return
|
||||
}
|
||||
|
||||
rootDeviceHint := ""
|
||||
addressSet := make(map[string]struct{})
|
||||
addressOrder := make([]string, 0, 4)
|
||||
|
||||
|
|
@ -1931,6 +1933,31 @@ func (m *Monitor) enrichContainerMetadata(ctx context.Context, client PVEClientI
|
|||
}
|
||||
}
|
||||
|
||||
configCtx, cancelConfig := context.WithTimeout(ctx, 5*time.Second)
|
||||
configData, configErr := client.GetContainerConfig(configCtx, nodeName, container.VMID)
|
||||
cancelConfig()
|
||||
if configErr != nil {
|
||||
log.Debug().
|
||||
Err(configErr).
|
||||
Str("instance", instanceName).
|
||||
Str("node", nodeName).
|
||||
Str("container", container.Name).
|
||||
Int("vmid", container.VMID).
|
||||
Msg("Container config metadata unavailable")
|
||||
} else if len(configData) > 0 {
|
||||
if hint := extractContainerRootDeviceFromConfig(configData); hint != "" {
|
||||
rootDeviceHint = hint
|
||||
}
|
||||
for _, detail := range parseContainerConfigNetworks(configData) {
|
||||
if len(detail.Addresses) > 0 {
|
||||
for _, addr := range detail.Addresses {
|
||||
addAddress(addr)
|
||||
}
|
||||
}
|
||||
mergeContainerNetworkInterface(&networkIfaces, detail)
|
||||
}
|
||||
}
|
||||
|
||||
if len(networkIfaces) > 1 {
|
||||
sort.SliceStable(networkIfaces, func(i, j int) bool {
|
||||
left := strings.TrimSpace(networkIfaces[i].Name)
|
||||
|
|
@ -1956,6 +1983,14 @@ func (m *Monitor) enrichContainerMetadata(ctx context.Context, client PVEClientI
|
|||
}
|
||||
|
||||
ensureContainerRootDiskEntry(container)
|
||||
|
||||
if rootDeviceHint != "" && len(container.Disks) > 0 {
|
||||
for i := range container.Disks {
|
||||
if container.Disks[i].Mountpoint == "/" && container.Disks[i].Device == "" {
|
||||
container.Disks[i].Device = rootDeviceHint
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ensureContainerRootDiskEntry(container *models.Container) {
|
||||
|
|
@ -2165,6 +2200,137 @@ func dedupeStringsPreserveOrder(values []string) []string {
|
|||
return result
|
||||
}
|
||||
|
||||
type containerNetworkDetails struct {
|
||||
Name string
|
||||
MAC string
|
||||
Addresses []string
|
||||
}
|
||||
|
||||
func parseContainerConfigNetworks(config map[string]interface{}) []containerNetworkDetails {
|
||||
if len(config) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(config))
|
||||
for key := range config {
|
||||
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(key)), "net") {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
results := make([]containerNetworkDetails, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
raw := fmt.Sprint(config[key])
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
detail := containerNetworkDetails{}
|
||||
parts := strings.Split(raw, ",")
|
||||
for _, part := range parts {
|
||||
kv := strings.SplitN(strings.TrimSpace(part), "=", 2)
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
k := strings.ToLower(strings.TrimSpace(kv[0]))
|
||||
value := strings.TrimSpace(kv[1])
|
||||
switch k {
|
||||
case "name":
|
||||
detail.Name = value
|
||||
case "hwaddr", "mac", "macaddr":
|
||||
detail.MAC = strings.ToUpper(value)
|
||||
case "ip", "ip6", "ips", "ip6addr", "ip6prefix":
|
||||
detail.Addresses = append(detail.Addresses, sanitizeGuestAddressStrings(value)...)
|
||||
}
|
||||
}
|
||||
|
||||
if detail.Name == "" {
|
||||
detail.Name = strings.TrimSpace(key)
|
||||
}
|
||||
if len(detail.Addresses) > 0 {
|
||||
detail.Addresses = dedupeStringsPreserveOrder(detail.Addresses)
|
||||
}
|
||||
|
||||
if detail.Name != "" || detail.MAC != "" || len(detail.Addresses) > 0 {
|
||||
results = append(results, detail)
|
||||
}
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func mergeContainerNetworkInterface(target *[]models.GuestNetworkInterface, detail containerNetworkDetails) {
|
||||
if target == nil {
|
||||
return
|
||||
}
|
||||
if len(detail.Addresses) > 0 {
|
||||
detail.Addresses = dedupeStringsPreserveOrder(detail.Addresses)
|
||||
}
|
||||
|
||||
findMatch := func() int {
|
||||
for i := range *target {
|
||||
if detail.Name != "" && (*target)[i].Name != "" && strings.EqualFold((*target)[i].Name, detail.Name) {
|
||||
return i
|
||||
}
|
||||
if detail.MAC != "" && (*target)[i].MAC != "" && strings.EqualFold((*target)[i].MAC, detail.MAC) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
if idx := findMatch(); idx >= 0 {
|
||||
if detail.Name != "" && (*target)[idx].Name == "" {
|
||||
(*target)[idx].Name = detail.Name
|
||||
}
|
||||
if detail.MAC != "" && (*target)[idx].MAC == "" {
|
||||
(*target)[idx].MAC = detail.MAC
|
||||
}
|
||||
if len(detail.Addresses) > 0 {
|
||||
combined := append((*target)[idx].Addresses, detail.Addresses...)
|
||||
(*target)[idx].Addresses = dedupeStringsPreserveOrder(combined)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
newIface := models.GuestNetworkInterface{
|
||||
Name: detail.Name,
|
||||
MAC: detail.MAC,
|
||||
}
|
||||
if len(detail.Addresses) > 0 {
|
||||
newIface.Addresses = dedupeStringsPreserveOrder(detail.Addresses)
|
||||
}
|
||||
*target = append(*target, newIface)
|
||||
}
|
||||
|
||||
func extractContainerRootDeviceFromConfig(config map[string]interface{}) string {
|
||||
if len(config) == 0 {
|
||||
return ""
|
||||
}
|
||||
raw, ok := config["rootfs"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
value := strings.TrimSpace(fmt.Sprint(raw))
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
parts := strings.Split(value, ",")
|
||||
device := strings.TrimSpace(parts[0])
|
||||
return device
|
||||
}
|
||||
|
||||
// GetConnectionStatuses returns the current connection status for all nodes
|
||||
func (m *Monitor) GetConnectionStatuses() map[string]bool {
|
||||
if mock.IsMockEnabled() {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,10 @@ func (s *stubPVEClient) GetContainerStatus(ctx context.Context, node string, vmi
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *stubPVEClient) GetContainerConfig(ctx context.Context, node string, vmid int) (map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *stubPVEClient) GetClusterResources(ctx context.Context, resourceType string) ([]proxmox.ClusterResource, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ func (f fakeSnapshotClient) GetVMStatus(ctx context.Context, node string, vmid i
|
|||
func (f fakeSnapshotClient) GetContainerStatus(ctx context.Context, node string, vmid int) (*proxmox.Container, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f fakeSnapshotClient) GetContainerConfig(ctx context.Context, node string, vmid int) (map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f fakeSnapshotClient) GetClusterResources(ctx context.Context, resourceType string) ([]proxmox.ClusterResource, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,10 @@ func (f *fakeStorageClient) GetContainerStatus(ctx context.Context, node string,
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeStorageClient) GetContainerConfig(ctx context.Context, node string, vmid int) (map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeStorageClient) GetClusterResources(ctx context.Context, resourceType string) ([]proxmox.ClusterResource, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -785,6 +785,29 @@ type ContainerDiskUsage struct {
|
|||
Used uint64 `json:"used,omitempty"`
|
||||
}
|
||||
|
||||
// GetContainerConfig returns the configuration of a specific container
|
||||
func (c *Client) GetContainerConfig(ctx context.Context, node string, vmid int) (map[string]interface{}, error) {
|
||||
resp, err := c.get(ctx, fmt.Sprintf("/nodes/%s/lxc/%d/config", node, vmid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result struct {
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Data == nil {
|
||||
result.Data = make(map[string]interface{})
|
||||
}
|
||||
|
||||
return result.Data, nil
|
||||
}
|
||||
|
||||
// Storage represents a Proxmox VE storage
|
||||
type Storage struct {
|
||||
Storage string `json:"storage"`
|
||||
|
|
|
|||
|
|
@ -1003,6 +1003,20 @@ func (cc *ClusterClient) GetContainerStatus(ctx context.Context, node string, vm
|
|||
return result, err
|
||||
}
|
||||
|
||||
// GetContainerConfig returns the configuration of a specific container
|
||||
func (cc *ClusterClient) GetContainerConfig(ctx context.Context, node string, vmid int) (map[string]interface{}, error) {
|
||||
var result map[string]interface{}
|
||||
err := cc.executeWithFailover(ctx, func(client *Client) error {
|
||||
config, err := client.GetContainerConfig(ctx, node, vmid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result = config
|
||||
return nil
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
// IsClusterMember checks if this node is part of a cluster
|
||||
func (cc *ClusterClient) IsClusterMember(ctx context.Context) (bool, error) {
|
||||
var result bool
|
||||
|
|
|
|||
Loading…
Reference in a new issue