Darryl L. Pierce
2008-Sep-24 21:09 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is containined. This patch is not complete. I'm going to be away for a few days and am putting this out there if someone else has time to pick it up. Otherwise, I'll continue with it on Monday. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 10 +++++- src/app/models/vm.rb | 31 +++++++++++++++++- src/task-omatic/task_vm.rb | 22 ++++++++++--- src/test/unit/vm_test.rb | 57 ++++++++++++++++++++++++++++++++-- 4 files changed, 108 insertions(+), 12 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index b6192e2..bf50980 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -224,10 +224,16 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| - [profile.name + Vm::COBBLER_PROFILE_SUFFIX, profile.name] + [profile.name + Vm::COBBLER_PROFILE_SUFFIX, + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}:#{profile.name}"] end rescue diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..a5def2b 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,12 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -241,6 +246,28 @@ class Vm < ActiveRecord::Base def search_users vm_resource_pool.search_users end + + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + self.provisioning.include? COBBLER_PREFIX + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end protected def validate diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..11b7b8a 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -154,15 +154,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -267,6 +264,21 @@ def start_vm(task) host = findHostSLA(vm) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + + # if the VM is an image, then prepare things + if vm.uses_cobbler? && (vm.cobbler_type == Vm.IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + # if the file's an .ISO then we'll have to mount as a CDrom, otherwise + # we'll mount as a hard drive + if details.file[/.iso/] + # TODO mount details.file as a CDROM + else + # TODO mount details.file as a hard disk + end + end storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..2fbdf59 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,59 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning = + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning = + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_With_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-01 14:17 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS URL for where the virtual image will go to mount it; i.e., nfs://hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 13 +++++-- src/app/models/vm.rb | 31 +++++++++++++++- src/task-omatic/task_vm.rb | 63 ++++++++++++++++++++++++++++------ src/test/unit/vm_test.rb | 57 +++++++++++++++++++++++++++++-- 4 files changed, 144 insertions(+), 20 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..8b16c94 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..2e2ddb5 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,12 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +247,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + self.provisioning.include? COBBLER_PREFIX + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..101878b 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -25,7 +25,7 @@ gem 'cobbler' require 'cobbler' def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, - macAddr, bridge, diskDevices) + macAddr, bridge, diskDevices, bootdrive) doc = Document.new doc.add_element("domain", {"type" => "kvm"}) @@ -65,16 +65,28 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = [ 'hda', 'hdb', 'hdc', 'hdd', 'hde', 'hdf' ] + which_device = 0 diskDevices.each do |disk| diskdev = Element.new("disk") diskdev.add_attribute("type", "block") diskdev.add_attribute("device", "disk") diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_element("target", {"dev" => devs[which_device]}) doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 + end + + if bootdrive + bootdisk = Element.new("disk") + + bootdisk.add_attribute("type", bootdisk[:type]) + bootdisk.add_attribute("device", bootdisk[:device]) + bootdisk.add_element("source", {"file" => bootdrive[:source]}) + bootdisk.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + bootdisk.add_element("readonly") if bootdrive[:readonly] + + doc.root.elements["devices"] << bootdisk end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +166,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -268,12 +277,44 @@ def start_vm(task) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + # if the VM is an image, then prepare things + if vm.uses_cobbler? && (vm.cobbler_type == Vm.IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, protocol, sharepath, filename = details.filename(/(.*):\/\/(.*)\/(.*)/) + + raise Exception.new("Only NFS mounts are supported currently") unless protocol == 'nfs' + + # if the file's an .ISO then we'll have to mount as a CDrom, otherwise + # we'll mount as a hard drive + bootdrive[:type] = 'file' + bootdrive[:source] = filename + bootdrive[:path] = sharepath + if details.file[/.iso/] + bootdrive[:readonly] = true + bootdrive[:device] = 'cdrom' + else + bootdrive[:readonly] = false + bootdrive[:device] = 'disk' + end + + # mount the filesystem + mountpoint = `mktemp -d` + bootdrive[:mountpoint] = mountpoint + + unless system("mount #{sharepath} #{mountpoint}") + raise Exception.new "Unable to mount #{details.file}" + end + end + storagedevs = connect_storage_pools(conn, vm) # FIXME: get rid of the hardcoded bridge xml = create_vm_xml(vm.description, vm.uuid, vm.memory_allocated, vm.memory_used, vm.num_vcpus_allocated, vm.boot_device, - vm.vnic_mac_addr, "ovirtbr0", storagedevs) + vm.vnic_mac_addr, "ovirtbr0", storagedevs, bootdrive) dom = conn.define_domain_xml(xml.to_s) dom.create diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..c226c63 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,59 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_With_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-02 13:24 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 13 +++++-- src/app/models/vm.rb | 31 +++++++++++++++- src/task-omatic/task_vm.rb | 63 ++++++++++++++++++++++++++++------ src/test/unit/vm_test.rb | 57 +++++++++++++++++++++++++++++-- 4 files changed, 144 insertions(+), 20 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..8b16c94 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..2e2ddb5 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,12 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +247,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + self.provisioning.include? COBBLER_PREFIX + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..101878b 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -25,7 +25,7 @@ gem 'cobbler' require 'cobbler' def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, - macAddr, bridge, diskDevices) + macAddr, bridge, diskDevices, bootdrive) doc = Document.new doc.add_element("domain", {"type" => "kvm"}) @@ -65,16 +65,28 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = [ 'hda', 'hdb', 'hdc', 'hdd', 'hde', 'hdf' ] + which_device = 0 diskDevices.each do |disk| diskdev = Element.new("disk") diskdev.add_attribute("type", "block") diskdev.add_attribute("device", "disk") diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_element("target", {"dev" => devs[which_device]}) doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 + end + + if bootdrive + bootdisk = Element.new("disk") + + bootdisk.add_attribute("type", bootdisk[:type]) + bootdisk.add_attribute("device", bootdisk[:device]) + bootdisk.add_element("source", {"file" => bootdrive[:source]}) + bootdisk.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + bootdisk.add_element("readonly") if bootdrive[:readonly] + + doc.root.elements["devices"] << bootdisk end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +166,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -268,12 +277,44 @@ def start_vm(task) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") + # if the VM is an image, then prepare things + if vm.uses_cobbler? && (vm.cobbler_type == Vm.IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, protocol, sharepath, filename = details.filename(/(.*):\/\/(.*)\/(.*)/) + + raise Exception.new("Only NFS mounts are supported currently") unless protocol == 'nfs' + + # if the file's an .ISO then we'll have to mount as a CDrom, otherwise + # we'll mount as a hard drive + bootdrive[:type] = 'file' + bootdrive[:source] = filename + bootdrive[:path] = sharepath + if details.file[/.iso/] + bootdrive[:readonly] = true + bootdrive[:device] = 'cdrom' + else + bootdrive[:readonly] = false + bootdrive[:device] = 'disk' + end + + # mount the filesystem + mountpoint = `mktemp -d` + bootdrive[:mountpoint] = mountpoint + + unless system("mount #{sharepath} #{mountpoint}") + raise Exception.new "Unable to mount #{details.file}" + end + end + storagedevs = connect_storage_pools(conn, vm) # FIXME: get rid of the hardcoded bridge xml = create_vm_xml(vm.description, vm.uuid, vm.memory_allocated, vm.memory_used, vm.num_vcpus_allocated, vm.boot_device, - vm.vnic_mac_addr, "ovirtbr0", storagedevs) + vm.vnic_mac_addr, "ovirtbr0", storagedevs, bootdrive) dom = conn.define_domain_xml(xml.to_s) dom.create diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..c226c63 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,59 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_With_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-03 20:46 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
*** NOTE *** This is a work in progress. I'm at the point now where the NFS image is properly being mounted and the ISO being booted. But, for whatever reason, the VM is refusing to actually boot the CDROM. I'm sure it's something simple but I haven't figured out how to get past it. As it stands now, I can boot from an ISO on a cobbler server if I manually intervene and tell the VM to boot the CDROM. A pointer would be nice if someone can apply the patch and see what's going on. *** NOTE *** Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 13 ++++-- src/app/models/vm.rb | 31 ++++++++++++- src/task-omatic/task_vm.rb | 85 ++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 +++++++++++++++++++++- 4 files changed, 163 insertions(+), 22 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..8b16c94 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..963da84 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,12 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +247,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..0c1be3a 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = [ 'hda', 'hdb', 'hdd', 'hde', 'hdf' ] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => "hdc", "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 unless is_cdrom end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,22 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + result = StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) + + if result + puts "Found #{ip_addr}:#{export_path}" + else + puts "FUCK!" + end + + return result +end + def start_vm(task) puts "start_vm" @@ -268,8 +291,44 @@ def start_vm(task) conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") - storagedevs = connect_storage_pools(conn, vm) + # if the VM is an image, then prepare things + if vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + puts "Creating a new NFS storage volume" + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + if image_pool + image_pool.storage_volumes << image_volume + image_pool.save! + end + vm.storage_volumes << image_volume + vm.save! + end + end + + storagedevs = connect_storage_pools(conn, vm) + # FIXME: get rid of the hardcoded bridge xml = create_vm_xml(vm.description, vm.uuid, vm.memory_allocated, vm.memory_used, vm.num_vcpus_allocated, vm.boot_device, diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..0186c5f 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-08 19:04 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 50 +++++++++++++--------- src/app/models/vm.rb | 34 ++++++++++++++- src/task-omatic/task_vm.rb | 78 ++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 +++++++++++++++++++++++- 4 files changed, 181 insertions(+), 37 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..7f911e1 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +244,29 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision =~ /Vm::IMAGE_PREFIX at Vm::COBBLER_PREFIX/ + provisioning_type = :system_type if provision =~ /Vm::PROFILE_PREFIX at Vm::COBBLER_PREFIX/ + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.gsub(/(Vm::IMAGE_PREFIX|Vm::PROFILE_PREFIX)@Vm::COBBLER_PREFIX/, '') + + system = Cobbler::System.find_one(name) + + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.create("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.create("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..e37b88e 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{COBBLER_IMAGE}\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisiong] = settings + elsif settings =~ /#{COBBLER_PROFILE}\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..afac66d 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,45 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + if image_pool + image_pool.storage_volumes << image_volume + image_pool.save! + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-08 19:53 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 50 +++++++++++++--------- src/app/models/vm.rb | 34 ++++++++++++++- src/task-omatic/task_vm.rb | 78 ++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 +++++++++++++++++++++++- 4 files changed, 181 insertions(+), 37 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..0ec2837 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +244,29 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision =~ /Vm::IMAGE_PREFIX at Vm::COBBLER_PREFIX/ + provisioning_type = :system_type if provision =~ /Vm::PROFILE_PREFIX at Vm::COBBLER_PREFIX/ + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.gsub(/(Vm::IMAGE_PREFIX|Vm::PROFILE_PREFIX)@Vm::COBBLER_PREFIX/, '') + + system = Cobbler::System.find_one(name) + + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.create("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.create("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..e37b88e 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{COBBLER_IMAGE}\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisiong] = settings + elsif settings =~ /#{COBBLER_PROFILE}\@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..6adda3b 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,45 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + if image_pool + image_pool.storage_volumes << image_volume + image_pool.save! + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-09 15:12 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 50 ++++++++++++-------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 83 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 +++++++++++++++++++++- 4 files changed, 186 insertions(+), 37 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..0ec2837 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +244,29 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision =~ /Vm::IMAGE_PREFIX at Vm::COBBLER_PREFIX/ + provisioning_type = :system_type if provision =~ /Vm::PROFILE_PREFIX at Vm::COBBLER_PREFIX/ + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.gsub(/(Vm::IMAGE_PREFIX|Vm::PROFILE_PREFIX)@Vm::COBBLER_PREFIX/, '') + + system = Cobbler::System.find_one(name) + + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.create("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.create("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..982613f 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,50 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + image_pool.save! + end + vm.storage_volumes << image_volume + vm.save! + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-09 15:17 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 50 ++++++++++++-------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 81 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 ++++++++++++++++++++++- 4 files changed, 184 insertions(+), 37 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..0ec2837 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -223,13 +223,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +244,29 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision =~ /Vm::IMAGE_PREFIX at Vm::COBBLER_PREFIX/ + provisioning_type = :system_type if provision =~ /Vm::PROFILE_PREFIX at Vm::COBBLER_PREFIX/ + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.gsub(/(Vm::IMAGE_PREFIX|Vm::PROFILE_PREFIX)@Vm::COBBLER_PREFIX/, '') + + system = Cobbler::System.find_one(name) + + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.create("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.create("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..ef0f8e2 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise Exception.new("Image #{vm.cobbler_name} not found in Cobbler server") unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-10 14:43 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 51 +++++++++++++-------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 81 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 ++++++++++++++++++++++- 4 files changed, 185 insertions(+), 37 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..5e9da06 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,29 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.gsub(/(Vm::IMAGE_PREFIX|Vm::PROFILE_PREFIX)@Vm::COBBLER_PREFIX/, '') + + system = Cobbler::System.find_one(name) + + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.create("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.create("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..6c4ace6 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-10 19:55 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 51 ++++++++++++--------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 81 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 ++++++++++++++++++++++- 4 files changed, 184 insertions(+), 38 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..bc88760 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -70,7 +71,7 @@ class VmController < ApplicationController alert = "VM was successfully created." end render :json => { :object => "vm", :success => true, :alert => alert } - rescue + rescue Exception => error # FIXME: need to distinguish vm vs. task save errors (but should mostly be vm) render :json => { :object => "vm", :success => false, :errors => @vm.errors.localize_error_messages.to_a } @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,27 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.slice(/(.*):(.*)/, 2) + system = Cobbler::System.find_one(name) + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.new("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.new("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..6c4ace6 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-11 11:29 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 51 ++++++++++++-------- src/app/models/vm.rb | 34 +++++++++++++- src/task-omatic/task_vm.rb | 84 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 +++++++++++++++++++++- 4 files changed, 187 insertions(+), 38 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..bc88760 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -70,7 +71,7 @@ class VmController < ApplicationController alert = "VM was successfully created." end render :json => { :object => "vm", :success => true, :alert => alert } - rescue + rescue Exception => error # FIXME: need to distinguish vm vs. task save errors (but should mostly be vm) render :json => { :object => "vm", :success => false, :errors => @vm.errors.localize_error_messages.to_a } @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,27 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.slice(/(.*):(.*)/, 2) + system = Cobbler::System.find_one(name) + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.new("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.new("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..72ad9d8 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,14 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + StoragePool.find(:first, + :conditions => + ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +281,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) @@ -277,6 +334,9 @@ def start_vm(task) dom = conn.define_domain_xml(xml.to_s) dom.create + + # Remove the transient storage volume so that it's not persisted + vm.storage_volumes.delete image_volume defined? image_volume setVmVncPort(vm, dom) diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-13 15:30 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
The NFS export for Cobbler needs to be added as an NFS storage pool. Otherwise, taskomatic will not be able to locate it. Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 51 +++++++++++-------- src/app/models/vm.rb | 34 ++++++++++++- src/task-omatic/task_vm.rb | 91 +++++++++++++++++++++++++++++----- src/test/unit/vm_test.rb | 56 ++++++++++++++++++++- 4 files changed, 194 insertions(+), 38 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..bc88760 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -70,7 +71,7 @@ class VmController < ApplicationController alert = "VM was successfully created." end render :json => { :object => "vm", :success => true, :alert => alert } - rescue + rescue Exception => error # FIXME: need to distinguish vm vs. task save errors (but should mostly be vm) render :json => { :object => "vm", :success => false, :errors => @vm.errors.localize_error_messages.to_a } @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,27 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.slice(/(.*):(.*)/, 2) + system = Cobbler::System.find_one(name) + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.new("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.new("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..40e0a1d 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -231,6 +238,21 @@ def shutdown_vm(task) setVmShutdown(vm) end +# Find thes storage pool with the given ip address and export path. +# +def find_storage_pool(ip_addr, export_path) + pool = StoragePool.factory(StoragePool::NFS) + + pool.ip_addr = ip_addr + pool.export_path = export_path + + return pool + + #StoragePool.find(:first, + # :conditions => + # ['ip_addr = ? and export_path = ?',ip_addr, export_path]) +end + def start_vm(task) puts "start_vm" @@ -266,6 +288,48 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = find_storage_pool(ip_addr, export_path) + + raise Exception.new("Unable to find Cobbler storage pool") unless image_pool + + if image_pool + image_pool.storage_volumes << image_volume + end + vm.storage_volumes << image_volume + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") storagedevs = connect_storage_pools(conn, vm) @@ -278,6 +342,9 @@ def start_vm(task) dom = conn.define_domain_xml(xml.to_s) dom.create + # Remove the transient storage volume so that it's not persisted + vm.storage_volumes.delete image_volume defined? image_volume + setVmVncPort(vm, dom) conn.close diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1
Darryl L. Pierce
2008-Oct-13 16:45 UTC
[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.
Also added a few helper methods to Vm to contain the knowledge of how Cobbler integration is contained. When a user adds an ISO image to the Cobbler server on the appliance, they will need to do so using the full NFS path for where the virtual image will go to mount it; i.e., hostname:/path/to/filename.iso If the filename ends in ".iso" then the virtual machine will mount the file as a CDROM device and boot it. Otherwise, it mounts it as a hard disk device. To add an image to Cobbler, do the following: 1. Download an ISO image, such as the KDE LiveImage from Fedora. 2. Copy it to the NFS directory on the server: cp *.iso /ovirtnfs/kde-live-cd.iso 3. Add that image to your Cobbler instance: cobbler image add --name=KDE-LiveCD --file=management.priv.ovirt.org:/ovirtnfs/kde-live-cd.iso 4. Create a new VM in your server. 5. Select "KDE-LiveCD" from the list of operating systems. 6. Save the VM. 7. Start the VM. It should run the selected ISO. Signed-off-by: Darryl L. Pierce <dpierce at redhat.com> --- src/app/controllers/vm_controller.rb | 51 +++++++++++++--------- src/app/models/vm.rb | 34 ++++++++++++++- src/task-omatic/task_vm.rb | 76 ++++++++++++++++++++++++++++------ src/task-omatic/utils.rb | 4 +- src/test/unit/vm_test.rb | 56 +++++++++++++++++++++++- 5 files changed, 180 insertions(+), 41 deletions(-) diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index f5c0845..bc88760 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -48,6 +48,7 @@ class VmController < ApplicationController begin Vm.transaction do @vm.save! + _setup_vm_provision(params) @task = VmTask.new({ :user => @user, :vm_id => @vm.id, :action => VmTask::ACTION_CREATE_VM, @@ -70,7 +71,7 @@ class VmController < ApplicationController alert = "VM was successfully created." end render :json => { :object => "vm", :success => true, :alert => alert } - rescue + rescue Exception => error # FIXME: need to distinguish vm vs. task save errors (but should mostly be vm) render :json => { :object => "vm", :success => false, :errors => @vm.errors.localize_error_messages.to_a } @@ -223,13 +224,18 @@ class VmController < ApplicationController def _setup_provisioning_options @provisioning_options = [[Vm::PXE_OPTION_LABEL, Vm::PXE_OPTION_VALUE], [Vm::HD_OPTION_LABEL, Vm::HD_OPTION_VALUE]] - # FIXME add cobbler images too + begin + @provisioning_options += Cobbler::Image.find.collect do |image| + [image.name + Vm::COBBLER_IMAGE_SUFFIX, + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{image.name}"] + end + @provisioning_options += Cobbler::Profile.find.collect do |profile| [profile.name + Vm::COBBLER_PROFILE_SUFFIX, - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER + profile.name] - end + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{profile.name}"] + + end rescue #if cobbler doesn't respond/is misconfigured/etc just don't add profiles end @@ -239,24 +245,27 @@ class VmController < ApplicationController def _setup_vm_provision(params) # spaces are invalid in the cobbler name name = params[:vm][:uuid] - provision = params[:vm][:provisioning_and_boot_settings].gsub( - Vm::COBBLER_PREFIX + Vm::PROVISIONING_DELIMITER + - Vm::PROFILE_PREFIX + Vm::PROVISIONING_DELIMITER, "") mac = params[:vm][:vnic_mac_addr] - unless provision == Vm::PXE_OPTION_VALUE or - provision == Vm::HD_OPTION_VALUE - found = false - Cobbler::System.find.each{ |system| - if system.name == name - system.profile = provision - system.save - found = true + provision = params[:vm][:provisioning_and_boot_settings] + # determine what type of provisioning was selected for the VM + provisioning_type = :pxe_or_hd_type + provisioning_type = :image_type if provision.index "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}" + provisioning_type = :system_type if provision.index "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}" + + unless provisioning_type == :pxe_or_hd_type + cobbler_name = provision.slice(/(.*):(.*)/, 2) + system = Cobbler::System.find_one(name) + unless system + nic = Cobbler::NetworkInterface.new({'mac_address' => mac}) + + case provisioning_type + when :image_type: + system = Cobbler::System.new("name" => name, "image" => cobbler_name) + when :system_type: + system = Cobbler::System.new("name" => name, "profile" => cobbler_name) end - } - unless found - system = Cobbler::System.create("name" => name, - "profile" => provision) - system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => mac})] + + system.interfaces = [nic] system.save end end diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index ace6fb1..d7beacf 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -49,7 +49,7 @@ class Vm < ActiveRecord::Base PROFILE_PREFIX = "profile" IMAGE_PREFIX = "image" COBBLER_PROFILE_SUFFIX = " (Cobbler Profile)" - COBBLER_IMAGE_SUFFIX = " (Cobbler Profile)" + COBBLER_IMAGE_SUFFIX = " (Cobbler Image)" PXE_OPTION_LABEL = "PXE Boot" PXE_OPTION_VALUE = "pxe" @@ -139,7 +139,15 @@ class Vm < ActiveRecord::Base end def provisioning_and_boot_settings=(settings) - if settings==PXE_OPTION_VALUE + # if the settings have a prefix that matches cobber settings, then process + # those details + if settings =~ /#{IMAGE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_CDROM + self[:provisioning] = settings + elsif settings =~ /#{PROFILE_PREFIX}@#{COBBLER_PREFIX}/ + self[:boot_device] = BOOT_DEV_NETWORK + self[:provisioning] = settings + elsif settings==PXE_OPTION_VALUE self[:boot_device]= BOOT_DEV_NETWORK self[:provisioning]= nil elsif settings==HD_OPTION_VALUE @@ -242,6 +250,28 @@ class Vm < ActiveRecord::Base vm_resource_pool.search_users end + # Reports whether the VM is uses Cobbler for booting. + # + def uses_cobbler? + (self.provisioning != nil) && (self.provisioning.include? COBBLER_PREFIX) + end + + # Returns the cobbler type. + # + def cobbler_type + if self.uses_cobbler? + self.provisioning[/^(.*)@/,1] + end + end + + # Returns the cobbler provisioning name. + # + def cobbler_name + if self.uses_cobbler? + self.provisioning[/^.*@.*:(.*)/,1] + end + end + protected def validate resources = vm_resource_pool.max_resources_for_vm(self) diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index 3588224..8398e83 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -65,16 +65,26 @@ def create_vm_xml(name, uuid, memAllocated, memUsed, vcpus, bootDevice, doc.root.elements["devices"].add_element("emulator") doc.root.elements["devices"].elements["emulator"].text = "/usr/bin/qemu-kvm" - devs = [ 'hda', 'hdb', 'hdc', 'hdd' ] - i = 0 + devs = ['hda', 'hdb', 'hdc', 'hdd'] + which_device = 0 diskDevices.each do |disk| + is_cdrom = (disk =~ /\.iso/) ? true : false + diskdev = Element.new("disk") - diskdev.add_attribute("type", "block") - diskdev.add_attribute("device", "disk") - diskdev.add_element("source", {"dev" => disk}) - diskdev.add_element("target", {"dev" => devs[i]}) + diskdev.add_attribute("type", is_cdrom ? "file" : "block") + diskdev.add_attribute("device", is_cdrom ? "cdrom" : "disk") + + if is_cdrom + diskdev.add_element("readonly") + diskdev.add_element("source", {"file" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device], "bus" => "ide"}) + else + diskdev.add_element("source", {"dev" => disk}) + diskdev.add_element("target", {"dev" => devs[which_device]}) + end + doc.root.elements["devices"] << diskdev - i += 1 + which_device += 1 end doc.root.elements["devices"].add_element("interface", {"type" => "bridge"}) @@ -154,15 +164,12 @@ def create_vm(task) # create cobbler system profile begin if vm.provisioning and !vm.provisioning.empty? - provisioning_arr = vm.provisioning.split(Vm::PROVISIONING_DELIMITER) - if provisioning_arr[0]==Vm::COBBLER_PREFIX - if provisioning_arr[1]==Vm::PROFILE_PREFIX + if vm.uses_cobbler? + if vm.cobbler_type == Vm::PROFILE_PREFIX: system = Cobbler::System.new('name' => vm.uuid, 'profile' => provisioning_arr[2]) system.interfaces=[Cobbler::NetworkInterface.new({'mac_address' => vm.vnic_mac_addr})] system.save - elsif provisioning_arr[1]==Vm::IMAGE_PREFIX - #FIXME handle cobbler images end end end @@ -266,9 +273,52 @@ def start_vm(task) # hosts to see if there is a host that will fit these constraints host = findHostSLA(vm) + # if we're booting from a CDROM the VM is an image, + # then we need to add the NFS mount as a storage volume for this + # boot + # + if (vm.boot_device == Vm::BOOT_DEV_CDROM) && vm.uses_cobbler? && (vm.cobbler_type == Vm::IMAGE_PREFIX) + details = Cobbler::Image.find_one(vm.cobbler_name) + + raise "Image #{vm.cobbler_name} not found in Cobbler server" unless details + + ignored, ip_addr, export_path, filename + details.file.split(/(.*):(.*)\/(.*)/) + + found = false + + vm.storage_volumes.each do |volume| + if volume.filename == filename + if (volume.storage_pool.ip_addr == ip_addr) && + (volume.storage_pool.export_path == export_path) + found = true + end + end + end + + unless found + # Create a new transient NFS storage volume + # This volume is *not* persisted. + image_volume = StorageVolume.factory("NFS", + :filename => filename + ) + + image_volume.storage_pool + image_pool = StoragePool.factory(StoragePool::NFS) + + image_pool.ip_addr = ip_addr + image_pool.export_path = export_path + image_pool.storage_volumes << image_volume + image_volume.storage_pool = image_pool + end + end + conn = Libvirt::open("qemu+tcp://" + host.hostname + "/system") - storagedevs = connect_storage_pools(conn, vm) + volumes = [] + volumes += vm.storage_volumes + volumes << image_volume if image_volume + storagedevs = connect_storage_pools(conn, volumes) # FIXME: get rid of the hardcoded bridge xml = create_vm_xml(vm.description, vm.uuid, vm.memory_allocated, diff --git a/src/task-omatic/utils.rb b/src/task-omatic/utils.rb index 9e60122..47a4543 100644 --- a/src/task-omatic/utils.rb +++ b/src/task-omatic/utils.rb @@ -67,7 +67,7 @@ def teardown_storage_pools(conn) end end -def connect_storage_pools(conn, vm) +def connect_storage_pools(conn, storage_volumes) # here, build up a list of already defined pools. We'll use it # later to see if we need to define new pools for the storage or just # keep using existing ones @@ -78,7 +78,7 @@ def connect_storage_pools(conn, vm) end storagedevs = [] - vm.storage_volumes.each do |volume| + storage_volumes.each do |volume| # here, we need to iterate through each volume and possibly attach it # to the host we are going to be using storage_pool = volume.storage_pool diff --git a/src/test/unit/vm_test.rb b/src/test/unit/vm_test.rb index 4a5e353..22164e8 100644 --- a/src/test/unit/vm_test.rb +++ b/src/test/unit/vm_test.rb @@ -22,8 +22,58 @@ require File.dirname(__FILE__) + '/../test_helper' class VmTest < Test::Unit::TestCase fixtures :vms - # Replace this with your real tests. - def test_truth - assert true + def setup + @vm_name = "Test" + @no_cobbler_provisioning = "#{@vm_name}" + @cobbler_image_provisioning + "#{Vm::IMAGE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + @cobbler_profile_provisioning + "#{Vm::PROFILE_PREFIX}@#{Vm::COBBLER_PREFIX}#{Vm::PROVISIONING_DELIMITER}#{@vm_name}" + end + + # Ensures that, if the VM does not contain the Cobbler prefix, that it + # does not claim to be a Cobbler VM. + # + def test_uses_cobbler_without_cobbler_prefix + vm = Vm.new + + vm.provisioning_and_boot_settings=@no_cobbler_provisioning + + flunk "VM is not a Cobbler provisioned one." if vm.uses_cobbler? + assert_equal @vm_name, vm.provisioning, "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler profile. + # + def test_uses_cobbler_with_cobbler_profile + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_profile_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::PROFILE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." + end + + # Ensures that the VM reports that it uses Cobbler if the provisioning + # is for a Cobbler image. + # + def test_uses_cobbler_with_cobbler_image + vm = Vm.new + + vm.provisioning_and_boot_settings = @cobbler_image_provisioning + + flunk "VM did not report that it's Cobbler provisioned." unless vm.uses_cobbler? + assert_equal Vm::IMAGE_PREFIX, + vm.cobbler_type, + "Wrong cobbler type reported." + assert_equal @vm_name, + vm.cobbler_name, + "Wrong name reported." end end -- 1.5.5.1