Class: Pvectl::Repositories::Vm

Inherits:
Base
  • Object
show all
Defined in:
lib/pvectl/repositories/vm.rb

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).

Examples:

Listing all VMs

repo = Vm.new(connection)
vms = repo.list
vms.each { |vm| puts "#{vm.vmid}: #{vm.name}" }

Listing VMs on a specific node

vms = repo.list(node: "pve-node1")

Getting a single VM

vm = repo.get(100)
puts vm.name if vm

See Also:

Instance Method Summary collapse

Methods inherited from Base

#initialize

Constructor Details

This class inherits a constructor from Pvectl::Repositories::Base

Instance Method Details

#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.

Parameters:

  • vmid (Integer, String)

    source VM identifier

  • node (String)

    source node name

  • new_vmid (Integer)

    VMID for the new cloned VM

  • options (Hash) (defaults to: {})

    optional clone parameters

Options Hash (options):

  • :name (String)

    name for the new VM

  • :target (String)

    target node for the clone

  • :storage (String)

    target storage for the clone

  • :full (Boolean)

    full clone (true) or linked clone (false)

  • :description (String)

    description for the new VM

  • :pool (String)

    resource pool for the new VM

Returns:

  • (String)

    Task UPID



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, options = {})
  params = { newid: new_vmid }
  params[:name] = options[:name] if options[:name]
  params[:target] = options[:target] if options[:target]
  params[:storage] = options[:storage] if options[:storage]
  params[:full] = options[:full] ? 1 : 0 if options.key?(:full)
  params[:description] = options[:description] if options[:description]
  params[:pool] = options[:pool] if options[: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.

Parameters:

  • node (String)

    node name

  • vmid (Integer, String)

    VM identifier

  • type (String)

    config type — one of “user”, “network”, “meta”

Returns:

  • (String)

    cloud-init configuration as raw text



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.

Parameters:

  • node (String)

    node name

  • vmid (Integer, String)

    VM identifier

Returns:

  • (Array<Hash{Symbol => untyped}>)

    pending entries



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.

Parameters:

  • node (String)

    node name

  • vmid (Integer, String)

    VM identifier

Returns:

  • (nil)


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.

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

  • disk (String, nil) (defaults to: nil)

    specific disk to convert (e.g., “scsi0”)



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.

Examples:

Create a basic VM

repo.create("pve1", 100, { name: "web-server", cores: 4, memory: 4096 })
#=> "UPID:pve1:..."

Parameters:

  • node (String)

    target node name

  • vmid (Integer)

    VM identifier

  • params (Hash) (defaults to: {})

    VM configuration parameters (name, cores, memory, etc.)

Returns:

  • (String)

    Task UPID



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.

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

  • destroy_disks (Boolean) (defaults to: true)

    destroy unreferenced disks (default: true)

  • purge (Boolean) (defaults to: false)

    remove from HA, replication, backups (default: false)

  • force (Boolean) (defaults to: false)

    skip lock (default: false)

Returns:

  • (String)

    Task UPID



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.

Parameters:

  • vmid (Integer, String)

    VM identifier

Returns:

  • (Models::Vm, nil)

    VM model with full details, or nil if not found



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.

Examples:

Check whether VM 100 can be cloned

repo.feature_available?(100, "pve1", "clone")
#=> { available: true, nodes: ["pve1", "pve2"] }

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    node currently hosting the VM

  • feature (String)

    feature name (one of: clone, snapshot, copy)

  • snapname (String, nil) (defaults to: nil)

    snapshot name (required for some checks)

Returns:

  • (Hash)

    result with :available (Boolean) and :nodes (Array<String>)



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_config(node, vmid) ⇒ Hash

Fetches VM configuration.

Parameters:

  • node (String)

    node name

  • vmid (Integer)

    VM identifier

Returns:

  • (Hash)

    config data



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

#get(vmid) ⇒ Models::Vm?

Gets a single VM by VMID.

Parameters:

  • vmid (Integer, String)

    VM identifier

Returns:

  • (Models::Vm, nil)

    VM model or nil if not found



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”).

Parameters:

  • node (String, nil) (defaults to: nil)

    filter by node name

Returns:



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.

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    current node name

  • params (Hash) (defaults to: {})

    migration parameters (:target, :online, :“with-local-disks”, :targetstorage)

Returns:

  • (String)

    Task UPID

Raises:

  • (ArgumentError)

    if node name or vmid format is invalid



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.

Examples:

Move scsi0 to local-lvm storage

repo.move_disk(100, "pve1", "scsi0", "local-lvm")
#=> "UPID:pve1:..."

Move and convert format, delete source

repo.move_disk(100, "pve1", "scsi0", "local-lvm",
               format: "qcow2", delete: true)

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    node name where the VM currently resides

  • disk (String)

    disk identifier (e.g., “scsi0”, “virtio0”)

  • target_storage (String)

    destination storage ID

  • format (String, nil) (defaults to: nil)

    target disk format (“raw”, “qcow2”, “vmdk”)

  • delete (Boolean) (defaults to: false)

    delete the source disk after copy (default: false)

  • bwlimit (Integer, nil) (defaults to: nil)

    I/O bandwidth limit in KiB/s

Returns:

  • (String)

    Task UPID



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_vmidInteger

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.

Returns:

  • (Integer)

    next available VMID



429
430
431
# File 'lib/pvectl/repositories/vm.rb', line 429

def next_available_vmid
  connection.client["cluster/nextid"].get.to_i
end

#reset(vmid, node) ⇒ String

Resets a VM (hard reset).

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

Returns:

  • (String)

    Task UPID



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”).

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    node name

  • disk (String)

    disk name (e.g., “scsi0”, “virtio0”)

  • size (String)

    new size or size increment (e.g., “50G”, “+10G”)

Returns:

  • (nil)


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).

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

Returns:

  • (String)

    Task UPID



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.

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

Returns:

  • (String)

    Task UPID



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.

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    node name

  • key (String)

    QEMU qcode key sequence (e.g., “ctrl-alt-delete”)

Returns:

  • (nil)


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).

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

Returns:

  • (String)

    Task UPID



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.

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

Returns:

  • (String)

    Task UPID



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).

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

Returns:

  • (String)

    Task UPID



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).

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

Returns:

  • (String)

    Task UPID



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.

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    Node name

Returns:

  • (Hash)

    termproxy data with :port, :ticket, :user keys



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

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.

Parameters:

  • node (String)

    node name

  • vmid (Integer, String)

    VM identifier

  • disk_ids (Array<String>, String)

    disk identifiers (e.g., “scsi0” or %w[scsi0 scsi1] or “scsi0,scsi1”)

  • force (Boolean) (defaults to: false)

    physically delete the underlying volume(s) (default: false — keep as unused)

Returns:

  • (nil)

    this is a synchronous operation and returns no UPID



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.

Parameters:

  • vmid (Integer, String)

    VM identifier

  • node (String)

    node name

  • params (Hash) (defaults to: {})

    VM configuration parameters to update

Returns:

  • (nil)


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