Skip to content

Commit d8e521f

Browse files
committed
Add VM details
Description, Hostname, IP Addresses, guest tools, OS info and disks
1 parent 72c1bff commit d8e521f

File tree

10 files changed

+2478
-184
lines changed

10 files changed

+2478
-184
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,28 @@
11
class ManageIQ::Providers::Proxmox::Inventory::Collector < ManageIQ::Providers::Inventory::Collector
2+
private
3+
4+
def collect_vm_details(vm)
5+
base = "/nodes/#{vm["node"]}/qemu/#{vm["vmid"]}"
6+
config = connection.request(:get, "#{base}/config")
7+
status = connection.request(:get, "#{base}/status/current")
8+
9+
details = {
10+
"config" => config,
11+
"status" => status,
12+
"name" => config&.dig("name"),
13+
"template" => config&.dig("template")
14+
}
15+
16+
if status&.dig("status") == "running" && config&.dig("agent")&.to_s&.start_with?("1")
17+
begin
18+
details["agent_info"] = connection.request(:get, "#{base}/agent/get-osinfo")&.dig("result")
19+
details["networks"] = connection.request(:get, "#{base}/agent/network-get-interfaces")&.dig("result")
20+
details["hostname"] = connection.request(:get, "#{base}/agent/get-host-name")&.dig("result", "host-name")
21+
rescue RuntimeError => err
22+
$proxmox_log.warn("Proxmox agent not responding for VM #{vm["vmid"]}: #{err.message}")
23+
end
24+
end
25+
26+
vm.merge(details)
27+
end
228
end

app/models/manageiq/providers/proxmox/inventory/collector/infra_manager.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def nodes
88
end
99

1010
def vms
11-
@vms ||= cluster_resources_by_type["qemu"]
11+
@vms ||= cluster_resources_by_type["qemu"]&.map { |vm| collect_vm_details(vm) } || []
1212
end
1313

1414
def storages

app/models/manageiq/providers/proxmox/inventory/collector/target_collection.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@ def vms
2020
return [] if references(:vms).blank?
2121
return @vms if @vms
2222

23-
@vms = references(:vms).filter_map do |vm_ref|
23+
@vms ||= references(:vms).filter_map do |vm_ref|
2424
node, vmid = vm_ref.split("/")
2525
next unless node && vmid
2626

27-
status_data = connection.request(:get, "/nodes/#{node}/qemu/#{vmid}/status/current")
28-
status_data&.merge("node" => node, "id" => "qemu/#{vmid}")
29-
end.compact
27+
collect_vm_details("node" => node, "vmid" => vmid.to_i, "id" => "qemu/#{vmid}")
28+
end
3029
end
3130

3231
def storages

app/models/manageiq/providers/proxmox/inventory/parser/infra_manager.rb

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,142 @@ def vms
7171

7272
collector.vms.each do |vm|
7373
ems_ref = vm["id"].gsub("qemu/", "")
74-
host = persister.hosts.lazy_find(vm["node"]) if vm["node"]
7574
template = vm["template"] == 1
76-
raw_power_state = template ? "never" : vm["status"]
75+
config = vm["config"] || {}
76+
status = vm["status"] || {}
7777

