Class: QemuToolkit::VM

Inherits:
Object
  • Object
show all
Defined in:
lib/qemu-toolkit/vm.rb

Overview

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(backend) ⇒ VM

Returns a new instance of VM.



68
69
70
71
72
73
74
75
76
77
78
79
80
# 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

Instance Attribute Details

#bootObject

Boot order (if set)



66
67
68
# File 'lib/qemu-toolkit/vm.rb', line 66

def boot
  @boot
end

#cpusObject

The number of cpus to configure, defaults to 2.



56
57
58
# File 'lib/qemu-toolkit/vm.rb', line 56

def cpus
  @cpus
end

#devicesObject (readonly)

Other devices



64
65
66
# File 'lib/qemu-toolkit/vm.rb', line 64

def devices
  @devices
end

#iscsi_targetObject

iSCSI target iqn and ip address to connect to



49
50
51
# File 'lib/qemu-toolkit/vm.rb', line 49

def iscsi_target
  @iscsi_target
end

#keyboard_layoutObject

Keyboard layout



62
63
64
# File 'lib/qemu-toolkit/vm.rb', line 62

def keyboard_layout
  @keyboard_layout
end

#nameObject

VM name



47
48
49
# File 'lib/qemu-toolkit/vm.rb', line 47

def name
  @name
end

#netsObject (readonly)

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



54
55
56
# File 'lib/qemu-toolkit/vm.rb', line 54

def nets
  @nets
end

#nicsObject (readonly)

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



51
52
53
# File 'lib/qemu-toolkit/vm.rb', line 51

def nics
  @nics
end

#ramObject

Ram in megabytes



58
59
60
# File 'lib/qemu-toolkit/vm.rb', line 58

def ram
  @ram
end

#vnc_displayObject

VNC display port



60
61
62
# File 'lib/qemu-toolkit/vm.rb', line 60

def vnc_display
  @vnc_display
end

Class Method Details

.[](name, backend = nil) ⇒ Object

Access the definition of a single vm.



41
42
43
# File 'lib/qemu-toolkit/vm.rb', line 41

def [](name, backend=nil)
  all(backend).find { |vm| vm.name === name }
end

.all(backend = nil) ⇒ Object

Load all vm descriptions and provide an iterator for them.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 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

Instance Method Details

#add_device(driver, parameters) ⇒ Object



82
83
84
# File 'lib/qemu-toolkit/vm.rb', line 82

def add_device(driver, parameters)
  @devices << [driver, parameters]
end

#add_disk(path) ⇒ Object



88
89
90
# File 'lib/qemu-toolkit/vm.rb', line 88

def add_disk(path)
  @disks << path
end

#add_drive(parameters) ⇒ Object



85
86
87
# File 'lib/qemu-toolkit/vm.rb', line 85

def add_drive(parameters)
  @drives << parameters
end

#add_extra_arg(argument) ⇒ Object



97
98
99
# File 'lib/qemu-toolkit/vm.rb', line 97

def add_extra_arg(argument)
  @extra_args << argument
end

#add_net(type, parameters) ⇒ Object



94
95
96
# File 'lib/qemu-toolkit/vm.rb', line 94

def add_net(type, parameters)
  @nets << [type, parameters]
end

#add_nic(name, parameters) ⇒ Object



91
92
93
# File 'lib/qemu-toolkit/vm.rb', line 91

def add_nic(name, parameters)
  @nics << [name, parameters]
end

#command(opts = {}) ⇒ Object

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.

Returns:

  • String command to run the machine



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# 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) ⇒ Object

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



286
287
288
289
290
291
# File 'lib/qemu-toolkit/vm.rb', line 286

def connect(socket)
  socket_path = run_path("vm.#{socket}")
  cmd = "socat stdio unix-connect:#{socket_path}"
  
  exec cmd
end

#disk_optionsObject



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# 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|
    cmd << "-drive file=#{path},if=virtio,index=#{disk_index},"+
      "media=disk,boot=on"
    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

#killObject

Kills the vm the hard way.



295
296
297
# File 'lib/qemu-toolkit/vm.rb', line 295

def kill
  run_cmd "kill #{pid}"
end

#network_optionsObject



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# 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

#pidObject

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



332
333
334
# File 'lib/qemu-toolkit/vm.rb', line 332

def pid
  Integer(File.read(pid_path).lines.first.chomp)
end

#produce_target(host, port) ⇒ Object

Returns an ISCSITarget for host and port.



307
308
309
# File 'lib/qemu-toolkit/vm.rb', line 307

def produce_target(host, port)
  ISCSITarget.new(host, port, @backend)
end

#running?Boolean

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

Returns:

  • (Boolean)


313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/qemu-toolkit/vm.rb', line 313

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

#shutdownObject

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



301
302
303
# File 'lib/qemu-toolkit/vm.rb', line 301

def shutdown
  monitor_cmd 'system_powerdown'
end

#start(dryrun, opts = {}) ⇒ Object

Runs the VM using qemu.



102
103
104
105
106
107
108
109
110
111
# 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