Added basic support for SPICE
authorAndrea Spadaccini <spadaccio@google.com>
Wed, 10 Aug 2011 15:13:35 +0000 (16:13 +0100)
committerMichael Hanselmann <hansmi@google.com>
Fri, 12 Aug 2011 15:08:29 +0000 (17:08 +0200)
Implemented the following parameters:
- spice_bind
- spice_ip_version

Signed-off-by: Andrea Spadaccini <spadaccio@google.com>
Reviewed-by: Michael Hanselmann <hansmi@google.com>

lib/constants.py
lib/hypervisor/hv_kvm.py

index c0b8c3b..45c02db 100644 (file)
@@ -554,6 +554,7 @@ IP6_ADDRESS_LOCALHOST = "::1"
 IP6_ADDRESS_ANY = "::"
 IP4_VERSION = 4
 IP6_VERSION = 6
+VALID_IP_VERSIONS = frozenset([IP4_VERSION, IP6_VERSION])
 TCP_PING_TIMEOUT = 10
 GANETI_RUNAS = "root"
 DEFAULT_VG = "xenvg"
@@ -647,6 +648,9 @@ ENFORCEABLE_TYPES = frozenset([
   VTYPE_INT,
   ])
 
+# Constant representing that the user does not specify any IP version
+IFACE_NO_IP_VERSION_SPECIFIED = 0
+
 # HV parameter names (global namespace)
 HV_BOOT_ORDER = "boot_order"
 HV_CDROM_IMAGE_PATH = "cdrom_image_path"
@@ -660,6 +664,8 @@ HV_VNC_PASSWORD_FILE = "vnc_password_file"
 HV_VNC_TLS = "vnc_tls"
 HV_VNC_X509 = "vnc_x509_path"
 HV_VNC_X509_VERIFY = "vnc_x509_verify"
+HV_KVM_SPICE_BIND = "spice_bind"
+HV_KVM_SPICE_IP_VERSION = "spice_ip_version"
 HV_ACPI = "acpi"
 HV_PAE = "pae"
 HV_USE_BOOTLOADER = "use_bootloader"
