class QemuToolkit::VM

Abstracts a virtual machine on a vm host. This class provides all sorts of methods that execute administration actions.

Attributes

boot[RW]

Boot order (if set)

cpus[RW]

The number of cpus to configure, defaults to 2.

devices[R]

Other devices

iscsi_target[RW]

iSCSI target iqn and ip address to connect to

keyboard_layout[RW]

Keyboard layout

name[RW]

VM name

nets[R]

A list of network configuration statements that will be passed through to qemu.

nics[R]

A list of network cards that will be connected to vnics on the host.

ram[RW]

Ram in megabytes

vnc_display[RW]

VNC display port

Public Class Methods

[](name, backend=nil) click to toggle source

Access the definition of a single vm.

# File lib/qemu-toolkit/vm.rb, line 41
def [](name, backend=nil)
  all(backend).find { |vm| vm.name === name }
end
all(backend=nil) click to toggle source

Load all vm descriptions and provide an iterator for them.

# File lib/qemu-toolkit/vm.rb, line 16
def all(backend=nil)
  Enumerator.new do |yielder|
    libdir = Config.etc('lib')
    if ::File.directory? libdir
      $:.unshift libdir
    end
    
    Dir[Config.etc('*.rb')].each do |vm_file|
      # Load all virtual machines from the given file
      dsl = DSL::File.new
      dsl.add_toplevel_target :virtual_machine, lambda { |name| 
        VM.new(backend).tap { |vm| vm.name = name } }
        
      dsl.load_file(vm_file)

      # Yield them all in turn
      dsl.objects.each do |vm|
        yielder << vm
      end
    end
  end
end
new(backend) click to toggle source
# File lib/qemu-toolkit/vm.rb, line 68
def initialize(backend)
  @disks = []
  @drives = []
  @nics = []
  @nets = []
  @cpus = 2
  @ram = 1024
  @backend = backend
  @vnc_display = nil
  @extra_args = []
  # TODO document
  @devices = []
end

Public Instance Methods

add_device(driver, parameters) click to toggle source
# File lib/qemu-toolkit/vm.rb, line 82
def add_device(driver, parameters)
  @devices << [driver, parameters]
end
add_disk(path) click to toggle source
# File lib/qemu-toolkit/vm.rb, line 88
def add_disk(path)
  @disks << path
end
add_drive(parameters) click to toggle source
# File lib/qemu-toolkit/vm.rb, line 85
def add_drive(parameters)
  @drives << parameters
end
add_extra_arg(argument) click to toggle source
# File lib/qemu-toolkit/vm.rb, line 97
def add_extra_arg(argument)
  @extra_args << argument
end
add_net(type, parameters) click to toggle source
# File lib/qemu-toolkit/vm.rb, line 94
def add_net(type, parameters)
  @nets << [type, parameters]
end
add_nic(name, parameters) click to toggle source
# File lib/qemu-toolkit/vm.rb, line 91
def add_nic(name, parameters)
  @nics << [name, parameters]
end
command(opts={}) click to toggle source

Returns the command that is needed to run this virtual machine. Note that this also modifies system configuration and is not just a routine that returns a string.

@return String command to run the machine