7878
vm_obj = persister.vms_and_templates.build(
7979
:type => "#{persister.manager.class}::#{template ? "Template" : "Vm"}",
8080
:ems_ref => ems_ref,
8181
:uid_ems => ems_ref,
8282
:name => vm["name"],
8383
:template => template,
84-
:raw_power_state => raw_power_state,
85-
:host => host,
84+
:raw_power_state => template ? "never" : (status["qmpstatus"] || vm["status"]),
85+
:host => persister.hosts.lazy_find(vm["node"]),
8686
:ems_cluster => cluster,
8787
:location => "#{vm["node"]}/#{vm["vmid"]}",
88-
:vendor => "proxmox"
88+
:vendor => "proxmox",
89+
:description => config["description"],
90+
:tools_status => parse_tools_status(config, vm["agent_info"])
8991
)
92+
93+
hardware = parse_hardware(vm_obj, config, status)
94+
parse_disks(hardware, config)
95+
parse_networks(hardware, vm)
96+
parse_operating_system(vm_obj, vm["agent_info"], config)
97+
end
98+
end
99+
100+
def parse_tools_status(config, agent_info)
101+
return "toolsNotInstalled" unless config["agent"].to_s.start_with?("1")
102+
103+
agent_info.present? ? "toolsOk" : "toolsNotRunning"
104+
end
105+
106+
def parse_hardware(vm_obj, config, status)
107+
persister.hardwares.build(
108+
:vm_or_template => vm_obj,
109+
:cpu_total_cores => status["cpus"] || (config["cores"].to_i * config.fetch("sockets", 1).to_i),
110+
:memory_mb => (status["maxmem"] || (config["memory"].to_i * 1024 * 1024)) / 1024 / 1024,
111+
:disk_capacity => status["maxdisk"]
112+
)
113+
end
114+
115+
def parse_disks(hardware, config)
116+
disk_keys = config.keys.grep(/^(scsi|ide|sata|virtio|nvme)\d+$/)
117+
118+
disk_keys.each do |disk_id|
119+
disk_str = config[disk_id]
120+
next if disk_str.include?("media=cdrom")
121+
122+
size_bytes = parse_disk_size(disk_str)
123+
storage_name = disk_str.split(":").first
124+
125+
persister.disks.build(
126+
:hardware => hardware,
127+
:device_name => disk_id,
128+
:device_type => "disk",
129+
:controller_type => disk_id.gsub(/\d+$/, ""),
130+
:size => size_bytes,
131+
:location => disk_id,
132+
:filename => disk_str.split(",").first,
133+
:storage => persister.storages.lazy_find(storage_name)
134+
)
135+
end
136+
end
137+
138+
def parse_disk_size(disk_str)
139+
return nil unless disk_str
140+
141+
size_match = disk_str.match(/size=(\d+)([TGMK]?)/)
142+
return nil unless size_match
143+
144+
size = size_match[1].to_i
145+
unit = size_match[2]
146+
147+
case unit
148+
when "T" then size * 1024 * 1024 * 1024 * 1024
149+
when "G" then size * 1024 * 1024 * 1024
150+
when "M" then size * 1024 * 1024
151+
when "K" then size * 1024
152+
else size
153+
end
154+
end
155+
156+
def parse_networks(hardware, vm)
157+
agent_networks = vm["networks"]
158+
return unless agent_networks
159+
160+
agent_networks.each do |iface|
161+
next if iface["name"] == "lo"
162+
163+
mac = iface["hardware-address"]&.downcase
164+
ipv4 = iface["ip-addresses"]&.find { |ip| ip["ip-address-type"] == "ipv4" }
165+
ipv6 = iface["ip-addresses"]&.find { |ip| ip["ip-address-type"] == "ipv6" }
166+
167+
network = persister.networks.build(
168+
:hardware => hardware,
169+
:description => iface["name"],
170+
:hostname => vm["hostname"],
171+
:ipaddress => ipv4&.dig("ip-address"),
172+
:ipv6address => ipv6&.dig("ip-address")
173+
)
174+
175+
persister.guest_devices.build(
176+
:hardware => hardware,
177+
:uid_ems => "#{vm["vmid"]}-#{iface["name"]}",
178+
:device_name => iface["name"],
179+
:device_type => "ethernet",
180+
:controller_type => "ethernet",
181+
:address => mac,
182+
:network => network
183+
)
184+
end
185+
end
186+
187+
def parse_mac(net_config)
188+
net_config&.match(/([0-9a-fA-F:]{17})/)&.[](1)&.downcase
189+
end
190+
191+
def parse_operating_system(vm_obj, agent_info, config)
192+
product_name = if agent_info.present?
193+
agent_info["pretty-name"] || agent_info["name"]
194+
else
195+
map_ostype(config["ostype"])
196+
end
197+
198+
persister.operating_systems.build(
199+
:vm_or_template => vm_obj,
200+
:product_name => product_name || "Unknown"
201+
)
202+
end
203+
204+
def map_ostype(ostype)
205+
case ostype.to_s
206+
when /^w/ then "Windows"
207+
when "l26" then "Linux"
208+
when "l24" then "Linux (2.4 kernel)"
209+
else ostype
90210
end
91211
end
92212
end