@@ -703,6 +709,8 @@ HVS_PARAMETER_TYPES = {
   HV_VNC_TLS: VTYPE_BOOL,
   HV_VNC_X509: VTYPE_STRING,
   HV_VNC_X509_VERIFY: VTYPE_BOOL,
+  HV_KVM_SPICE_BIND: VTYPE_STRING,
+  HV_KVM_SPICE_IP_VERSION: VTYPE_INT,
   HV_ACPI: VTYPE_BOOL,
   HV_PAE: VTYPE_BOOL,
   HV_USE_BOOTLOADER: VTYPE_BOOL,
@@ -1278,6 +1286,8 @@ HVC_DEFAULTS = {
     HV_VNC_X509: "",
     HV_VNC_X509_VERIFY: False,
     HV_VNC_PASSWORD_FILE: "",
+    HV_KVM_SPICE_BIND: "",
+    HV_KVM_SPICE_IP_VERSION: IFACE_NO_IP_VERSION_SPECIFIED,
     HV_KVM_FLOPPY_IMAGE_PATH: "",
     HV_CDROM_IMAGE_PATH: "",
     HV_KVM_CDROM2_IMAGE_PATH: "",
index 6420cd5..579e563 100644 (file)
@@ -165,6 +165,12 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
     constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK,
     constants.HV_VNC_PASSWORD_FILE: hv_base.OPT_FILE_CHECK,
+    constants.HV_KVM_SPICE_BIND: hv_base.NO_CHECK, # will be checked later
+    constants.HV_KVM_SPICE_IP_VERSION:
+      (False, lambda x: (x == constants.IFACE_NO_IP_VERSION_SPECIFIED or
+                         x in constants.VALID_IP_VERSIONS),
+       "the SPICE IP version should be 4 or 6",
+       None, None),
     constants.HV_KVM_FLOPPY_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
     constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
     constants.HV_KVM_CDROM2_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
@@ -543,6 +549,8 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     boot_floppy = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_FLOPPY
     boot_network = hvp[constants.HV_BOOT_ORDER] == constants.HT_BO_NETWORK
 
+    self.ValidateParameters(hvp)
+
     if hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_ENABLED:
       kvm_cmd.extend(["-enable-kvm"])
     elif hvp[constants.HV_KVM_FLAG] == constants.HT_KVM_DISABLED:
@@ -712,6 +720,51 @@ class KVMHypervisor(hv_base.BaseHypervisor):
     else:
       kvm_cmd.extend(["-serial", "none"])
 
+    spice_bind = hvp[constants.HV_KVM_SPICE_BIND]
+    if spice_bind:
+      if netutils.IsValidInterface(spice_bind):
+        # The user specified a network interface, we have to figure out the IP
+        # address.
+        addresses = netutils.GetInterfaceIpAddresses(spice_bind)
+        spice_ip_version = hvp[constants.HV_KVM_SPICE_IP_VERSION]
+
+        # if the user specified an IP version and the interface does not
+        # have that kind of IP addresses, throw an exception
+        if spice_ip_version != constants.IFACE_NO_IP_VERSION_SPECIFIED:
+          if not addresses[spice_ip_version]:
+            raise errors.HypervisorError("spice: unable to get an IPv%s address"
+                                         " for %s" % (spice_ip_version,
+                                                      spice_bind))
+
+        # the user did not specify an IP version, we have to figure it out
+        elif (addresses[constants.IP4_VERSION] and
+              addresses[constants.IP6_VERSION]):
+          # we have both ipv4 and ipv6, let's use the cluster default IP
+          # version
+          cluster_family = ssconf.SimpleStore().GetPrimaryIPFamily()
+          spice_ip_version = netutils.IPAddress.GetVersionFromAddressFamily(
+              cluster_family)
+        elif addresses[constants.IP4_VERSION]:
+          spice_ip_version = constants.IP4_VERSION
+        else:
+          spice_ip_version = constants.IP6_VERSION
+
+        spice_address = addresses[spice_ip_version][0]
+
+      else:
+        # spice_bind is known to be a valid IP address, because
+        # ValidateParameters checked it.
+        spice_address = spice_bind
+
+      spice_arg = "addr=%s,ipv%s,port=%s" % (spice_address,
+                                             spice_ip_version,
+                                             instance.network_port)
+
+      spice_arg = "%s,disable-ticketing" % spice_arg
+
+      logging.info("KVM: SPICE will listen on port %s", instance.network_port)
+      kvm_cmd.extend(["-spice", spice_arg])
+
     if hvp[constants.HV_USE_LOCALTIME]:
       kvm_cmd.extend(["-localtime"])
 
@@ -1240,6 +1293,24 @@ class KVMHypervisor(hv_base.BaseHypervisor):
         raise errors.HypervisorError("Cannot have a security domain when the"
                                      " security model is 'none' or 'pool'")
 
+    spice_bind = hvparams[constants.HV_KVM_SPICE_BIND]
+    if spice_bind:
+      spice_ip_version = hvparams[constants.HV_KVM_SPICE_IP_VERSION]
+      if spice_ip_version != constants.IFACE_NO_IP_VERSION_SPECIFIED:
+        # if an IP version is specified, the spice_bind parameter must be an
+        # IP of that family
+        if (netutils.IP4Address.IsValid(spice_bind) and
+            spice_ip_version != constants.IP4_VERSION):
+          raise errors.HypervisorError("spice: got an IPv4 address (%s), but"
+                                       " the specified IP version is %s" %
+                                       (spice_bind, spice_ip_version))
+
+        if (netutils.IP6Address.IsValid(spice_bind) and
+            spice_ip_version != constants.IP6_VERSION):
+          raise errors.HypervisorError("spice: got an IPv6 address (%s), but"
+                                       " the specified IP version is %s" %
+                                       (spice_bind, spice_ip_version))
+
   @classmethod
   def ValidateParameters(cls, hvparams):
     """Check the given parameters for validity.
@@ -1260,6 +1331,28 @@ class KVMHypervisor(hv_base.BaseHypervisor):
         raise errors.HypervisorError("Unknown security domain user %s"
                                      % username)
 
+    spice_bind = hvparams[constants.HV_KVM_SPICE_BIND]
+    if spice_bind:
+      # only one of VNC and SPICE can be used currently.
+      if hvparams[constants.HV_VNC_BIND_ADDRESS]:
+        raise errors.HypervisorError("both SPICE and VNC are configured, but"
+                                     " only one of them can be used at a"
+                                     " given time.")
+
+      # KVM version should be >= 0.14.0
+      _, v_major, v_min, _ = cls._GetKVMVersion()
+      if (v_major, v_min) < (0, 14):
+        raise errors.HypervisorError("spice is configured, but it is not"
+                                     " available in versions of KVM < 0.14")
+
+      # if spice_bind is not an IP address, it must be a valid interface
+      bound_to_addr = (netutils.IP4Address.IsValid(spice_bind)
+                       or netutils.IP6Address.IsValid(spice_bind))
+      if not bound_to_addr and not netutils.IsValidInterface(spice_bind):
+        raise errors.HypervisorError("spice: the %s parameter must be either"
+                                     " a valid IP address or interface name" %
+                                     constants.HV_KVM_SPICE_BIND)
+
   @classmethod
   def PowercycleNode(cls):
     """KVM powercycle, just a wrapper over Linux powercycle.