# File lib/qemu-toolkit/vm.rb, line 119
def command opts={}
  cmd = []
  cmd << "-name #{name}"
  cmd << "-m #{ram}"
  cmd << "-daemonize"
  cmd << '-nographic'
  cmd << "-cpu qemu64"
  cmd << "-smp #{cpus}"
  cmd << "-no-hpet"
  cmd << "-enable-kvm"
  cmd << "-vga cirrus"
  cmd << "-parallel none"
  cmd << "-usb"
  cmd << '-usbdevice tablet'
  
  if keyboard_layout
    cmd << "-k #{keyboard_layout}"
  end

  # Add disks
  cmd += disk_options
  
  # Was an iso image given to boot from?
  if iso_path=opts[:bootiso]
    cmd << "-cdrom #{iso_path}"
    cmd << "-boot order=cd,once=d"
  else
    cmd << '-boot order=cd'
  end
  
  # Set paths for communication with vm
  cmd << "-pidfile #{pid_path}"
  
  cmd << socket_chardev(:monitor, monitor_path)
  cmd << "-monitor chardev:monitor"
  
  cmd << socket_chardev(:serial0, run_path('vm.console'))
  cmd << "-serial chardev:serial0"
  cmd << socket_chardev(:serial1, run_path('vm.ttyb'))
  cmd << "-serial chardev:serial1"
  
  # vnc socket
  cmd << "-vnc unix:#{run_path('vm.vnc')}"
  
  # If vnc_display is set, allow configuring a TCP based VNC port:
  if vnc_display
    cmd << "-vnc #{vnc_display}"
  end
  
  # Other devices
  devices.each do |driver, parameters| 
    cmd << "-device #{driver}," + 
      parameter_list(parameters)
  end

  # Boot order
  if boot
    cmd << "-boot " + parameter_list(boot)
  end

  cmd += network_options      
  
  # Extra arguments
  cmd += @extra_args
  
  return cmd
end
connect(socket) click to toggle source

Connects the current terminal to the given socket. Available sockets include :monitor, :vnc, :console, :ttyb.

# File lib/qemu-toolkit/vm.rb, line 289
def connect(socket)
  socket_path = run_path("vm.#{socket}")
  cmd = "socat stdio unix-connect:#{socket_path}"
  
  exec cmd
end
disk_options() click to toggle source
# File lib/qemu-toolkit/vm.rb, line 241
def disk_options
  cmd = []
  
  if @disks.empty? && !iscsi_target && @drives.empty?
    raise "No disks defined, can't run." 
  end
  
  disk_index = 0
  if iscsi_target
    target = produce_target(*iscsi_target)
    target.ensure_exists
    
    target.disks.each do |device|
      params = {
          file: device, 
          if: 'virtio', 
          index: disk_index, 
          media: 'disk', 
          cache: 'none'
      }
      params[:boot] = 'on' if disk_index == 0
      cmd << "-drive " + parameter_list(params)
      
      disk_index += 1
    end
  end
  
  @disks.each do |path|
    params = { file: path, if: 'virtio', index: disk_index, media: "disk" }
    params[:boot] = 'on' if disk_index == 0 

    cmd << "-drive " + parameter_list(params)

    disk_index += 1
  end
  
  @drives.each do |drive_options|
    cmd << "-drive " + 
      parameter_list(drive_options.merge(index: disk_index))
    disk_index += 1
  end
  
  return cmd
end
kill() click to toggle source

Kills the vm the hard way.

# File lib/qemu-toolkit/vm.rb, line 298
def kill
  run_cmd "kill #{pid}"
end
network_options() click to toggle source
# File lib/qemu-toolkit/vm.rb, line 186
def network_options
  cmd = []

  # networking: nic
  vlan = 0
  # Look up all existing vnics for this virtual machine
  vnics = Vnic.for_prefix(name, @backend)
  
  nics.each do |nic_name, parameters|
    via = parameters.delete(:via)
    model = parameters.delete(:model) || 'virtio'
    macaddr = parameters.delete(:macaddr)

    # All vnics that travel via the given interface (via)
    vnic = vnics.allocate(via, macaddr)

    # If no vnic has been found, create a new one.
    unless vnic
      vnic = Vnic.create(name, via, @backend, macaddr)
    end
    
    cmd << "-net vnic,"+
      parameter_list(
        parameters.merge(
          vlan: vlan, name: nic_name, 
          ifname: vnic.vnic_name, 
        ))

    if model == 'virtio'
      cmd << '-device virtio-net-pci,'+
        parameter_list(
          mac: vnic.macaddr, 
          tx: 'timer', x_txtimer: 200000, x_txburst: 128, 
          vlan: vlan) 
    else
      cmd << "-net nic,"+
        parameter_list(
          vlan: vlan, name: nic_name, 
          model: model,
          macaddr: vnic.macaddr)
    end

    vlan += 1
  end
  
  # networking: net
  nets.each do |type, parameters|
    map(parameters, :macaddr) { |a| Network::MacAddress.new(a) }
    
    cmd << "-net #{type},"+
      parameter_list(parameters)
  end

  cmd
