KVM provider: Fixed Windows support #465

Merged
merged 7 commits into from Jan 10, 2013
View
@@ -4,17 +4,22 @@ NOTE: Virtualbox doesn't like KVM to be enabled
## Prerequisites
+Depending on your operating system you may need to install packages for kvm,
+qemu and libvirt.
+
To check if your kernel can run kvm :
# kvm_ok or kvm-ok command (on Ubuntu at least)
kvm_ok
# or look for vmx or svm in /proc/cpuinfo
egrep '^flags.*(vmx|svm)' /proc/cpuinfo
-The modules needed are the following : kvm, kvm_intel or kvm-amd.
+The kernel modules needed are the following : kvm, kvm_intel or kvm-amd.
+
+### Storage Pool
-You need to have at least one storage pool defined in libvirt. You can check all
-available storage pools with
+You need to have at least one storage pool defined in libvirt where your VM
+images will be stored. You can check all available storage pools with
virsh pool-list
@@ -33,13 +38,16 @@ VM images in the directory /var/lib/libvirt/images with
EOF
virsh pool-create /tmp/pool.xml
+### Network
+
You need to have at least one network defined. You can check all available
networks with
virsh net-list
-If there is no default network, consult the documentation of your operating
-system to find out how to creat it.
+If there is no network, consult the documentation of your operating
+system to find out how to creat it. More information can also be found in the
+[libvirt documentation](http://libvirt.org/formatdomain.html#elementsNICS).
If you are using libvirt with a URI different than the default `qemu:///system`,
you need to create a config file for fog.io. If your libvirt endpoint is
@@ -49,9 +57,18 @@ file with
cat > ~/.fog << EOF
:default:
:libvirt_uri: qemu+ssh://cloud@myhost.com/system
+ EOF
+
+For more information have a look at the
+[libvirt documentation](http://libvirt.org/drvqemu.html#uris).
## Using VeeWee
+You can always get help by using the the built in help with every command.
+e.g. for the build command use
+
+ veewee kvm help build
+
List available templates
veewee kvm templates
@@ -64,23 +81,8 @@ Build the box using KVM / Quemu (this will take a while)
veewee kvm build 'My Ubuntu 12.10 box'
-You may want to use the VNC console (e.g. through virt-manager) to monitor /
-check the build process.
-
-## Options
-
-There is currently few options supported :
-
-1. **network_type**: the type of network used by this box on libvirt. It could
- be either _network_ (for using the default network) or _bridge_.
-2. **network_bridge_name**: the name of the bridge. It is used just in case
- **network_type** is set to _bridge_.
-3. **pool_name**: the _storage_ pool name to be used when creating the box. If
- not specified, the default one is used.
-
-## Notes
-
-Remove modules:
+You can specify the name of the storage pool and the network to be used when
+building a VM. Use the options`--pool-name` and `--network-name` with the built
+command:
- rmmod kvm_intel
- rmmod kvm
+ veewee kvm build 'My Ubuntu 12.10 box' --pool-name virtimages --network-name default
@@ -11,6 +11,8 @@ class Kvm< Veewee::Command::GroupBase
method_option :postinstall_include, :type => :array, :default => [], :aliases => "-i", :desc => "ruby regexp of postinstall filenames to additionally include"
method_option :postinstall_exclude, :type => :array, :default => [], :aliases => "-e", :desc => "ruby regexp of postinstall filenames to exclude"
method_option :use_emulation, :type => :boolean , :default => false, :desc => "Use QEMU emulation"
+ method_option :pool_name, :type => :string, :default => nil, :desc => "Name of the libvirt storage pool to be used"
+ method_option :network_name, :type => :string, :default => "default", :desc => "Name of the libvirt network to be used"
def build(box_name)
venv=Veewee::Environment.new(options)
venv.ui=env.ui
@@ -72,7 +74,6 @@ def undefine(definition_name)
begin
venv=Veewee::Environment.new(options)
venv.ui=env.ui
- venv.undefine(definition_name,options)
venv.definitions.undefine(definition_name,options)
rescue Error => ex
env.ui.error("#{ex}", :prefix=> false)
@@ -1,4 +1,5 @@
require 'nokogiri'
+require 'fileutils'
module Veewee
module Provider
@@ -7,82 +8,48 @@ module BoxCommand
# Create a new vm
def create(options={})
# Assemble the Virtualmachine and set all the memory and other stuff"
-
-
create_server(options)
create_volume(options)
+ add_virtio_drivers if File.exists?(File.join(definition.path, 'Autounattend.xml'))
self.create_floppy("virtualfloppy.img")
+ FileUtils.move(File.join(definition.path, 'Autounattend.xml.virtio'), File.join(definition.path, 'Autounattend.xml')) if File.exists?(File.join(definition.path, 'Autounattend.xml.virtio'))
+ add_floppy unless definition.floppy_files.nil?
end
def create_server(options)
- #memory_size,cpu_count, volume_size
- # verify some stuff before trying to create the server
- kvm_options = definition.kvm[:vm_options][0]
- # Create the "server"
- attributes = {
- :name => name,
- :memory_size => definition.memory_size.to_i*1024,
- :cpus => definition.cpu_count.to_i,
- :volume_capacity => "#{definition.disk_size}M",
- :domain_type => options['use_emulation'] ? 'qemu': 'kvm',
- :iso_file => definition.iso_file,
- :arch => definition.os_type_id.end_with?("_64") ? "x86_64" : "i686",
- :iso_dir => env.config.veewee.iso_dir
- }
- # Check for network stuff (default, bridge)
- check_network(kvm_options, attributes)
- # Check for pool (storage)
- check_pool(kvm_options, attributes)
- s=@connection.servers.create(attributes)
- end
- # Check the network availability of the defined network for kvm
- def check_network(options, attributes)
- env.logger.info "Checking network"
- if options.nil? or options.empty? or options["network_type"].nil? or options["network_type"] == "network"
- check_default_network
- attributes[:network_interface_type] = "network"
- elsif options["network_type"] == "bridge"
- options["network_bridge_name"].nil?
- if options["network_bridge_name"].nil?
- raise Veewee::Error, "You need to specify a 'network_bridge_name' if you plan to use 'bridge' as network type"
- else
- attributes[:network_interface_type] = "bridge"
- attributes[:network_bridge_name] = "#{options["network_bridge_name"]}"
- end
- else
- raise Veewee::Error, "You specified a 'network_type' that isn't known (#{options["network_type"]})"
+ # set volume pool name to user specified volume pool and fall back to first available volume pool
+ if options["pool_name"]
+ raise Veewee::Error, "Specified storage pool #{options["pool_name"]} does not exist" if @connection.pools.select { |pool| pool.name == options["pool_name"] }.empty?
+ volume_pool_name = options["pool_name"]
end
- end
+ volume_pool_name ||= @connection.pools.first.name
+ env.logger.info "Using storage pool #{volume_pool_name}"
- def check_default_network
- # Nothing specified, we check for default network
- conn = ::Libvirt::open("qemu:///system")
- networks=conn.list_networks
- if networks.count < 1
- raise Veewee::Error, "You need at least one (active) network defined or customize your definition. This needs to be available if you connect to qemu:///system."
+ # set network name to user specified network and fall back to default network or first available network
+ if options["network_name"]
+ raise Veewee::Error, "Specified network #{options["network_name"]} does not exist" if @connection.networks.select { |net| net.name == options["network_name"] }.empty?
+ network_name = options["network_name"]
end
- end
+ network_name ||= "default" unless @connection.networks.select { |net| net.name == 'default' }.empty?
+ network_name ||= @connection.networks.first.name
+ env.logger.info "Using network #{network_name}"
- # Check the given pool and append to attributes
- # Note: volume_pool_name is not working in fog library version 1.7.0.
- # This should be fixed in the next release.
- def check_pool(options, attributes)
- env.logger.info "Checking pool storage"
- if not options.nil? and not options.empty? and not options["pool_name"].nil?
- conn = ::Libvirt::open("qemu:///system")
- # Checking if the pool exists and if it is active
- begin
- storage = conn.lookup_storage_pool_by_name(options["pool_name"])
- rescue Libvirt::RetrieveError
- raise Veewee::Error, "You've specified an non-existent storage pool (#{options["pool_name"]})"
- end
- if storage.active?
- attributes[:volume_pool_name] = options["pool_name"]
- else
- raise Veewee::Error, "The storage pool '#{options["pool_name"]}' is not started. Start it and restart the creation process"
- end
- end
+ # Create the "server"
+ attributes = {
+ :name => name,
+ :memory_size => definition.memory_size.to_i*1024,
+ :cpus => definition.cpu_count.to_i,
+ :volume_capacity => "#{definition.disk_size}M",
+ :domain_type => options['use_emulation'] ? 'qemu' : 'kvm',
+ :iso_file => definition.iso_file,
+ :arch => definition.os_type_id.end_with?("_64") ? "x86_64" : "i686",
+ :iso_dir => env.config.veewee.iso_dir,
+ :volume_pool_name => volume_pool_name,
+ :network_nat_network => network_name
+ }
+
+ @connection.servers.create(attributes)
end
# Create the volume of a new vm
@@ -91,11 +58,12 @@ def create_volume(options)
end
def add_floppy
+ env.logger.info 'Adding floppy disk'
# Get a raw libvirt connection
- c=@connection.raw
+ conn = @connection.client
# Retrieve the domain
- domain=c.lookup_domain_by_name(name)
+ domain=conn.lookup_domain_by_name(name)
# Retrieve the existing XML from the domain
domain_xml=domain.xml_desc
@@ -107,8 +75,8 @@ def add_floppy
devices=domain_doc.xpath('/domain/devices').first
# The floppy xml representation
floppy_xml="<disk type='file' device='floppy'><driver name='qemu' type='raw'/><source file='"+
- File.join(definitition.path,"virtualfloppy.img") +
- "'/><target dev='fda' bus='fdc'/><address type='drive' controller='0' bus='0' unit='0'/></disk>
+ File.join(definition.path, "virtualfloppy.img") +
+ "'/><target dev='fda' bus='fdc'/><address type='drive' controller='0' bus='0' unit='0'/></disk>
<controller type='fdc' index='0'>"
# Convert the floppy xml to nokogiri
@@ -121,10 +89,92 @@ def add_floppy
new_xml=domain_doc.to_xml
# Undefine the existing domain
- s.undefine
+ domain.undefine
+
+ # Re-define the domain
+ conn.define_domain_xml(new_xml)
+ end
+
+ def add_virtio_drivers
+ env.logger.info 'Adding virtio drivers for windows system to the virtual machine'
+ # Get a raw libvirt connection
+ conn = @connection.client
+
+ # Retrieve the domain
+ domain=conn.lookup_domain_by_name(name)
+
+ # Retrieve the existing XML from the domain
+ domain_xml=domain.xml_desc
+
+ # Convert the xml nokogiri doc
+ domain_doc=Nokogiri::XML(domain_xml)
+
+ # Find the device section
+ devices=domain_doc.xpath('/domain/devices').first
+
+ # get latest version of virtio drivers
+ url ='http://alt.fedoraproject.org/pub/alt/virtio-win/latest/images/bin/'
+ filename = open(url).read.scan(/\"(virtio-win-.*.iso)\"/).first.first
+ download_iso(url + filename, filename)
+ path = File.join(env.config.veewee.iso_dir, filename)
+
+ # The disk xml representation
+ disk_xml="<disk type='file' device='cdrom'><driver name='qemu' type='raw'/><source file='" +
+ path + "'/><target dev='hdd' bus='ide'/></disk>"
+
+ # Convert the disk xml to nokogiri
+ disk_doc=Nokogiri::XML(disk_xml)
+
+ # Add the floppy to the devices section
+ devices.add_child(disk_doc.root)
+
+ # Get the raw xml of the changed document
+ new_xml=domain_doc.to_xml
+
+ # Undefine the existing domain
+ domain.undefine
# Re-define the domain
- c.define_domain_xml(new_xml)
+ conn.define_domain_xml(new_xml)
+
+ env.logger.info 'Add search path for virtio drivers to Autounattend.xml'
+ # parse Autoattend.xml to document
+ FileUtils.copy(File.join(definition.path, 'Autounattend.xml'), File.join(definition.path, 'Autounattend.xml.virtio'))
+ doc = Nokogiri::XML.parse(File.read(File.join(definition.path, 'Autounattend.xml')))
+ # determine platform and windows version
+ platform = definition.os_type_id.end_with?("_64") ? "amd64" : "x86"
+ version = case definition.os_type_id.downcase
+ when /windows-?7/
+ 'win7'
+ when /windows-?2008/
+ 'win7'
+ when /windows-?8/
+ 'win8'
+ when /xp/
+ 'xp'
+ when /vista/
+ 'vista'
+ else
+ raise 'could not determine windows version'
+ end
+ # create new element
+ component=Nokogiri::XML(%Q|<component name="Microsoft-Windows-PnpCustomizationsWinPE"
+processorArchitecture="#{platform}" publicKeyToken="31bf3856ad364e35"
+language="neutral" versionScope="nonSxS"
+xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+<DriverPaths>
+<PathAndCredentials wcm:keyValue="1" wcm:action="add">
+<Path>e:\\#{version}\\#{platform}</Path>
+</PathAndCredentials>
+</DriverPaths>
+</component>|)
+ doc.xpath('//unattend:settings[@pass="windowsPE"]', 'unattend' => 'urn:schemas-microsoft-com:unattend').first.add_child component.root
+
+ file = File.open(File.join(definition.path, 'Autounattend.xml'), 'w')
+ file.write(doc)
+ file.close
+
end
end #End Module
Oops, something went wrong.