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

[PATCH v1 1/6] tools/migration: introduce PAGE_DATA_LZ4 stream record type



Allocate a new migration stream record type REC_TYPE_PAGE_DATA_LZ4
(0x00000013) to transmit PAGE_DATA when payload is LZ4-compressed. The
record use the same xc_sr_rec_page_data_header as REC_TYPE_PAGE_DATA
but the page data section type changes from page_data to data_lz4.

Signed-off-by: Marcus Granado <marcus.granado@xxxxxxxxxx>
---
 docs/specs/libxc-migration-stream.pandoc | 71 ++++++++++++++++-
 tools/libs/guest/xg_sr_common.c          |  4 +
 tools/libs/guest/xg_sr_stream_format.h   | 13 ++++
 tools/python/xen/migration/libxc.py      | 90 ++++++++++++++++++++-
 tools/python/xen/migration/tests.py      | 99 +++++++++++++++++++++++-
 5 files changed, 271 insertions(+), 6 deletions(-)

diff --git a/docs/specs/libxc-migration-stream.pandoc 
b/docs/specs/libxc-migration-stream.pandoc
index 1319ce1f1e..7469c95139 100644
--- a/docs/specs/libxc-migration-stream.pandoc
+++ b/docs/specs/libxc-migration-stream.pandoc
@@ -3,7 +3,8 @@
   Andrew Cooper <<andrew.cooper3@xxxxxxxxxx>>
   Wen Congyang <<wency@xxxxxxxxxxxxxx>>
   Yang Hongyang <<hongyang.yang@xxxxxxxxxxxx>>
-% Revision 3
+  Marcus Granado <<marcus.granado@xxxxxxxxxx>>
+% Revision 4
 
 Introduction
 ============
@@ -39,8 +40,6 @@ Not Yet Included
 The following features are not yet fully specified and will be
 included in a future draft.
 
-* Page data compression.
-
 * ARM
 
 
@@ -233,7 +232,9 @@ type         0x00000000: END
 
              0x00000012: X86_MSR_POLICY
 
-             0x00000013 - 0x7FFFFFFF: Reserved for future _mandatory_
+             0x00000013: PAGE_DATA_LZ4
+
+             0x00000014 - 0x7FFFFFFF: Reserved for future _mandatory_
              records.
 
              0x80000000 - 0xFFFFFFFF: Reserved for future _optional_
@@ -359,6 +360,68 @@ tail.
 
 \clearpage
 
+PAGE_DATA_LZ4
+-------------
+
+A PAGE_DATA_LZ4 record carries exactly the same information as a
+PAGE_DATA record, but with the page contents LZ4-compressed.  The saver
+may emit it in place of a PAGE_DATA record when LZ4 compression has been
+requested.
+
+     0     1     2     3     4     5     6     7 octet
+    +-----------------------+-------------------------+
+    | count (C)             | (reserved)              |
+    +-----------------------+-------------------------+
+    | pfn[0]                                          |
+    +-------------------------------------------------+
+    ...
+    +-------------------------------------------------+
+    | pfn[C-1]                                        |
+    +-----------+-------------------------------------+
+    | clen[0]   | page_data_lz4[0]...                 |
+    +-----------+-------------------------------------+
+    ...
+    +-----------+-------------------------------------+
+    | clen[N-1] | page_data_lz4[N-1]...               |
+    ...
+    +-------------------------------------------------+
+
+--------------------------------------------------------------------
+Field       Description
+----------- --------------------------------------------------------
+count       Number of pages described in this record.
+
+pfn         An array of count PFNs and their types, with the same
+            layout and page types as in a PAGE_DATA record.
+
+data_lz4    The compressed page contents, as one sub-block per page
+            set as present in the pfn array, in pfn-array order (i.e.
+            N sub-blocks, with N as in a PAGE_DATA record: N <= C).
+            Each sub-block is a `uint16` little-endian length `clen`
+            followed by either an LZ4 block or a raw page.  When
+            `clen > 0`, page_data_lz4 is `clen` octets of a raw LZ4
+            block (per the LZ4 block format) whose decompressed output
+            is one page_size page.  When `clen == 0`, page_data_lz4 is
+            page_size octets of a raw, uncompressed page.
+            A compressed `clen` is at most page_size - 1, occupying
+            only the low 12 bits; the top 4 bits are reserved for
+            future use and must be 0.
+--------------------------------------------------------------------
+
+The `count` (C) and `pfn` fields are identical in meaning and
+constraints to those of a PAGE_DATA record, and N (the number of
+present pages, N <= C) is as defined there.  Unlike PAGE_DATA, a
+PAGE_DATA_LZ4 record always has N >= 1 (at least one sub-block):
+the saver emits the LZ4 variant only when there is page data to
+compress, and a restoring side rejects a PAGE_DATA_LZ4 record with
+N == 0.  When a batch has no present pages (all pfns of invalid
+types) the saver emits a plain PAGE_DATA record instead.
+
+PAGE_DATA_LZ4 is a _mandatory_ record: a restoring side that does not
+support it must fail the migration.
+
+\clearpage
+
 X86_PV_INFO
 -----------
 
