Class: Pvectl::Repositories::Vm
- Defined in:
- lib/pvectl/repositories/vm.rb,
sig/pvectl/repositories/vm.rbs
Overview
Repository for QEMU virtual machines.
Uses the /cluster/resources API endpoint to list VMs across the cluster.
Filters to only include QEMU VMs (excludes LXC containers).
Instance Attribute Summary
Attributes inherited from Base
Instance Method Summary collapse
-
#build_describe_model(basic_data, describe_data) ⇒ Models::Vm
Builds VM model with describe-specific attributes.
-
#build_model(data) ⇒ Models::Vm
Builds Vm model from API response data.
-
#clone(vmid, node, new_vmid, options = {}) ⇒ String
Clones a VM to create a new VM.
-
#cloudinit_dump(node, vmid, type) ⇒ String
Dumps the generated cloud-init configuration for a VM.
-
#cloudinit_pending(node, vmid) ⇒ Array<Hash{Symbol => untyped}>
Fetches pending cloud-init configuration changes for a VM.
-
#cloudinit_regenerate(node, vmid) ⇒ nil
Regenerates the cloud-init configuration ISO for a VM.
-
#convert_to_template(vmid, node, disk: nil) ⇒ void
Converts a VM to a template.
-
#create(node, vmid, params = {}) ⇒ String
Creates a new VM on the specified node.
-
#delete(vmid, node, destroy_disks: true, purge: false, force: false) ⇒ String
Deletes a VM from the cluster.
-
#describe(vmid) ⇒ Models::Vm?
Describes a VM with comprehensive details from multiple API endpoints.
-
#feature_available?(vmid, node, feature, snapname: nil) ⇒ Hash
Checks whether a feature (clone, snapshot, copy) is available for a VM.
-
#fetch_agent_ips(node, vmid) ⇒ Array<Hash>?
Fetches IP addresses from QEMU guest agent.
-
#fetch_config(node, vmid) ⇒ Hash
Fetches VM configuration.
-
#fetch_firewall(node, vmid) ⇒ Hash
Fetches firewall configuration (options, rules, aliases, IP sets).
-
#fetch_pending(node, vmid) ⇒ Array<Hash>
Fetches pending configuration changes.
-
#fetch_snapshots(node, vmid) ⇒ Array<Hash>
Fetches VM snapshots.
-
#fetch_status(node, vmid) ⇒ Hash
Fetches VM runtime status.
-
#fetch_tasks(node, vmid, limit: 10) ⇒ Array<Models::TaskEntry>
Fetches recent task history for the VM.
-
#find_vm_basic_data(vmid) ⇒ Hash?
Finds VM basic data from cluster resources.
-
#get(vmid) ⇒ Models::Vm?
Gets a single VM by VMID.
-
#list(node: nil) ⇒ Array<Models::Vm>
Lists all VMs in the cluster.
-
#migrate(vmid, node, params = {}) ⇒ String
Migrates a VM to another node.
-
#move_disk(vmid, node, disk, target_storage, format: nil, delete: false, bwlimit: nil) ⇒ String
Moves a VM disk to a different storage on the same node.
-
#next_available_vmid ⇒ Integer
Returns the next available VMID from the Proxmox cluster.
-
#normalize_hash_response(response) ⇒ Hash
Normalizes hash response that may be wrapped in :data key.
-
#normalize_response(response) ⇒ Array<Hash>
Normalizes array response.
-
#post_status(vmid, node, action) ⇒ String
Posts to VM status endpoint.
-
#reset(vmid, node) ⇒ String
Resets a VM (hard reset).
-
#resize(vmid, node, disk:, size:) ⇒ nil
Resizes a VM disk.
-
#restart(vmid, node) ⇒ String
Restarts a VM (reboot).
-
#resume(vmid, node) ⇒ String
Resumes a suspended VM.
-
#sendkey(vmid, node, key) ⇒ nil
Sends a QEMU monitor key event to a running VM.
-
#shutdown(vmid, node) ⇒ String
Shuts down a VM gracefully (ACPI).
-
#start(vmid, node) ⇒ String
Starts a VM.
-
#stop(vmid, node) ⇒ String
Stops a VM immediately (hard stop).
-
#suspend(vmid, node) ⇒ String
Suspends a VM (hibernate).
-
#termproxy(vmid, node) ⇒ Hash
Opens a terminal proxy session for a VM.
-
#unlink_disks(node, vmid, disk_ids, force: false) ⇒ nil
Unlinks (removes) one or more disks from a VM configuration.
-
#update(vmid, node, params = {}) ⇒ nil
Updates an existing VM configuration.
Methods inherited from Base
#extract_data, #initialize, #models_from, #unwrap
Constructor Details
This class inherits a constructor from Pvectl::Repositories::Base
Instance Method Details
#build_describe_model(basic_data, describe_data) ⇒ Models::Vm
Builds VM model with describe-specific attributes.
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 |
# File 'lib/pvectl/repositories/vm.rb', line 592 def build_describe_model(basic_data, describe_data) Models::Vm.new( vmid: basic_data[:vmid], name: basic_data[:name], status: basic_data[:status], node: basic_data[:node], cpu: basic_data[:cpu], maxcpu: basic_data[:maxcpu], mem: basic_data[:mem], maxmem: basic_data[:maxmem], disk: basic_data[:disk], maxdisk: basic_data[:maxdisk], uptime: basic_data[:uptime], template: basic_data[:template], tags: basic_data[:tags], hastate: basic_data[:hastate], netin: basic_data[:netin], netout: basic_data[:netout], type: basic_data[:type], describe_data: describe_data ) end |
#build_model(data) ⇒ Models::Vm
Builds Vm model from API response data.
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 |
# File 'lib/pvectl/repositories/vm.rb', line 439 def build_model(data) Models::Vm.new( vmid: data[:vmid], name: data[:name], status: data[:status], node: data[:node], cpu: data[:cpu], maxcpu: data[:maxcpu], mem: data[:mem], maxmem: data[:maxmem], disk: data[:disk], maxdisk: data[:maxdisk], uptime: data[:uptime], template: data[:template], tags: data[:tags], hastate: data[:hastate], netin: data[:netin], netout: data[:netout], type: data[:type] ) end |
#clone(vmid, node, new_vmid, options = {}) ⇒ String
Clones a VM to create a new VM.
Posts to /nodes/{node}/qemu/{vmid}/clone with the specified parameters.
187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/pvectl/repositories/vm.rb', line 187 def clone(vmid, node, new_vmid, = {}) params = { newid: new_vmid } params[:name] = [:name] if [:name] params[:target] = [:target] if [:target] params[:storage] = [:storage] if [:storage] params[:full] = [:full] ? 1 : 0 if .key?(:full) params[:description] = [:description] if [:description] params[:pool] = [:pool] if [:pool] connection.client["nodes/#{node}/qemu/#{vmid}/clone"].post(params) end |
#cloudinit_dump(node, vmid, type) ⇒ String
Dumps the generated cloud-init configuration for a VM.
GETs from /nodes/{node}/qemu/{vmid}/cloudinit/dump with the
specified type query parameter. Returns the raw YAML/text body.
390 391 392 |
# File 'lib/pvectl/repositories/vm.rb', line 390 def cloudinit_dump(node, vmid, type) connection.client["nodes/#{node}/qemu/#{vmid}/cloudinit/dump"].get(params: { type: type }) end |
#cloudinit_pending(node, vmid) ⇒ Array<Hash{Symbol => untyped}>
Fetches pending cloud-init configuration changes for a VM.
GETs from /nodes/{node}/qemu/{vmid}/cloudinit. Returns an array
of pending entries, each with :key, :value, :pending, and
optional :delete keys.
376 377 378 379 |
# File 'lib/pvectl/repositories/vm.rb', line 376 def cloudinit_pending(node, vmid) response = connection.client["nodes/#{node}/qemu/#{vmid}/cloudinit"].get normalize_response(response) end |
#cloudinit_regenerate(node, vmid) ⇒ nil
Regenerates the cloud-init configuration ISO for a VM.
PUTs to /nodes/{node}/qemu/{vmid}/cloudinit. The Proxmox API
endpoint returns null on success.
363 364 365 |
# File 'lib/pvectl/repositories/vm.rb', line 363 def cloudinit_regenerate(node, vmid) connection.client["nodes/#{node}/qemu/#{vmid}/cloudinit"].put end |
#convert_to_template(vmid, node, disk: nil) ⇒ void
This method returns an undefined value.
Converts a VM to a template.
This is an irreversible operation. The VM will become read-only and can only be used as a source for cloning.
208 209 210 211 212 |
# File 'lib/pvectl/repositories/vm.rb', line 208 def convert_to_template(vmid, node, disk: nil) params = {} params[:disk] = disk if disk connection.client["nodes/#{node}/qemu/#{vmid}/template"].post(params) end |
#create(node, vmid, params = {}) ⇒ String
Creates a new VM on the specified node.
Posts to /nodes/{node}/qemu with the VM configuration parameters.
The vmid is merged into params automatically.
227 228 229 230 |
# File 'lib/pvectl/repositories/vm.rb', line 227 def create(node, vmid, params = {}) api_params = params.merge(vmid: vmid) connection.client["nodes/#{node}/qemu"].post(api_params) end |
#delete(vmid, node, destroy_disks: true, purge: false, force: false) ⇒ String
Deletes a VM from the cluster.
163 164 165 166 167 168 169 170 |
# File 'lib/pvectl/repositories/vm.rb', line 163 def delete(vmid, node, destroy_disks: true, purge: false, force: false) params = {} params["destroy-unreferenced-disks"] = 1 if destroy_disks params[:purge] = 1 if purge params[:skiplock] = 1 if force connection.client["nodes/#{node}/qemu/#{vmid}"].delete(params) end |
#describe(vmid) ⇒ Models::Vm?
Describes a VM with comprehensive details from multiple API endpoints.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/pvectl/repositories/vm.rb', line 55 def describe(vmid) vmid = vmid.to_i # 1. Find VM in cluster to get node basic_data = find_vm_basic_data(vmid) return nil if basic_data.nil? node = basic_data[:node] # 2. Fetch detailed data from node-specific endpoints describe_data = { config: fetch_config(node, vmid), status: fetch_status(node, vmid), snapshots: fetch_snapshots(node, vmid), agent_ips: fetch_agent_ips(node, vmid), pending: fetch_pending(node, vmid), tasks: fetch_tasks(node, vmid), firewall: fetch_firewall(node, vmid) } build_describe_model(basic_data, describe_data) end |
#feature_available?(vmid, node, feature, snapname: nil) ⇒ Hash
Checks whether a feature (clone, snapshot, copy) is available for a VM.
Calls GET /nodes/{node}/qemu/{vmid}/feature with the feature and
optional snapshot name. The Proxmox API returns hasFeature (0/1) and
a nodes array listing cluster nodes that satisfy the feature.
409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/pvectl/repositories/vm.rb', line 409 def feature_available?(vmid, node, feature, snapname: nil) params = { feature: feature } params[:snapname] = snapname unless snapname.nil? response = connection.client["nodes/#{node}/qemu/#{vmid}/feature"].get(params: params) data = normalize_hash_response(response) { available: data[:hasFeature].to_i == 1, nodes: Array(data[:nodes]) } end |
#fetch_agent_ips(node, vmid) ⇒ Array<Hash>?
Fetches IP addresses from QEMU guest agent. Graceful failure - returns nil on any error.
512 513 514 515 516 517 518 |
# File 'lib/pvectl/repositories/vm.rb', line 512 def fetch_agent_ips(node, vmid) resp = connection.client["nodes/#{node}/qemu/#{vmid}/agent/network-get-interfaces"].get data = normalize_hash_response(resp) data[:result] rescue StandardError nil end |
#fetch_config(node, vmid) ⇒ Hash
Fetches VM configuration.
299 300 301 302 303 304 |
# File 'lib/pvectl/repositories/vm.rb', line 299 def fetch_config(node, vmid) resp = connection.client["nodes/#{node}/qemu/#{vmid}/config"].get normalize_hash_response(resp) rescue StandardError {} end |
#fetch_firewall(node, vmid) ⇒ Hash
Fetches firewall configuration (options, rules, aliases, IP sets).
537 538 539 540 541 542 543 544 545 546 |
# File 'lib/pvectl/repositories/vm.rb', line 537 def fetch_firewall(node, vmid) base = "nodes/#{node}/qemu/#{vmid}/firewall" = (normalize_hash_response(connection.client["#{base}/options"].get) rescue {}) rules = (normalize_response(connection.client["#{base}/rules"].get) rescue []) aliases_data = (normalize_response(connection.client["#{base}/aliases"].get) rescue []) ipset = (normalize_response(connection.client["#{base}/ipset"].get) rescue []) { options: , rules: rules, aliases: aliases_data, ipset: ipset } rescue StandardError {} end |
#fetch_pending(node, vmid) ⇒ Array<Hash>
Fetches pending configuration changes.
525 526 527 528 529 530 |
# File 'lib/pvectl/repositories/vm.rb', line 525 def fetch_pending(node, vmid) resp = connection.client["nodes/#{node}/qemu/#{vmid}/pending"].get normalize_response(resp) rescue StandardError [] end |
#fetch_snapshots(node, vmid) ⇒ Array<Hash>
Fetches VM snapshots.
499 500 501 502 503 504 |
# File 'lib/pvectl/repositories/vm.rb', line 499 def fetch_snapshots(node, vmid) resp = connection.client["nodes/#{node}/qemu/#{vmid}/snapshot"].get normalize_response(resp).reject { |s| s[:name] == "current" } rescue StandardError [] end |
#fetch_status(node, vmid) ⇒ Hash
Fetches VM runtime status.
487 488 489 490 491 492 |
# File 'lib/pvectl/repositories/vm.rb', line 487 def fetch_status(node, vmid) resp = connection.client["nodes/#{node}/qemu/#{vmid}/status/current"].get normalize_hash_response(resp) rescue StandardError {} end |
#fetch_tasks(node, vmid, limit: 10) ⇒ Array<Models::TaskEntry>
Fetches recent task history for the VM.
554 555 556 557 558 559 |
# File 'lib/pvectl/repositories/vm.rb', line 554 def fetch_tasks(node, vmid, limit: 10) task_list_repo = TaskList.new(connection) task_list_repo.list(node: node, vmid: vmid, limit: limit) rescue StandardError [] end |
#find_vm_basic_data(vmid) ⇒ Hash?
Finds VM basic data from cluster resources.
477 478 479 480 |
# File 'lib/pvectl/repositories/vm.rb', line 477 def find_vm_basic_data(vmid) response = connection.client["cluster/resources"].get(params: { type: "vm" }) response.find { |r| r[:type] == "qemu" && r[:vmid] == vmid } end |
#get(vmid) ⇒ Models::Vm?
Gets a single VM by VMID.
47 48 49 |
# File 'lib/pvectl/repositories/vm.rb', line 47 def get(vmid) list.find { |vm| vm.vmid == vmid.to_i } end |
#list(node: nil) ⇒ Array<Models::Vm>
Lists all VMs in the cluster.
Uses /cluster/resources?type=vm endpoint for efficient cluster-wide
listing. Filters to only include QEMU VMs (type == "qemu").
36 37 38 39 40 41 |
# File 'lib/pvectl/repositories/vm.rb', line 36 def list(node: nil) response = connection.client["cluster/resources"].get(params: { type: "vm" }) vms = response.select { |r| r[:type] == "qemu" } vms = vms.select { |r| r[:node] == node } if node vms.map { |data| build_model(data) } end |
#migrate(vmid, node, params = {}) ⇒ String
Migrates a VM to another node.
313 314 315 316 317 318 319 320 321 322 |
# File 'lib/pvectl/repositories/vm.rb', line 313 def migrate(vmid, node, params = {}) unless node.match?(/\A[a-z][a-z0-9-]*\z/) raise ArgumentError, "Invalid node name: #{node}" end unless vmid.is_a?(Integer) && vmid.positive? raise ArgumentError, "Invalid VMID: #{vmid}" end connection.client["nodes/#{node}/qemu/#{vmid}/migrate"].post(params) end |
#move_disk(vmid, node, disk, target_storage, format: nil, delete: false, bwlimit: nil) ⇒ String
Moves a VM disk to a different storage on the same node.
POSTs to /nodes/{node}/qemu/{vmid}/move_disk with the disk identifier
and target storage. The operation is asynchronous — the returned UPID
can be polled via Repositories::Task to track completion.
346 347 348 349 350 351 352 353 |
# File 'lib/pvectl/repositories/vm.rb', line 346 def move_disk(vmid, node, disk, target_storage, format: nil, delete: false, bwlimit: nil) params = { disk: disk, storage: target_storage } params[:format] = format if format params[:delete] = 1 if delete params[:bwlimit] = bwlimit if bwlimit connection.client["nodes/#{node}/qemu/#{vmid}/move_disk"].post(params) end |
#next_available_vmid ⇒ Integer
Returns the next available VMID from the Proxmox cluster.
Uses the /cluster/nextid API endpoint which performs server-side allocation.
This is more reliable than client-side scanning because it detects stale
config files that don't appear in /cluster/resources.
429 430 431 |
# File 'lib/pvectl/repositories/vm.rb', line 429 def next_available_vmid connection.client["cluster/nextid"].get.to_i end |
#normalize_hash_response(response) ⇒ Hash
Normalizes hash response that may be wrapped in :data key.
565 566 567 568 569 570 571 |
# File 'lib/pvectl/repositories/vm.rb', line 565 def normalize_hash_response(response) if response.is_a?(Hash) && response[:data] response[:data] else response || {} end end |
#normalize_response(response) ⇒ Array<Hash>
Normalizes array response.
577 578 579 580 581 582 583 584 585 |
# File 'lib/pvectl/repositories/vm.rb', line 577 def normalize_response(response) if response.is_a?(Array) response elsif response.is_a?(Hash) && response[:data] response[:data] else response.to_a end end |
#post_status(vmid, node, action) ⇒ String
Posts to VM status endpoint.
469 470 471 |
# File 'lib/pvectl/repositories/vm.rb', line 469 def post_status(vmid, node, action) connection.client["nodes/#{node}/qemu/#{vmid}/status/#{action}"].post end |
#reset(vmid, node) ⇒ String
Resets a VM (hard reset).
123 124 125 |
# File 'lib/pvectl/repositories/vm.rb', line 123 def reset(vmid, node) post_status(vmid, node, "reset") end |
#resize(vmid, node, disk:, size:) ⇒ nil
Resizes a VM disk.
PUTs to /nodes/{node}/qemu/{vmid}/resize with disk and size parameters.
Size can be absolute (e.g., "50G") or relative (e.g., "+10G").
255 256 257 |
# File 'lib/pvectl/repositories/vm.rb', line 255 def resize(vmid, node, disk:, size:) connection.client["nodes/#{node}/qemu/#{vmid}/resize"].put({ disk: disk, size: size }) end |
#restart(vmid, node) ⇒ String
Restarts a VM (reboot).
114 115 116 |
# File 'lib/pvectl/repositories/vm.rb', line 114 def restart(vmid, node) post_status(vmid, node, "reboot") end |
#resume(vmid, node) ⇒ String
Resumes a suspended VM.
141 142 143 |
# File 'lib/pvectl/repositories/vm.rb', line 141 def resume(vmid, node) post_status(vmid, node, "resume") end |
#sendkey(vmid, node, key) ⇒ nil
Sends a QEMU monitor key event to a running VM.
PUTs to /nodes/{node}/qemu/{vmid}/sendkey with the key parameter.
The key uses QEMU qcode format (e.g., "ctrl-alt-delete", "ret", "f1").
This is a synchronous operation — Proxmox returns null on success.
269 270 271 |
# File 'lib/pvectl/repositories/vm.rb', line 269 def sendkey(vmid, node, key) connection.client["nodes/#{node}/qemu/#{vmid}/sendkey"].put({ key: key }) end |
#shutdown(vmid, node) ⇒ String
Shuts down a VM gracefully (ACPI).
105 106 107 |
# File 'lib/pvectl/repositories/vm.rb', line 105 def shutdown(vmid, node) post_status(vmid, node, "shutdown") end |
#start(vmid, node) ⇒ String
Starts a VM.
87 88 89 |
# File 'lib/pvectl/repositories/vm.rb', line 87 def start(vmid, node) post_status(vmid, node, "start") end |
#stop(vmid, node) ⇒ String
Stops a VM immediately (hard stop).
96 97 98 |
# File 'lib/pvectl/repositories/vm.rb', line 96 def stop(vmid, node) post_status(vmid, node, "stop") end |
#suspend(vmid, node) ⇒ String
Suspends a VM (hibernate).
132 133 134 |
# File 'lib/pvectl/repositories/vm.rb', line 132 def suspend(vmid, node) post_status(vmid, node, "suspend") end |
#termproxy(vmid, node) ⇒ Hash
Opens a terminal proxy session for a VM.
150 151 152 153 |
# File 'lib/pvectl/repositories/vm.rb', line 150 def termproxy(vmid, node) response = connection.client["nodes/#{node}/qemu/#{vmid}/termproxy"].post({}) normalize_hash_response(response) end |
#unlink_disks(node, vmid, disk_ids, force: false) ⇒ nil
Unlinks (removes) one or more disks from a VM configuration.
PUTs to /nodes/{node}/qemu/{vmid}/unlink with the comma-separated
list of disk IDs. By default, Proxmox keeps removed volumes as
unused[n] entries in the config; with force: true the underlying
volume is physically removed.
287 288 289 290 291 292 |
# File 'lib/pvectl/repositories/vm.rb', line 287 def unlink_disks(node, vmid, disk_ids, force: false) idlist = Array(disk_ids).flat_map { |id| id.to_s.split(",") }.map(&:strip).reject(&:empty?).join(",") connection.client["nodes/#{node}/qemu/#{vmid}/unlink"].put( { idlist: idlist, force: force ? 1 : 0 } ) end |
#update(vmid, node, params = {}) ⇒ nil
Updates an existing VM configuration.
PUTs to /nodes/{node}/qemu/{vmid}/config with configuration parameters.
This is a synchronous operation — changes are applied immediately.
241 242 243 |
# File 'lib/pvectl/repositories/vm.rb', line 241 def update(vmid, node, params = {}) connection.client["nodes/#{node}/qemu/#{vmid}/config"].put(params) end |