# HG changeset patch
# Parent 659ee31faec91ac543578db7c9b2849fb7367419

xenpaging: xend: start xenpaging via config option

Start xenpaging via config option.

TODO: add libxl support
TODO: parse config values like 42K, 42M, 42G, 42%

Signed-off-by: Olaf Hering <olaf@aepfle.de>

---
v5:
  use actmem=, xenpaging_file=, xenpaging_extra=
  add xm mem-swap-target

v4:
  add config option for pagefile directory
  add config option to enable debug
  add config option to set policy mru_size
  fail if chdir fails
  force self.xenpaging* variables to be strings because a xm new may turn some
  of them into type int and later os.execve fails with a TypeError

v3:
  decouple create/destroycreateXenPaging from _create/_removeDevices
  init xenpaging variable to 0 if xenpaging is not in config file to
  avoid string None coming from sxp file

v2:
  unlink logfile instead of truncating it.
  allows hardlinking for further inspection

---
 tools/examples/xmexample.hvm            |    9 +++
 tools/python/README.XendConfig          |    3 +
 tools/python/README.sxpcfg              |    3 +
 tools/python/xen/xend/XendConfig.py     |    9 +++
 tools/python/xen/xend/XendDomain.py     |   15 +++++
 tools/python/xen/xend/XendDomainInfo.py |   23 ++++++++
 tools/python/xen/xend/image.py          |   85 ++++++++++++++++++++++++++++++++
 tools/python/xen/xm/create.py           |   15 +++++
 tools/python/xen/xm/main.py             |   14 +++++
 tools/python/xen/xm/xenapi_create.py    |    3 +
 10 files changed, 179 insertions(+)

