Hello all,

Here are a couple of patches for ganeti that make it possible to pass a 
cdrom_image_path that is an HTTP(s)/FTP(s) url. The original patch was 
written by Apollon Oikonomopoulos more than 2 years ago and minor 
modifications were made by Christos Stavrakakis in order for the patches to 
apply cleanly to 2.7+ versions. There were occasional hiccups when using 
this feature with an old kvm version (0.12) but with version 1.1.2+ we 
haven't noticed any problems so far.

We are currently using this functionality on our ganetimgr[1] frontend with 
ganeti versions 2.6, 2.7 and 2.9 to give our users the ability to boot 
their VMs without an OS and then install a custom ISO of their choice.

We (at GRNET) have been sitting on this patch for quite a while and we hope 
it's time it gets merged upstream.

Regards,
--
George Kargiotakis
GRNET NOC

[1] http://ganetimgr.readthedocs.org/en/wheezy/

commit 9fe0df635fb34ad1eb69fbb62bd91061f9154e6a
Author: Christos Stavrakakis <[email protected]>
Date:   Tue Oct 29 12:10:54 2013 +0200

    kvm: check that the ISO image is there if it's a URL
    
    Perform a simple urllib2 check on ISO images specified as URL before
    instance start, so as to work around qemu bug #597575 [1].
    
    [1] https://bugs.launchpad.net/qemu/+bug/597575
    
    Signed-off-by: Apollon Oikonomopoulos <[email protected]>
    Signed-off-by: Christos Stavrakakis <[email protected]>

Index: ganeti-2.9.2/lib/hypervisor/hv_kvm.py
===================================================================
--- ganeti-2.9.2.orig/lib/hypervisor/hv_kvm.py	2014-01-09 17:27:18.000000000 +0200
+++ ganeti-2.9.2/lib/hypervisor/hv_kvm.py	2014-01-09 17:27:18.000000000 +0200
@@ -34,6 +34,7 @@
 import struct
 import fcntl
 import shutil
+import urllib2
 import socket
 import stat
 import StringIO
@@ -164,6 +165,24 @@
   return (ifname, tapfd)
 
 
+def _CheckUrl(url):
+  """Check if a given URL exists on the server
+
+  """
+  req = urllib2.Request(url)
+
+  # XXX: ugly but true
+  req.get_method = lambda: "HEAD"
+
+  try:
+    resp = urllib2.urlopen(req)
+  except urllib2.URLError:
+    return False
+
+  del resp
+  return True
+
+
 class QmpMessage:
   """QEMU Messaging Protocol (QMP) message.
 
@@ -1161,7 +1180,13 @@
     iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
     if iso_image:
       options = ",media=cdrom"
-      if not re.match(r'(https?|ftps?)://', iso_image):
+      if re.match(r'(https?|ftps?)://', iso_image):
+        # Check that the iso image is really there
+        # See https://bugs.launchpad.net/qemu/+bug/597575
+        if not _CheckUrl(iso_image):
+          raise errors.HypervisorError("ISO image %s is not accessible" %
+                                       iso_image)
+      else:
         options = "%s,format=raw" % options
       # set cdrom 'if' type
       if boot_cdrom:
commit abfdf963d6727fd031d97864956511a30087dd11
Author: Christos Stavrakakis <[email protected]>
Date:   Tue Oct 29 12:09:33 2013 +0200

    Allow KVM to boot from HTTP
    
    New versions of KVM support booting from HTTP-hosted ISO images, via
    libcurl. This patch adds a proper check to allow defining either a sane,
    absolute path or an HTTP URL as an iso image path.
    
    Remove "format=raw" from the cdrom device options when iso_image starts
    with "(https?|ftps?)://", to allow latest KVM versions (qemu-kvm) to boot
    from HTTP-hosted iso images. Added FILE_OR_URL check to allow booting
    from HTTP (kvm).
    
    Signed-off-by: Apollon Oikonomopoulos <[email protected]>
    Signed-off-by: Christos Stavrakakis <[email protected]>

diff --git a/lib/hypervisor/hv_base.py b/lib/hypervisor/hv_base.py
index c723a4d..a6e0b46 100644
--- a/lib/hypervisor/hv_base.py
+++ b/lib/hypervisor/hv_base.py
@@ -83,6 +83,13 @@ def _IsMultiCpuMaskWellFormed(cpu_mask):
 _FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
                os.path.isfile, "not found or not a file")
 
+# must be a file or a URL
+_FILE_OR_URL_CHECK = (utils.IsNormAbsPathOrURL,
+                      "must be an absolute normalized path or a URL",
+                      lambda x: os.path.isfile(x) or
+                      re.match(r'(https?|ftps?)://', x),
+                      "not found or not a file or URL")
+
 # must be a directory
 _DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
               os.path.isdir, "not found or not a directory")
@@ -108,6 +115,8 @@ _NONNEGATIVE_INT_CHECK = (lambda x: x >= 0, "cannot be negative", None, None)
 # nice wrappers for users
 REQ_FILE_CHECK = (True, ) + _FILE_CHECK
 OPT_FILE_CHECK = (False, ) + _FILE_CHECK
+REQ_FILE_OR_URL_CHECK = (True, ) + _FILE_OR_URL_CHECK
+OPT_FILE_OR_URL_CHECK = (False, ) + _FILE_OR_URL_CHECK
 REQ_DIR_CHECK = (True, ) + _DIR_CHECK
 OPT_DIR_CHECK = (False, ) + _DIR_CHECK
 REQ_NET_PORT_CHECK = (True, ) + _NET_PORT_CHECK
diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py
index 54bd557..01a3c52 100644
--- a/lib/hypervisor/hv_kvm.py
+++ b/lib/hypervisor/hv_kvm.py
@@ -477,7 +477,7 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     constants.HV_KVM_SPICE_TLS_CIPHERS: hv_base.NO_CHECK,
     constants.HV_KVM_SPICE_USE_VDAGENT: hv_base.NO_CHECK,
     constants.HV_KVM_FLOPPY_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
-    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
+    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_OR_URL_CHECK,
     constants.HV_KVM_CDROM2_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
     constants.HV_BOOT_ORDER:
       hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
@@ -1130,7 +1130,9 @@ class KVMHypervisor(hv_base.BaseHypervisor):
 
     iso_image = hvp[constants.HV_CDROM_IMAGE_PATH]
     if iso_image:
-      options = ",format=raw,media=cdrom"
+      options = ",media=cdrom"
+      if not re.match(r'(https?|ftps?)://', iso_image):
+        options = "%s,format=raw" % options
       # set cdrom 'if' type
       if boot_cdrom:
         actual_cdrom_type = constants.HT_DISK_IDE
diff --git a/lib/utils/io.py b/lib/utils/io.py
index f9675a8..47c7749 100644
--- a/lib/utils/io.py
+++ b/lib/utils/io.py
@@ -23,6 +23,7 @@
 """
 
 import os
+import re
 import logging
 import shutil
 import tempfile
@@ -667,6 +668,13 @@ def IsBelowDir(root, other_path):
   return os.path.commonprefix([prepared_root, norm_other]) == prepared_root
 
 
+def IsNormAbsPathOrURL(path):
+  """Check whether a path is absolute and normalized, or an HTTP URL.
+
+  """
+  return IsNormAbsPath(path) or re.match(r'(https?|ftps?)://', path)
+
+
 def PathJoin(*args):
   """Safe-join a list of path components.
 

Reply via email to