[Ovirt-devel] [PATCH server] Added support for booting a VM from an ISO image.

Michael DeHaan mdehaan at redhat.com
Fri Oct 10 20:01:23 UTC 2008


Darryl L. Pierce wrote:
> 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.
>   

Please set the "--type" on the image object in Cobbler if you aren't 
doing so already.
This is much better than paying attention to the filename.

There is one for "iso", and another for "virtimage".   If specifying 
virt-image there
is also a parameter you can use to track the location of the XML file, 
though I had
some recent discussions with Bryan Kearney and David Huff about that.

The basics are that most likely we're going to want the data that goes 
into that XML
file stored in cobbler so that cobbler can generate the XML file if we 
decide to bump
up the RAM later.   Cobbler has a live mod_python based templating 
system so you
could just use that (once the code is added to support it) to just wget 
the XML and use
that in conjunction with virt-image.

This also prevents from having to keep the XML files around, though we 
may add
a facility in cobbler to "import" from an XML file that came out of 
virt-convert.

TBD, I'm still honestly unclear about some of the details.

Anyway, "image add" takes a --type, so be sure to set that and don't use 
the extension.

This will make things work better with koan, even though I know you 
aren't using it, it should
be consistent. 

(In fact, the path also needs tweaking for how you store it...
it should start with nfs://hostname:/path not just hostname:/path)

--Michael





> 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
>   




More information about the ovirt-devel mailing list