app/models/manageiq/providers/proxmox/inventory/persister/infra_manager.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ def initialize_inventory_collections
55
add_collection(infra, :host_hardwares)
66
add_collection(infra, :storages)
77
add_collection(infra, :host_storages)
8+
add_collection(infra, :disks, :parent_inventory_collections => %i[vms_and_templates])
89
add_collection(infra, :guest_devices, :parent_inventory_collections => %i[vms_and_templates])
910
add_collection(infra, :hardwares, :parent_inventory_collections => %i[vms_and_templates])
11+
add_collection(infra, :networks, :parent_inventory_collections => %i[vms_and_templates])
1012
add_collection(infra, :operating_systems, :parent_inventory_collections => %i[vms_and_templates])
1113
add_collection(infra, :vms_and_templates, {}, {:without_sti => true}) do |builder|
1214
builder.vm_template_shared

spec/models/manageiq/providers/proxmox/infra_manager/refresher_spec.rb

Lines changed: 94 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
before { with_vcr { described_class.refresh([ems]) } }
3434

3535
context "with a Host target" do
36-
let(:target) { ems.hosts.find_by(:ems_ref => "pve") }
36+
let(:target) { ems.hosts.find_by(:ems_ref => "vmpvetest") }
3737
before { with_vcr("targeted-refresh/host") { described_class.refresh(targets) } }
3838

3939
it "doesn't impact unrelated inventory" do
@@ -45,7 +45,7 @@
4545
end
4646

4747
it "updates the host's power state" do
48-
expect(target.reload.power_state).to eq("off")
48+
expect(target.reload.power_state).to eq("on")
4949
end
5050
end
5151

@@ -69,74 +69,140 @@
6969
end
7070

7171
def assert_counts
72-
expect(Vm.count).to eq(1)
73-
expect(MiqTemplate.count).to eq(1)
74-
expect(Host.count).to eq(1)
75-
expect(Storage.count).to eq(2)
72+
expect(Vm.count).to eq(6)
73+
expect(MiqTemplate.count).to eq(14)
74+
expect(Host.count).to eq(2)
75+
expect(Storage.count).to eq(4)
7676
expect(EmsCluster.count).to eq(1)
7777
end
7878

7979
def assert_ems_counts
80-
expect(ems.vms.count).to eq(1)
81-
expect(ems.miq_templates.count).to eq(1)
82-
expect(ems.hosts.count).to eq(1)
83-
expect(ems.storages.count).to eq(2)
80+
expect(ems.vms.count).to eq(6)
81+
expect(ems.miq_templates.count).to eq(14)
82+
expect(ems.hosts.count).to eq(2)
83+
expect(ems.storages.count).to eq(4)
8484
expect(ems.ems_clusters.count).to eq(1)
8585
end
8686

8787
def assert_specific_cluster
8888
cluster = ems.ems_clusters.find_by(:ems_ref => "cluster")
8989
expect(cluster).to have_attributes(
90-
:name => "Cluster",
90+
:name => "pvetest",
9191
:uid_ems => "cluster",
9292
:ems_ref => "cluster"
9393
)
9494
end
9595

9696
def assert_specific_host
97-
host = ems.hosts.find_by(:ems_ref => "pve")
97+
host = ems.hosts.find_by(:ems_ref => "vmpvetest")
9898
expect(host).to have_attributes(
99-
:name => "pve",
99+
:name => "vmpvetest",
100100
:vmm_vendor => "proxmox",
101101
:vmm_version => nil,
102102
:vmm_product => "Proxmox VE",
103103
:vmm_buildnumber => nil,
104104
:power_state => "on",
105-
:uid_ems => "pve",
106-
:ems_ref => "pve",
105+
:uid_ems => "vmpvetest",
106+
:ems_ref => "vmpvetest",
107107
:ems_cluster => ems.ems_clusters.find_by(:ems_ref => "cluster")
108108
)
109109
expect(host.hardware).to have_attributes(
110-
:cpu_total_cores => 2,
111-
:memory_mb => 1_967
110+
:cpu_total_cores => 8,
111+
:memory_mb => 32_096
112112
)
113113
end
114114

