/lib/qemu-toolkit/vm.rb

https://bitbucket.org/kschiess/qemu-toolkit · Ruby · 402 lines · 293 code · 50 blank · 59 comment · 12 complexity · 8fc0e75f7f6981de4c082715426f93cb MD5 · raw file

  1. require 'qemu-toolkit/config'
  2. require 'qemu-toolkit/dsl'
  3. require 'qemu-toolkit/iscsi_target'
  4. require 'fileutils'
  5. require 'socket'
  6. module QemuToolkit
  7. # Abstracts a virtual machine on a vm host. This class provides all sorts
  8. # of methods that execute administration actions.
  9. #
  10. class VM
  11. class << self # CLASS METHODS
  12. # Load all vm descriptions and provide an iterator for them.
  13. #
  14. def all(backend=nil)
  15. Enumerator.new do |yielder|
  16. libdir = Config.etc('lib')
  17. if ::File.directory? libdir
  18. $:.unshift libdir
  19. end
  20. Dir[Config.etc('*.rb')].each do |vm_file|
  21. # Load all virtual machines from the given file
  22. dsl = DSL::File.new
  23. dsl.add_toplevel_target :virtual_machine, lambda { |name|
  24. VM.new(backend).tap { |vm| vm.name = name } }
  25. dsl.load_file(vm_file)
  26. # Yield them all in turn
  27. dsl.objects.each do |vm|
  28. yielder << vm
  29. end
  30. end
  31. end
  32. end
  33. # Access the definition of a single vm.
  34. #
  35. def [](name, backend=nil)
  36. all(backend).find { |vm| vm.name === name }
  37. end
  38. end
  39. # VM name
  40. attr_accessor :name
  41. # iSCSI target iqn and ip address to connect to
  42. attr_accessor :iscsi_target
  43. # A list of network cards that will be connected to vnics on the host.
  44. attr_reader :nics
  45. # A list of network configuration statements that will be passed through
  46. # to qemu.
  47. attr_reader :nets
  48. # The number of cpus to configure, defaults to 2.
  49. attr_accessor :cpus
  50. # Ram in megabytes
  51. attr_accessor :ram
  52. # VNC display port
  53. attr_accessor :vnc_display
  54. # Keyboard layout
  55. attr_accessor :keyboard_layout
  56. # Other devices
  57. attr_reader :devices
  58. # Boot order (if set)
  59. attr_accessor :boot
  60. def initialize(backend)
  61. @disks = []
  62. @drives = []
  63. @nics = []
  64. @nets = []
  65. @cpus = 2
  66. @ram = 1024
  67. @backend = backend
  68. @vnc_display = nil
  69. @extra_args = []
  70. # TODO document
  71. @devices = []
  72. end
  73. def add_device(driver, parameters)
  74. @devices << [driver, parameters]
  75. end
  76. def add_drive(parameters)
  77. @drives << parameters
  78. end
  79. def add_disk(path)
  80. @disks << path
  81. end
  82. def add_nic(name, parameters)
  83. @nics << [name, parameters]
  84. end
  85. def add_net(type, parameters)
  86. @nets << [type, parameters]
  87. end
  88. def add_extra_arg(argument)
  89. @extra_args << argument
  90. end
  91. # Runs the VM using qemu.
  92. def start(dryrun, opts={})
  93. if dryrun
  94. puts command(opts)
  95. else
  96. # Make sure var/run/qemu-toolkit/VMNAME exists.
  97. FileUtils.mkdir_p run_path
  98. @backend.qemu("vm<#{name}>", command(opts))
  99. end
  100. end
  101. # Returns the command that is needed to run this virtual machine. Note
  102. # that this also modifies system configuration and is not just a routine
  103. # that returns a string.
  104. #
  105. # @return String command to run the machine
  106. #
  107. def command opts={}
  108. cmd = []
  109. cmd << "-name #{name}"
  110. cmd << "-m #{ram}"
  111. cmd << "-daemonize"
  112. cmd << '-nographic'
  113. cmd << "-cpu qemu64"
  114. cmd << "-smp #{cpus}"
  115. cmd << "-no-hpet"
  116. cmd << "-enable-kvm"
  117. cmd << "-vga cirrus"
  118. cmd << "-parallel none"
  119. cmd << "-usb"
  120. cmd << '-usbdevice tablet'
  121. if keyboard_layout
  122. cmd << "-k #{keyboard_layout}"
  123. end
  124. # Add disks
  125. cmd += disk_options
  126. # Was an iso image given to boot from?
  127. if iso_path=opts[:bootiso]
  128. cmd << "-cdrom #{iso_path}"
  129. cmd << "-boot order=cd,once=d"
  130. else
  131. cmd << '-boot order=cd'
  132. end
  133. # Set paths for communication with vm
  134. cmd << "-pidfile #{pid_path}"
  135. cmd << socket_chardev(:monitor, monitor_path)
  136. cmd << "-monitor chardev:monitor"
  137. cmd << socket_chardev(:serial0, run_path('vm.console'))
  138. cmd << "-serial chardev:serial0"
  139. cmd << socket_chardev(:serial1, run_path('vm.ttyb'))
  140. cmd << "-serial chardev:serial1"
  141. # vnc socket
  142. cmd << "-vnc unix:#{run_path('vm.vnc')}"
  143. # If vnc_display is set, allow configuring a TCP based VNC port:
  144. if vnc_display
  145. cmd << "-vnc #{vnc_display}"
  146. end
  147. # Other devices
  148. devices.each do |driver, parameters|
  149. cmd << "-device #{driver}," +
  150. parameter_list(parameters)
  151. end
  152. # Boot order
  153. if boot
  154. cmd << "-boot " + parameter_list(boot)
  155. end
  156. cmd += network_options
  157. # Extra arguments
  158. cmd += @extra_args
  159. return cmd
  160. end
  161. def network_options
  162. cmd = []
  163. # networking: nic
  164. vlan = 0
  165. # Look up all existing vnics for this virtual machine
  166. vnics = Vnic.for_prefix(name, @backend)
  167. nics.each do |nic_name, parameters|
  168. via = parameters.delete(:via)
  169. model = parameters.delete(:model) || 'virtio'
  170. macaddr = parameters.delete(:macaddr)
  171. # All vnics that travel via the given interface (via)
  172. vnic = vnics.allocate(via, macaddr)
  173. # If no vnic has been found, create a new one.
  174. unless vnic
  175. vnic = Vnic.create(name, via, @backend, macaddr)
  176. end
  177. cmd << "-net vnic,"+
  178. parameter_list(
  179. parameters.merge(
  180. vlan: vlan, name: nic_name,
  181. ifname: vnic.vnic_name,
  182. ))
  183. if model == 'virtio'
  184. cmd << '-device virtio-net-pci,'+
  185. parameter_list(
  186. mac: vnic.macaddr,
  187. tx: 'timer', x_txtimer: 200000, x_txburst: 128,
  188. vlan: vlan)
  189. else
  190. cmd << "-net nic,"+
  191. parameter_list(
  192. vlan: vlan, name: nic_name,
  193. model: model,
  194. macaddr: vnic.macaddr)
  195. end
  196. vlan += 1
  197. end
  198. # networking: net
  199. nets.each do |type, parameters|
  200. map(parameters, :macaddr) { |a| Network::MacAddress.new(a) }
  201. cmd << "-net #{type},"+
  202. parameter_list(parameters)
  203. end
  204. cmd
  205. end
  206. def disk_options
  207. cmd = []
  208. if @disks.empty? && !iscsi_target && @drives.empty?
  209. raise "No disks defined, can't run."
  210. end
  211. disk_index = 0
  212. if iscsi_target
  213. target = produce_target(*iscsi_target)
  214. target.ensure_exists
  215. target.disks.each do |device|
  216. params = {
  217. file: device,
  218. if: 'virtio',
  219. index: disk_index,
  220. media: 'disk',
  221. cache: 'none'
  222. }
  223. params[:boot] = 'on' if disk_index == 0
  224. cmd << "-drive " + parameter_list(params)
  225. disk_index += 1
  226. end
  227. end
  228. @disks.each do |path|
  229. params = { file: path, if: 'virtio', index: disk_index, media: "disk" }
  230. params[:boot] = 'on' if disk_index == 0
  231. cmd << "-drive " + parameter_list(params)
  232. disk_index += 1
  233. end
  234. @drives.each do |drive_options|
  235. cmd << "-drive " +
  236. parameter_list(drive_options.merge(index: disk_index))
  237. disk_index += 1
  238. end
  239. return cmd
  240. end
  241. # Connects the current terminal to the given socket. Available sockets
  242. # include :monitor, :vnc, :console, :ttyb.
  243. #
  244. def connect(socket)
  245. socket_path = run_path("vm.#{socket}")
  246. cmd = "socat stdio unix-connect:#{socket_path}"
  247. exec cmd
  248. end
  249. # Kills the vm the hard way.
  250. #
  251. def kill
  252. run_cmd "kill #{pid}"
  253. end
  254. # Sends a shutdown command via the monitor socket of the virtual machine.
  255. #
  256. def shutdown
  257. monitor_cmd 'system_powerdown'
  258. end
  259. # Returns an ISCSITarget for host and port.
  260. #
  261. def produce_target(host, port)
  262. ISCSITarget.new(host, port, @backend)
  263. end
  264. # Returns true if the virtual machine seems to be currently running.
  265. #
  266. def running?
  267. if File.exist?(pid_path)
  268. # Prod the process using kill. This will not actually kill the
  269. # process!
  270. begin
  271. Process.kill(0, pid)
  272. rescue Errno::ESRCH
  273. # When this point is reached, the process doesn't exist.
  274. return false
  275. end
  276. return true
  277. end
  278. return false
  279. end
  280. # Attempts to read and return the pid of the running VM process.
  281. #
  282. def pid
  283. Integer(File.read(pid_path).lines.first.chomp)
  284. end
  285. private
  286. def monitor_cmd(cmd)
  287. socket = ::UNIXSocket.new(monitor_path)
  288. socket.puts cmd
  289. socket.close
  290. end
  291. # Maps a key from a hash to a new value returned by the block.
  292. #
  293. def map hash, key, &block
  294. return unless hash.has_key? key
  295. hash[key] = block.call(hash[key])
  296. end
  297. def socket_chardev(name, path)
  298. "-chardev socket,id=#{name},path=#{path},server,nowait"
  299. end
  300. # Formats a parameter list as key=value,key=value
  301. #
  302. def parameter_list(parameters)
  303. key_translator = Hash.new { |h,k| k }
  304. key_translator.update(
  305. x_txtimer: 'x-txtimer',
  306. x_txburst: 'x-txburst')
  307. parameters.
  308. map { |k,v| "#{key_translator[k]}=#{v}" }.
  309. join(',')
  310. end
  311. # Returns the path below /var/run (usually) that contains runtime files
  312. # for the virtual machine.
  313. #
  314. def run_path(*args)
  315. Config.var_run(name, *args)
  316. end
  317. # Returns the file path of the vm pid file.
  318. #
  319. def pid_path
  320. run_path 'vm.pid'
  321. end
  322. # Returns the file path of the monitor socket (unix socket below /var/run)
  323. # usually. )
  324. #
  325. def monitor_path
  326. run_path 'vm.monitor'
  327. end
  328. # Runs a command and returns its stdout. This raises an error if the
  329. # command doesn't exit with a status of 0.
  330. #
  331. def run_cmd(*args)
  332. cmd = args.join(' ')
  333. ret = %x(#{cmd})
  334. raise "Execution error: #{cmd}." unless $?.success?
  335. ret
  336. end
  337. end
  338. end