Index: xen-4.1.6-testing/tools/examples/xmexample.hvm
===================================================================
--- xen-4.1.6-testing.orig/tools/examples/xmexample.hvm
+++ xen-4.1.6-testing/tools/examples/xmexample.hvm
@@ -142,6 +142,15 @@ disk = [ 'file:/var/lib/xen/images/disk.
 # Device Model to be used
 device_model = 'qemu-dm'
 
+# the amount of memory in MiB for the guest
+#actmem=42
+
+# Optional: guest page file
+#xenpaging_file="/var/lib/xen/xenpaging/<domain_name>.<domaind_id>.paging"
+
+# Optional: extra cmdline options for xenpaging
+#xenpaging_extra=[ 'string', 'string' ]
+
 #-----------------------------------------------------------------------------
 # boot on floppy (a), hard disk (c), Network (n) or CD-ROM (d) 
 # default: hard disk, cd-rom, floppy
Index: xen-4.1.6-testing/tools/python/README.XendConfig
===================================================================
--- xen-4.1.6-testing.orig/tools/python/README.XendConfig
+++ xen-4.1.6-testing/tools/python/README.XendConfig
@@ -120,6 +120,9 @@ otherConfig
                                 image.vncdisplay
                                 image.vncunused
                                 image.hvm.device_model
+                                image.hvm.actmem
+                                image.hvm.xenpaging_file
+                                image.hvm.xenpaging_extra
                                 image.hvm.display
                                 image.hvm.xauthority
                                 image.hvm.vncconsole
Index: xen-4.1.6-testing/tools/python/README.sxpcfg
===================================================================
--- xen-4.1.6-testing.orig/tools/python/README.sxpcfg
+++ xen-4.1.6-testing/tools/python/README.sxpcfg
@@ -51,6 +51,9 @@ image
   - vncunused
   (HVM)
   - device_model
+  - actmem
+  - xenpaging_file
+  - xenpaging_extra
   - display
   - xauthority
   - vncconsole
Index: xen-4.1.6-testing/tools/python/xen/xend/XendConfig.py
===================================================================
--- xen-4.1.6-testing.orig/tools/python/xen/xend/XendConfig.py
+++ xen-4.1.6-testing/tools/python/xen/xend/XendConfig.py
@@ -147,6 +147,9 @@ XENAPI_PLATFORM_CFG_TYPES = {
     'apic': int,
     'boot': str,
     'device_model': str,
+    'actmem': str,
+    'xenpaging_file': str,
+    'xenpaging_extra': str,
     'loader': str,
     'display' : str,
     'fda': str,
@@ -516,6 +519,12 @@ class XendConfig(dict):
             self['platform']['nomigrate'] = 0
 
         if self.is_hvm():
+            if 'actmem' not in self['platform']:
+                self['platform']['actmem'] = "0"
+            if 'xenpaging_file' not in self['platform']:
+                self['platform']['xenpaging_file'] = ""
+            if 'xenpaging_extra' not in self['platform']:
+                self['platform']['xenpaging_extra'] = []
             if 'timer_mode' not in self['platform']:
                 self['platform']['timer_mode'] = 1
             if 'extid' in self['platform'] and int(self['platform']['extid']) == 1:
Index: xen-4.1.6-testing/tools/python/xen/xend/XendDomain.py
===================================================================
--- xen-4.1.6-testing.orig/tools/python/xen/xend/XendDomain.py
+++ xen-4.1.6-testing/tools/python/xen/xend/XendDomain.py
@@ -2036,6 +2036,21 @@ class XendDomain:
             raise XendInvalidDomain(str(domid))
         dominfo.setMigrateConstraints(max_iters, max_factor, min_remaining, abort_if_busy, log_save_progress)
 
+    def domain_swaptarget_set(self, domid, mem):
+        """Set the memory limit for a domain.
+
+        @param domid: Domain ID or Name
+        @type domid: int or string.
+        @param mem: memory limit (in MiB)
+        @type mem: int
+        @raise XendError: fail to set memory
+        @rtype: 0
+        """
+        dominfo = self.domain_lookup_nr(domid)
+        if not dominfo:
+            raise XendInvalidDomain(str(domid))
+        dominfo.setSwapTarget(mem)
+
     def domain_maxmem_set(self, domid, mem):
         """Set the memory limit for a domain.
 
Index: xen-4.1.6-testing/tools/python/xen/xend/XendDomainInfo.py
===================================================================
--- xen-4.1.6-testing.orig/tools/python/xen/xend/XendDomainInfo.py
+++ xen-4.1.6-testing/tools/python/xen/xend/XendDomainInfo.py
@@ -1529,6 +1529,17 @@ class XendDomainInfo:
                 break
         xen.xend.XendDomain.instance().managed_config_save(self)
 
+    def setSwapTarget(self, target):
+        """Set the swap target of this domain.
+        @param target: In MiB.
+        """
+        log.debug("Setting swap target of domain %s (%s) to %d MiB.",
+                  self.info['name_label'], str(self.domid), target)
+
+        if self.domid > 0:
+            self.storeDom("memory/target-tot_pages", target * 1024)
+            self.info['platform']['actmem'] = str(target)
+
     def setMemoryTarget(self, target):
         """Set the memory target of this domain.
         @param target: In MiB.
@@ -2327,6 +2338,8 @@ class XendDomainInfo:
                  self.info['name_label'], self.domid, self.info['uuid'],
                  new_name, new_uuid)
         self._unwatchVm()
+        if self.image:
+            self.image.destroyXenPaging()
         self._releaseDevices()
         # Remove existing vm node in xenstore
         self._removeVm()
@@ -3005,6 +3018,9 @@ class XendDomainInfo:
 
             self._createDevices()
 
+            if self.image:
+                self.image.createXenPaging()
+
             self.image.cleanupTmpImages()
 
             self.info['start_time'] = time.time()
@@ -3029,6 +3045,8 @@ class XendDomainInfo:
         self.refresh_shutdown_lock.acquire()
         try:
             self.unwatchShutdown()
+            if self.image:
+                self.image.destroyXenPaging()
             self._releaseDevices()
             bootloader_tidy(self)
 
@@ -3113,6 +3131,7 @@ class XendDomainInfo:
         self.image = image.create(self, self.info)
         if self.image:
             self._createDevices(True)
+            self.image.createXenPaging()
         self.console_port = console_port
         self._storeDomDetails()
         self._registerWatches()
@@ -3254,6 +3273,8 @@ class XendDomainInfo:
             # could also fetch a parsed note from xenstore
             fast = self.info.get_notes().get('SUSPEND_CANCEL') and 1 or 0
             if not fast:
+                if self.image:
+                    self.image.destroyXenPaging()
                 self._releaseDevices()
                 self.testDeviceComplete()
                 self.testvifsComplete()
@@ -3269,6 +3290,8 @@ class XendDomainInfo:
                 self._storeDomDetails()
 
                 self._createDevices()
+                if self.image:
+                    self.image.createXenPaging()
                 log.debug("XendDomainInfo.resumeDomain: devices created")
 
             xc.domain_resume(self.domid, fast)
Index: xen-4.1.6-testing/tools/python/xen/xend/image.py
===================================================================
--- xen-4.1.6-testing.orig/tools/python/xen/xend/image.py
+++ xen-4.1.6-testing/tools/python/xen/xend/image.py
@@ -122,6 +122,10 @@ class ImageHandler:
         self.vm.permissionsVm("image/cmdline", { 'dom': self.vm.getDomid(), 'read': True } )
 
         self.device_model = vmConfig['platform'].get('device_model')
+        self.actmem = str(vmConfig['platform'].get('actmem'))
+        self.xenpaging_file = str(vmConfig['platform'].get('xenpaging_file'))
+        self.xenpaging_extra = vmConfig['platform'].get('xenpaging_extra')
+        self.xenpaging_pid = None
 
         self.display = vmConfig['platform'].get('display')
         self.xauthority = vmConfig['platform'].get('xauthority')
@@ -392,6 +396,87 @@ class ImageHandler:
         sentinel_fifos_inuse[sentinel_path_fifo] = 1
         self.sentinel_path_fifo = sentinel_path_fifo
 
+    def createXenPaging(self):
+        if not self.vm.info.is_hvm():
+            return
+        if self.actmem == "0":
+            return
+        if self.xenpaging_pid:
+            return
+        xenpaging_bin = auxbin.pathTo("xenpaging")
+        args = [xenpaging_bin]
+        args = args + ([ "-f", "/var/lib/xen/xenpaging/%s.%d.paging" % (str(self.vm.info['name_label']), self.vm.getDomid())])
+        if self.xenpaging_extra:
+            args = args + (self.xenpaging_extra)
+        args = args + ([ "-d", "%d" % self.vm.getDomid()])
+        self.xenpaging_logfile = "/var/log/xen/xenpaging-%s.log" %  str(self.vm.info['name_label'])
+        logfile_mode = os.O_WRONLY|os.O_CREAT|os.O_APPEND|os.O_TRUNC
+        null = os.open("/dev/null", os.O_RDONLY)
+        try:
+            os.unlink(self.xenpaging_logfile)
+        except:
+            pass
+        logfd = os.open(self.xenpaging_logfile, logfile_mode, 0644)
+        sys.stderr.flush()
+        contract = osdep.prefork("%s:%d" % (self.vm.getName(), self.vm.getDomid()))
+        xenpaging_pid = os.fork()
+        if xenpaging_pid == 0: #child
+            try:
+                osdep.postfork(contract)
+                os.dup2(null, 0)
+                os.dup2(logfd, 1)
+                os.dup2(logfd, 2)
+                try:
+                    env = dict(os.environ)
+                    log.info("starting %s" % args)
+                    os.execve(xenpaging_bin, args, env)
+                except Exception, e:
+                    log.warn('failed to execute xenpaging: %s' % utils.exception_string(e))
+                    os._exit(126)
+            except:
+                log.warn("starting xenpaging failed")
+                os._exit(127)
+        else:
+            osdep.postfork(contract, abandon=True)
+            self.xenpaging_pid = xenpaging_pid
+            os.close(null)
+            os.close(logfd)
+        self.vm.storeDom("xenpaging/xenpaging-pid", self.xenpaging_pid)
+        self.vm.storeDom("memory/target-tot_pages", int(self.actmem) * 1024)
+
+    def destroyXenPaging(self):
+        if self.actmem == "0":
+            return
+        if self.xenpaging_pid:
+            try:
+                os.kill(self.xenpaging_pid, signal.SIGHUP)
+            except OSError, exn:
+                log.exception(exn)
+            for i in xrange(100):
+                try:
+                    (p, rv) = os.waitpid(self.xenpaging_pid, os.WNOHANG)
+                    if p == self.xenpaging_pid:
+                        break
+                except OSError:
+                    # This is expected if Xend has been restarted within
+                    # the life of this domain.  In this case, we can kill
+                    # the process, but we can't wait for it because it's
+                    # not our child. We continue this loop, and after it is
+                    # terminated make really sure the process is going away
+                    # (SIGKILL).
+                    pass
+                time.sleep(0.1)
+            else:
+                log.warning("xenpaging %d took more than 10s "
+                            "to terminate: sending SIGKILL" % self.xenpaging_pid)
+                try:
+                    os.kill(self.xenpaging_pid, signal.SIGKILL)
+                    os.waitpid(self.xenpaging_pid, 0)
+                except OSError:
+                    # This happens if the process doesn't exist.
+                    pass
+        self.xenpaging_pid = None
+
     def createDeviceModel(self, restore = False):
         if self.device_model is None:
             return
Index: xen-4.1.6-testing/tools/python/xen/xm/create.py
===================================================================
--- xen-4.1.6-testing.orig/tools/python/xen/xm/create.py
+++ xen-4.1.6-testing/tools/python/xen/xm/create.py
@@ -495,6 +495,18 @@ gopts.var('nfs_root', val="PATH",
           fn=set_value, default=None,
           use="Set the path of the root NFS directory.")
 
+gopts.var('actmem', val='NUM',
+          fn=set_value, default='0',
+          use="Number of pages to swap.")
+
+gopts.var('xenpaging_file', val='PATH',
+          fn=set_value, default=None,
+          use="pagefile to use (optional)")
+
+gopts.var('xenpaging_extra', val='string1,string2',
+          fn=append_value, default=[],
+          use="additional args for xenpaging (optional)")
+
 gopts.var('device_model', val='FILE',
           fn=set_value, default=None,
           use="Path to device model program.")
@@ -1095,6 +1107,9 @@ def configure_hvm(config_image, vals):
     args = [ 'acpi', 'apic',
              'boot',
              'cpuid', 'cpuid_check',
+             'actmem',
+             'xenpaging_file',
+             'xenpaging_extra',
              'device_model', 'display',
              'fda', 'fdb',
              'gfx_passthru', 'guest_os_type',
Index: xen-4.1.6-testing/tools/python/xen/xm/main.py
===================================================================
--- xen-4.1.6-testing.orig/tools/python/xen/xm/main.py
+++ xen-4.1.6-testing/tools/python/xen/xm/main.py
@@ -115,6 +115,8 @@ SUBCOMMAND_HELP = {
                      'Set the maximum amount reservation for a domain.'),
     'mem-set'     : ('<Domain> <Mem>',
                      'Set the current memory usage for a domain.'),
+    'mem-swap-target' : ('<Domain> <Mem>',
+                     'Set the memory usage for a domain.'),
     'migrate'     : ('<Domain> <Host>',
                      'Migrate a domain to another machine.'),
     'pause'       : ('<Domain>', 'Pause execution of a domain.'),
@@ -1667,6 +1669,17 @@ def xm_mem_set(args):
         mem_target = int_unit(args[1], 'm')
         server.xend.domain.setMemoryTarget(dom, mem_target)
 
+def xm_mem_swap_target(args):
+    arg_check(args, "mem-swap-target", 2)
+
+    dom = args[0]
+
+    if serverType == SERVER_XEN_API:
+        err("xenapi not supported")
+    else:
+        swap_target = int_unit(args[1], 'm')
+        server.xend.domain.swaptarget_set(dom, swap_target)
+
 def xm_usb_add(args):
     arg_check(args, "usb-add", 2)
     server.xend.domain.usb_add(args[0],args[1])
@@ -3926,6 +3939,7 @@ commands = {
     # memory commands
     "mem-max": xm_mem_max,
     "mem-set": xm_mem_set,
+    "mem-swap-target": xm_mem_swap_target,
     # cpu commands
     "vcpu-pin": xm_vcpu_pin,
     "vcpu-list": xm_vcpu_list,
Index: xen-4.1.6-testing/tools/python/xen/xm/xenapi_create.py
===================================================================
--- xen-4.1.6-testing.orig/tools/python/xen/xm/xenapi_create.py
+++ xen-4.1.6-testing/tools/python/xen/xm/xenapi_create.py
@@ -1085,6 +1085,9 @@ class sxp2xml:
             'acpi',
             'apic',
             'boot',
+            'actmem',
+            'xenpaging_file',
+            'xenpaging_extra',
             'device_model',
             'loader',
             'fda',