115115
def assert_specific_vm
116116
vm = ems.vms.find_by(:ems_ref => "100")
117117
expect(vm).to have_attributes(
118118
:vendor => "proxmox",
119-
:name => "vm-test",
120-
:location => "pve/100",
121-
:host => ems.hosts.find_by(:ems_ref => "pve"),
119+
:name => "vmpvevm1",
120+
:location => "vmpvetest/100",
121+
:host => ems.hosts.find_by(:ems_ref => "vmpvetest"),
122122
:ems_cluster => ems.ems_clusters.find_by(:ems_ref => "cluster"),
123123
:uid_ems => "100",
124124
:ems_ref => "100",
125-
:power_state => "off",
126-
:raw_power_state => "stopped"
125+
:power_state => "on",
126+
:raw_power_state => "running",
127+
:tools_status => "toolsOk"
128+
)
129+
130+
assert_specific_vm_hardware(vm)
131+
assert_specific_vm_disks(vm)
132+
assert_specific_vm_networks(vm)
133+
assert_specific_vm_os(vm)
134+
end
135+
136+
def assert_specific_vm_hardware(vm)
137+
expect(vm.hardware).to have_attributes(
138+
:cpu_total_cores => 4,
139+
:memory_mb => 2048,
140+
:disk_capacity => 10_737_418_240
141+
)
142+
end
143+
144+
def assert_specific_vm_disks(vm)
145+
expect(vm.hardware.disks.count).to be >= 3
146+
147+
scsi0 = vm.hardware.disks.find_by(:device_name => "scsi0")
148+
expect(scsi0).to have_attributes(
149+
:device_type => "disk",
150+
:controller_type => "scsi",
151+
:size => 10_737_418_240,
152+
:location => "scsi0",
153+
:filename => "local-lvm:vm-100-disk-1"
154+
)
155+
156+
scsi1 = vm.hardware.disks.find_by(:device_name => "scsi1")
157+
expect(scsi1).to have_attributes(
158+
:device_type => "disk",
159+
:controller_type => "scsi",
160+
:size => 2_147_483_648,
161+
:location => "scsi1"
162+
)
163+
164+
scsi2 = vm.hardware.disks.find_by(:device_name => "scsi2")
165+
expect(scsi2).to have_attributes(
166+
:device_type => "disk",
167+
:controller_type => "scsi",
168+
:size => 3_221_225_472,
169+
:location => "scsi2"
170+
)
171+
end
172+
173+
def assert_specific_vm_networks(vm)
174+
expect(vm.hardware.networks.count).to be >= 1
175+
176+
ens18 = vm.hardware.networks.find_by(:description => "ens18")
177+
expect(ens18).to have_attributes(
178+
:hostname => "vmpvevm1",
179+
:ipaddress => "192.0.2.253"
180+
)
181+
182+
guest_device = vm.hardware.guest_devices.find_by(:device_name => "ens18")
183+
expect(guest_device).to have_attributes(
184+
:device_type => "ethernet",
185+
:controller_type => "ethernet",
186+
:address => "bc:24:11:e2:9f:14"
187+
)
188+
end
189+
190+
def assert_specific_vm_os(vm)
191+
expect(vm.operating_system).to have_attributes(
192+
:product_name => "Debian GNU/Linux 13 (trixie)"
127193
)
128194
end
129195

130196
def assert_specific_template
131-
template = ems.miq_templates.find_by(:ems_ref => "101")
197+
template = ems.miq_templates.find_by(:ems_ref => "900")
132198
expect(template).to have_attributes(
133199
:vendor => "proxmox",
134-
:name => "vm-template",
135-
:location => "pve/101",
136-
:host => ems.hosts.find_by(:ems_ref => "pve"),
200+
:name => "debian-13-2025-11-17T19-50-19Z-00000000",
201+
:location => "vmpvetest/900",
202+
:host => ems.hosts.find_by(:ems_ref => "vmpvetest"),
137203
:ems_cluster => ems.ems_clusters.find_by(:ems_ref => "cluster"),
138-
:uid_ems => "101",
139-
:ems_ref => "101",
204+
:uid_ems => "900",
205+
:ems_ref => "900",
140206
:power_state => "never",
141207
:raw_power_state => "never"
142208
)

0 commit comments

Comments
 (0)