diff --git a/tools/libs/guest/xg_sr_common.c b/tools/libs/guest/xg_sr_common.c
index 9b2782b5cf..8b0a59fa4c 100644
--- a/tools/libs/guest/xg_sr_common.c
+++ b/tools/libs/guest/xg_sr_common.c
@@ -39,6 +39,7 @@ static const char *const mandatory_rec_types[] =
     [REC_TYPE_STATIC_DATA_END]              = "Static data end",
     [REC_TYPE_X86_CPUID_POLICY]             = "x86 CPUID policy",
     [REC_TYPE_X86_MSR_POLICY]               = "x86 MSR policy",
+    [REC_TYPE_PAGE_DATA_LZ4]                = "Page data (lz4)",
 };
 
 const char *rec_type_to_str(uint32_t type)
@@ -154,6 +155,9 @@ static void __attribute__((unused)) build_assertions(void)
     BUILD_BUG_ON(sizeof(struct xc_sr_rec_x86_tsc_info)      != 24);
     BUILD_BUG_ON(sizeof(struct xc_sr_rec_hvm_params_entry)  != 16);
     BUILD_BUG_ON(sizeof(struct xc_sr_rec_hvm_params)        != 8);
+
+    /* clen's low bits hold the compressed length; reserved bits stay clear */
+    BUILD_BUG_ON((PAGE_SIZE - 1) & PAGE_DATA_LZ4_CLEN_RESERVED_MASK);
 }
 
 /*
diff --git a/tools/libs/guest/xg_sr_stream_format.h 
b/tools/libs/guest/xg_sr_stream_format.h
index 99dca5d490..e12833b2cd 100644
--- a/tools/libs/guest/xg_sr_stream_format.h
+++ b/tools/libs/guest/xg_sr_stream_format.h
@@ -76,6 +76,7 @@ struct xc_sr_rhdr
 #define REC_TYPE_STATIC_DATA_END            0x00000010U
 #define REC_TYPE_X86_CPUID_POLICY           0x00000011U
 #define REC_TYPE_X86_MSR_POLICY             0x00000012U
+#define REC_TYPE_PAGE_DATA_LZ4              0x00000013U
 
 #define REC_TYPE_OPTIONAL             0x80000000U
 
@@ -90,6 +91,18 @@ struct xc_sr_rec_page_data_header
 #define PAGE_DATA_PFN_MASK  0x000fffffffffffffULL
 #define PAGE_DATA_TYPE_MASK 0xf000000000000000ULL
 
+/*
+ * PAGE_DATA_LZ4 payload: one sub-block per present page (pfn order), each a
+ * uint16 LE length clen then clen octets.  clen == PAGE_DATA_LZ4_CLEN_RAW
+ * means a raw PAGE_SIZE page follows.  A compressed length is at most
+ * PAGE_SIZE - 1, so it occupies only the low 12 bits; the top 4 bits
+ * (PAGE_DATA_LZ4_CLEN_RESERVED_MASK) are reserved for future use and must
+ * be 0.
+ */
+#define PAGE_DATA_LZ4_CLEN_SIZE          (sizeof(uint16_t))
+#define PAGE_DATA_LZ4_CLEN_RAW           0x0000U
+#define PAGE_DATA_LZ4_CLEN_RESERVED_MASK 0xF000U
+
 /* X86_PV_INFO */
 struct xc_sr_rec_x86_pv_info
 {
diff --git a/tools/python/xen/migration/libxc.py 
b/tools/python/xen/migration/libxc.py
index e52e632cb1..0b161749ea 100644
--- a/tools/python/xen/migration/libxc.py
+++ b/tools/python/xen/migration/libxc.py
@@ -58,6 +58,7 @@ REC_TYPE_checkpoint_dirty_pfn_list  = 0x0000000f
 REC_TYPE_static_data_end            = 0x00000010
 REC_TYPE_x86_cpuid_policy           = 0x00000011
 REC_TYPE_x86_msr_policy             = 0x00000012
+REC_TYPE_page_data_lz4              = 0x00000013
 
 rec_type_to_str = {
     REC_TYPE_end                        : "End",
@@ -79,6 +80,7 @@ rec_type_to_str = {
     REC_TYPE_static_data_end            : "Static data end",
     REC_TYPE_x86_cpuid_policy           : "x86 CPUID policy",
     REC_TYPE_x86_msr_policy             : "x86 MSR policy",
+    REC_TYPE_page_data_lz4              : "Page data (lz4)",
 }
 
 # page_data
@@ -101,6 +103,10 @@ PAGE_DATA_TYPE_BROKEN        = (0xd << 
PAGE_DATA_TYPE_SHIFT) # Broken
 PAGE_DATA_TYPE_XALLOC        = (0xe << PAGE_DATA_TYPE_SHIFT) # Allocate-only
 PAGE_DATA_TYPE_XTAB          = (0xf << PAGE_DATA_TYPE_SHIFT) # Invalid
 
+# PAGE_DATA_LZ4 per-page sub-block: uint16 LE clen; top 4 bits reserved (must
+# be 0); see tools/libs/guest/xg_sr_stream_format.h.
+PAGE_DATA_LZ4_CLEN_RESERVED_MASK = 0xf000
+
 # x86_pv_info
 X86_PV_INFO_FORMAT        = "BBHI"
 
@@ -217,7 +223,7 @@ class VerifyLibxc(VerifyBase):
         contentsz = (length + 7) & ~7
         content = self.rdexact(contentsz)
 
-        if rtype != REC_TYPE_page_data:
+        if rtype not in (REC_TYPE_page_data, REC_TYPE_page_data_lz4):
 
             if self.squashed_pagedata_records > 0:
                 self.info("Squashed %d Page Data records together" %
@@ -476,6 +482,85 @@ class VerifyLibxc(VerifyBase):
                               (contentsz, sz))
 
 
+    def verify_record_page_data_lz4(self, content):
+        """ Page Data (lz4) record """
+        # Structure-only check: this verifier does not link liblz4 and so does
+        # not decompress data_lz4.  Decompression integrity (e.g. a corrupt
+        # block) is enforced by the C restore (LZ4_decompress_safe,
+        # xg_sr_restore.c) and by the end-to-end migration tests, not here.
+        minsz = calcsize(PAGE_DATA_FORMAT)
+
+        if len(content) <= minsz:
+            raise RecordError(
+                "PAGE_DATA_LZ4 record must be at least %d bytes long"
+                % (minsz, ))
+
+        count, res1 = unpack(PAGE_DATA_FORMAT, content[:minsz])
+
+        if res1 != 0:
+            raise StreamError(
+                "Reserved bits set in PAGE_DATA_LZ4 record 0x%04x" % (res1, ))
+
+        pfnsz = count * 8
+        if (len(content) - minsz) < pfnsz:
+            raise RecordError(
+                "PAGE_DATA_LZ4 record must contain a pfn record for each 
count")
+
+        pfns = list(unpack("=%dQ" % (count, ), content[minsz:minsz + pfnsz]))
+
+        nr_pages = 0
+        for idx, pfn in enumerate(pfns):
+
+            if pfn & PAGE_DATA_PFN_RESZ_MASK:
+                raise RecordError("Reserved bits set in pfn[%d]: 0x%016x" %
+                                  (idx, pfn & PAGE_DATA_PFN_RESZ_MASK))
+
+            if pfn >> PAGE_DATA_TYPE_SHIFT in (5, 6, 7, 8):
+                raise RecordError("Invalid type value in pfn[%d]: 0x%016x" %
+                                  (idx, pfn & PAGE_DATA_TYPE_LTAB_MASK))
+
+            # We expect page data for each normal page or pagetable
+            if PAGE_DATA_TYPE_NOTAB <= (pfn & PAGE_DATA_TYPE_LTABTYPE_MASK) \
+                    <= PAGE_DATA_TYPE_L4TAB:
+                nr_pages += 1
+
+        if nr_pages == 0:
+            raise RecordError(
+                "PAGE_DATA_LZ4 record with no pages of data "
+                "(zero-payload LZ4 records are rejected on restore)")
+
+        # data_lz4 follows as nr_pages per-page sub-blocks, in pfn order: a
+        # uint16 LE length clen then clen octets (clen == 0 => a raw 4096-byte
+        # page; clen's top 4 bits reserved).  Walk the framing exactly as the C
+        # restore does (xg_sr_restore.c); this does not decompress.
+        data_lz4 = content[minsz + pfnsz:]
+        comprsz = len(data_lz4)
+        off = 0
+        for idx in range(nr_pages):
+            if off + 2 > comprsz:
+                raise RecordError(
+                    "PAGE_DATA_LZ4 sub-block[%d]: truncated clen prefix" % idx)
+            clen = data_lz4[off] | (data_lz4[off + 1] << 8)
+            off += 2
+
+            if clen & PAGE_DATA_LZ4_CLEN_RESERVED_MASK:
+                raise RecordError(
+                    "PAGE_DATA_LZ4 sub-block[%d]: reserved clen bits 0x%04x" %
+                    (idx, clen))
+
+            blocksz = 4096 if clen == 0 else clen
+            if blocksz > comprsz - off:
+                raise RecordError(
+                    "PAGE_DATA_LZ4 sub-block[%d]: overruns payload" % idx)
+            off += blocksz
+
+        if off != comprsz:
+            raise RecordError(
+                "PAGE_DATA_LZ4 record has %u trailing octets "
+                "after %u sub-blocks"
+                % (comprsz - off, nr_pages))
+
+
 record_verifiers = {
     REC_TYPE_end:
         VerifyLibxc.verify_record_end,
@@ -525,4 +610,7 @@ record_verifiers = {
         VerifyLibxc.verify_record_x86_cpuid_policy,
     REC_TYPE_x86_msr_policy:
         VerifyLibxc.verify_record_x86_msr_policy,
+
+    REC_TYPE_page_data_lz4:
+        VerifyLibxc.verify_record_page_data_lz4,
     }
diff --git a/tools/python/xen/migration/tests.py 
b/tools/python/xen/migration/tests.py
index fcf94b0bb2..9f8a4e2a0d 100644
--- a/tools/python/xen/migration/tests.py
+++ b/tools/python/xen/migration/tests.py
@@ -4,9 +4,10 @@
 Unit tests for migration v2 streams
 """
 
+import io
 import unittest
 
-from struct import calcsize
+from struct import calcsize, pack
 
 from xen.migration import libxc, libxl
 
@@ -31,6 +32,102 @@ class TestLibxc(unittest.TestCase):
             self.assertEqual(calcsize(fmt), sz)
 
 
+    def test_page_data_lz4(self):
+        """ REC_TYPE_PAGE_DATA_LZ4 verifier: accept valid per-page framing,
+        reject malformed framing """
+
+        v = libxc.VerifyLibxc(lambda *_: None, lambda *_: b"")
+
+        def hdr(count, res=0):
+            return pack(libxc.PAGE_DATA_FORMAT, count, res)
+
+        def clen(n):
+            return pack("<H", n)            # uint16 LE sub-block length prefix
+
+        NOTAB = libxc.PAGE_DATA_TYPE_NOTAB | 0x1   # a normal page (type 0)
+        XTAB = libxc.PAGE_DATA_TYPE_XTAB | 0x1     # invalid type, no data
+        PAGE = b"\x00" * 4096                       # a raw page (clen == 0)
+
+        # Positive: one normal page stored raw (clen == 0 => 4096 octets).
+        v.verify_record_page_data_lz4(
+            hdr(1) + pack("=Q", NOTAB) + clen(0) + PAGE)
+
+        # Positive: one normal page "compressed" (clen > 0 => clen octets).  
The
+        # bytes are arbitrary -- the verifier checks framing, not 
decompression.
+        v.verify_record_page_data_lz4(
+            hdr(1) + pack("=Q", NOTAB) + clen(4) + b"\xde\xad\xbe\xef")
+
+        # Positive: two pages, compressed then raw, back-to-back sub-blocks.
+        v.verify_record_page_data_lz4(
+            hdr(2) + pack("=QQ", NOTAB, NOTAB)
+            + clen(3) + b"\x01\x02\x03" + clen(0) + PAGE)
+
+        # Negative: no pages of data (all-invalid pfns) -- rejected to match 
the
+        # restore side, which rejects a zero-payload PAGE_DATA_LZ4 record.
+        with self.assertRaises(libxc.RecordError):
+            v.verify_record_page_data_lz4(hdr(1) + pack("=Q", XTAB))
+
+        # Negative: pages present but no payload at all (truncated clen 
prefix).
+        with self.assertRaises(libxc.RecordError):
+            v.verify_record_page_data_lz4(hdr(1) + pack("=Q", NOTAB))
+
+        # Negative: truncated clen prefix (1 octet where 2 are needed).
+        with self.assertRaises(libxc.RecordError):
+            v.verify_record_page_data_lz4(
+                hdr(1) + pack("=Q", NOTAB) + b"\x04")
+
+        # Negative: a compressed sub-block whose clen overruns the payload.
+        with self.assertRaises(libxc.RecordError):
+            v.verify_record_page_data_lz4(
+                hdr(1) + pack("=Q", NOTAB) + clen(8) + b"\x01\x02")
+
+        # Negative: a raw sub-block (clen == 0) without a full 4096-octet page.
+        with self.assertRaises(libxc.RecordError):
+            v.verify_record_page_data_lz4(
+                hdr(1) + pack("=Q", NOTAB) + clen(0) + b"\x00" * 100)
+
+        # Negative: reserved bits set in clen (top 4 bits).
+        with self.assertRaises(libxc.RecordError):
+            v.verify_record_page_data_lz4(
+                hdr(1) + pack("=Q", NOTAB) + clen(0x8000) + PAGE)
+
+        # Negative: trailing octets after the last sub-block.
+        with self.assertRaises(libxc.RecordError):
+            v.verify_record_page_data_lz4(
+                hdr(1) + pack("=Q", NOTAB) + clen(4) + b"\xde\xad\xbe\xef"
+                + b"\x99\x99")
+
+        # Negative: reserved bits set in the record header.
+        with self.assertRaises(libxc.StreamError):
+            v.verify_record_page_data_lz4(
+                hdr(1, 1) + pack("=Q", NOTAB) + clen(0) + PAGE)
+
+        # Negative: reserved bits set in a pfn.
+        with self.assertRaises(libxc.RecordError):
+            v.verify_record_page_data_lz4(
+                hdr(1) + pack("=Q", NOTAB | libxc.PAGE_DATA_PFN_RESZ_MASK)
+                + clen(0) + PAGE)
+
+
+    def test_page_data_lz4_dispatch(self):
+        """ REC_TYPE_PAGE_DATA_LZ4 routed through verify_record() end-to-end:
+        exercises the record_verifiers registry, the page-data squash branch,
+        and the trailing-padding check (which the direct-call test bypasses) 
"""
+        NOTAB = libxc.PAGE_DATA_TYPE_NOTAB | 0x1
+
+        # One normal page + a compressed sub-block, 8-byte-aligned body.
+        body = (pack(libxc.PAGE_DATA_FORMAT, 1, 0)
+                + pack("=Q", NOTAB)
+                + pack("<H", 4) + b"\xde\xad\xbe\xef")
+        padding = b"\x00" * ((8 - len(body) % 8) % 8)
+        record = (pack(libxc.RH_FORMAT, libxc.REC_TYPE_page_data_lz4, 
len(body))
+                  + body + padding)
+
+        stream = io.BytesIO(record)
+        v = libxc.VerifyLibxc(lambda *_: None, stream.read)
+        self.assertEqual(v.verify_record(), libxc.REC_TYPE_page_data_lz4)
+
+
 class TestLibxl(unittest.TestCase):
 
     def test_format_sizes(self):

base-commit: a7fd7d4cbd5e793d31d61c25e08526b330edd7f8
-- 
2.43.0




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.