[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[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 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 Vm::COBBLER_PREFIX/
+    provisioning_type = :system_type if provision =~ /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..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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]