end
pid() click to toggle source

Attempts to read and return the pid of the running VM process.

# File lib/qemu-toolkit/vm.rb, line 335
def pid
  Integer(File.read(pid_path).lines.first.chomp)
end
produce_target(host, port) click to toggle source

Returns an ISCSITarget for host and port.

# File lib/qemu-toolkit/vm.rb, line 310
def produce_target(host, port)
  ISCSITarget.new(host, port, @backend)
end
running?() click to toggle source

Returns true if the virtual machine seems to be currently running.

# File lib/qemu-toolkit/vm.rb, line 316
def running?
  if File.exist?(pid_path) 
    # Prod the process using kill. This will not actually kill the
    # process!
    begin
      Process.kill(0, pid)
    rescue Errno::ESRCH
      # When this point is reached, the process doesn't exist.
      return false
    end

    return true
  end
  
  return false
end
shutdown() click to toggle source

Sends a shutdown command via the monitor socket of the virtual machine.

# File lib/qemu-toolkit/vm.rb, line 304
def shutdown
  monitor_cmd 'system_powerdown'
end
start(dryrun, opts={}) click to toggle source

Runs the VM using qemu.

# File lib/qemu-toolkit/vm.rb, line 102
def start(dryrun, opts={})
  if dryrun
    puts command(opts) 
  else
    # Make sure var/run/qemu-toolkit/VMNAME exists.
    FileUtils.mkdir_p run_path
    
    @backend.qemu("vm<#{name}>", command(opts))
  end
end

Private Instance Methods

map(hash, key, &block) click to toggle source

Maps a key from a hash to a new value returned by the block.

# File lib/qemu-toolkit/vm.rb, line 348
def map hash, key, &block
  return unless hash.has_key? key
  hash[key] = block.call(hash[key])
end
monitor_cmd(cmd) click to toggle source
# File lib/qemu-toolkit/vm.rb, line 340
def monitor_cmd(cmd)
  socket = ::UNIXSocket.new(monitor_path)
  socket.puts cmd
  socket.close
end
monitor_path() click to toggle source

Returns the file path of the monitor socket (unix socket below /var/run) usually. )

# File lib/qemu-toolkit/vm.rb, line 386
def monitor_path
  run_path 'vm.monitor'
end
parameter_list(parameters) click to toggle source

Formats a parameter list as key=value,key=value

# File lib/qemu-toolkit/vm.rb, line 359
def parameter_list(parameters)
  key_translator = Hash.new { |h,k| k }
  key_translator.update(
    x_txtimer: 'x-txtimer', 
    x_txburst: 'x-txburst')
  
  parameters.
    map { |k,v| "#{key_translator[k]}=#{v}" }.
    join(',')
end
pid_path() click to toggle source

Returns the file path of the vm pid file.

# File lib/qemu-toolkit/vm.rb, line 379
def pid_path
  run_path 'vm.pid'
end
run_cmd(*args) click to toggle source

Runs a command and returns its stdout. This raises an error if the command doesn’t exit with a status of 0.

# File lib/qemu-toolkit/vm.rb, line 393
def run_cmd(*args)
  cmd = args.join(' ')
  ret = %x(#{cmd})

  raise "Execution error: #{cmd}." unless $?.success?

  ret
end
run_path(*args) click to toggle source

Returns the path below /var/run (usually) that contains runtime files for the virtual machine.

# File lib/qemu-toolkit/vm.rb, line 373
def run_path(*args)
  Config.var_run(name, *args)
end
socket_chardev(name, path) click to toggle source
# File lib/qemu-toolkit/vm.rb, line 353
def socket_chardev(name, path)
  "-chardev socket,id=#{name},path=#{path},server,nowait"
end