From mboxrd@z Thu Jan 1 00:00:00 1970 From: Andrew Cooper Subject: [PATCH v4 16/29] tools/python: Conversion utility for legacy migration streams Date: Tue, 14 Jul 2015 11:59:31 +0100 Message-ID: <1436871584-6522-17-git-send-email-andrew.cooper3@citrix.com> References: <1436871584-6522-1-git-send-email-andrew.cooper3@citrix.com> Mime-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Return-path: In-Reply-To: <1436871584-6522-1-git-send-email-andrew.cooper3@citrix.com> List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Sender: xen-devel-bounces@lists.xen.org Errors-To: xen-devel-bounces@lists.xen.org To: Xen-devel Cc: Andrew Cooper , Ian Jackson , Wei Liu List-Id: xen-devel@lists.xenproject.org This utility will take a legacy stream as in input, and produce a v2 stream as an output. It is exec()'d by libxl to provide backwards compatibility. Signed-off-by: Andrew Cooper Acked-by: Ian Campbell CC: Ian Jackson CC: Wei Liu --- tools/python/Makefile | 4 + tools/python/scripts/convert-legacy-stream | 678 ++++++++++++++++++++++++++++ 2 files changed, 682 insertions(+) create mode 100755 tools/python/scripts/convert-legacy-stream diff --git a/tools/python/Makefile b/tools/python/Makefile index e933be8..df942a7 100644 --- a/tools/python/Makefile +++ b/tools/python/Makefile @@ -17,9 +17,13 @@ build: genwrap.py $(XEN_ROOT)/tools/libxl/libxl_types.idl \ .PHONY: install install: + $(INSTALL_DIR) $(DESTDIR)$(PRIVATE_BINDIR) + CC="$(CC)" CFLAGS="$(PY_CFLAGS)" $(PYTHON) setup.py install \ $(PYTHON_PREFIX_ARG) --root="$(DESTDIR)" --force + $(INSTALL_PROG) scripts/convert-legacy-stream $(DESTDIR)$(PRIVATE_BINDIR) + .PHONY: test test: export LD_LIBRARY_PATH=$$(readlink -f ../libxc):$$(readlink -f ../xenstore); $(PYTHON) test.py -b -u diff --git a/tools/python/scripts/convert-legacy-stream b/tools/python/scripts/convert-legacy-stream new file mode 100755 index 0000000..d54fa22 --- /dev/null +++ b/tools/python/scripts/convert-legacy-stream @@ -0,0 +1,678 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Convert a legacy migration stream to a v2 stream. +""" + +import sys +import os, os.path +import syslog +import traceback + +from struct import calcsize, unpack, pack + +from xen.migration import legacy, public, libxc, libxl, xl + +__version__ = 1 + +fin = None # Input file/fd +fout = None # Output file/fd +twidth = 0 # Legacy toolstack bitness (32 or 64) +pv = None # Boolean (pv or hvm) +qemu = True # Boolean - process qemu record? +log_to_syslog = False # Boolean - Log to syslog instead of stdout/err? +verbose = False # Boolean - Summarise stream contents + +def stream_read(_ = None): + """Read from the input""" + return fin.read(_) + +def stream_write(_): + """Write to the output""" + return fout.write(_) + +def info(msg): + """Info message, routed to appropriate destination""" + if verbose: + if log_to_syslog: + for line in msg.split("\n"): + syslog.syslog(syslog.LOG_INFO, line) + else: + print msg + +def err(msg): + """Error message, routed to appropriate destination""" + if log_to_syslog: + for line in msg.split("\n"): + syslog.syslog(syslog.LOG_ERR, line) + print >> sys.stderr, msg + +class StreamError(StandardError): + """Error with the incoming migration stream""" + pass + +class VM(object): + """Container of VM parameters""" + + def __init__(self, fmt): + # Common + self.p2m_size = 0 + + # PV + self.max_vcpu_id = 0 + self.online_vcpu_map = [] + self.width = 0 + self.levels = 0 + self.basic_len = 0 + self.extd = False + self.xsave_len = 0 + + # libxl + self.libxl = fmt == "libxl" + self.xenstore = [] # Deferred "toolstack" records + +def write_libxc_ihdr(): + stream_write(pack(libxc.IHDR_FORMAT, + libxc.IHDR_MARKER, # Marker + libxc.IHDR_IDENT, # Ident + libxc.IHDR_VERSION, # Version + libxc.IHDR_OPT_LE, # Options + 0, 0)) # Reserved + +def write_libxc_dhdr(): + if pv: + dtype = libxc.DHDR_TYPE_x86_pv + else: + dtype = libxc.DHDR_TYPE_x86_hvm + + stream_write(pack(libxc.DHDR_FORMAT, + dtype, # Type + 12, # Page size + 0, # Reserved + 0, # Xen major (converted) + __version__)) # Xen minor (converted) + +def write_libxl_hdr(): + stream_write(pack(libxl.HDR_FORMAT, + libxl.HDR_IDENT, # Ident + libxl.HDR_VERSION, # Version 2 + libxl.HDR_OPT_LE | # Options + libxl.HDR_OPT_LEGACY # Little Endian and Legacy + )) + +def write_record(rt, *argl): + alldata = ''.join(argl) + length = len(alldata) + + record = pack(libxc.RH_FORMAT, rt, length) + alldata + plen = (8 - (length & 7)) & 7 + record += '\x00' * plen + + stream_write(record) + +def write_libxc_pv_info(vm): + write_record(libxc.REC_TYPE_x86_pv_info, + pack(libxc.X86_PV_INFO_FORMAT, + vm.width, vm.levels, 0, 0)) + +def write_libxc_pv_p2m_frames(vm, pfns): + write_record(libxc.REC_TYPE_x86_pv_p2m_frames, + pack(libxc.X86_PV_P2M_FRAMES_FORMAT, + 0, vm.p2m_size - 1), + pack("Q" * len(pfns), *pfns)) + +def write_libxc_pv_vcpu_basic(vcpu_id, data): + write_record(libxc.REC_TYPE_x86_pv_vcpu_basic, + pack(libxc.X86_PV_VCPU_HDR_FORMAT, vcpu_id, 0), data) + +def write_libxc_pv_vcpu_extd(vcpu_id, data): + write_record(libxc.REC_TYPE_x86_pv_vcpu_extended, + pack(libxc.X86_PV_VCPU_HDR_FORMAT, vcpu_id, 0), data) + +def write_libxc_pv_vcpu_xsave(vcpu_id, data): + write_record(libxc.REC_TYPE_x86_pv_vcpu_xsave, + pack(libxc.X86_PV_VCPU_HDR_FORMAT, vcpu_id, 0), data) + +def write_page_data(pfns, pages): + if fout is None: # Save copying 1M buffers around for no reason + return + + new_pfns = [(((x & 0xf0000000) << 32) | (x & 0x0fffffff)) for x in pfns] + + # Optimise the needless buffer copying in write_record() + stream_write(pack(libxc.RH_FORMAT, + libxc.REC_TYPE_page_data, + 8 + (len(new_pfns) * 8) + len(pages))) + stream_write(pack(libxc.PAGE_DATA_FORMAT, len(new_pfns), 0)) + stream_write(pack("Q" * len(new_pfns), *new_pfns)) + stream_write(pages) + +def write_libxc_tsc_info(mode, khz, nsec, incarn): + write_record(libxc.REC_TYPE_tsc_info, + pack(libxc.TSC_INFO_FORMAT, + mode, khz, nsec, incarn, 0)) + +def write_libxc_hvm_params(params): + if pv: + raise StreamError("HVM-only param in PV stream") + elif len(params) % 2: + raise RuntimeError("Expected even length list of hvm parameters") + + write_record(libxc.REC_TYPE_hvm_params, + pack(libxc.HVM_PARAMS_FORMAT, len(params) / 2, 0), + pack("Q" * len(params), *params)) + +def write_libxl_end(): + write_record(libxl.REC_TYPE_end, "") + +def write_libxl_libxc_context(): + write_record(libxl.REC_TYPE_libxc_context, "") + +def write_libxl_xenstore_data(data): + write_record(libxl.REC_TYPE_xenstore_data, data) + +def write_libxl_emulator_context(blob): + write_record(libxl.REC_TYPE_emulator_context, + pack(libxl.EMULATOR_CONTEXT_FORMAT, + libxl.EMULATOR_ID_unknown, 0) + blob) + +def rdexact(nr_bytes): + """Read exactly nr_bytes from fin""" + _ = stream_read(nr_bytes) + if len(_) != nr_bytes: + raise IOError("Stream truncated") + return _ + +def unpack_exact(fmt): + """Unpack a format from fin""" + sz = calcsize(fmt) + return unpack(fmt, rdexact(sz)) + +def unpack_ulongs(nr_ulongs): + if twidth == 32: + return unpack_exact("I" * nr_ulongs) + else: + return unpack_exact("Q" * nr_ulongs) + +def read_pv_extended_info(vm): + + marker, = unpack_ulongs(1) + + if twidth == 32: + expected = 0xffffffff + else: + expected = 0xffffffffffffffff + + if marker != expected: + raise StreamError("Unexpected extended info marker 0x%x" % (marker, )) + + total_length, = unpack_exact("I") + so_far = 0 + + info("Extended Info: length 0x%x" % (total_length, )) + + while so_far < total_length: + + blkid, datasz = unpack_exact("=4sI") + so_far += 8 + + info(" Record type: %s, size 0x%x" % (blkid, datasz)) + + data = rdexact(datasz) + so_far += datasz + + # Eww, but this is how it is done :( + if blkid == "vcpu": + + vm.basic_len = datasz + + if datasz == 0x1430: + vm.width = 8 + vm.levels = 4 + info(" 64bit domain, 4 levels") + elif datasz == 0xaf0: + vm.width = 4 + vm.levels = 3 + info(" 32bit domain, 3 levels") + else: + raise StreamError("Unable to determine guest width/level") + + write_libxc_pv_info(vm) + + elif blkid == "extv": + vm.extd = True + + elif blkid == "xcnt": + vm.xsave_len, = unpack("I", data[:4]) + info("xcnt sz 0x%x" % (vm.xsave_len, )) + + else: + raise StreamError("Unrecognised extended block") + + + if so_far != total_length: + raise StreamError("Overshot Extended Info size by %d bytes" + % (so_far - total_length,)) + +def read_pv_p2m_frames(vm): + fpp = 4096 / vm.width + p2m_frame_len = (vm.p2m_size - 1) / fpp + 1 + + info("P2M frames: fpp %d, p2m_frame_len %d" % (fpp, p2m_frame_len)) + write_libxc_pv_p2m_frames(vm, unpack_ulongs(p2m_frame_len)) + +def read_pv_tail(vm): + + nr_unmapped_pfns, = unpack_exact("I") + + if nr_unmapped_pfns != 0: + # "Unmapped" pfns are bogus + _ = unpack_ulongs(nr_unmapped_pfns) + info("discarding %d bogus 'unmapped pfns'" % (nr_unmapped_pfns, )) + + for vcpu_id in vm.online_vcpu_map: + + basic = rdexact(vm.basic_len) + info("Got VCPU basic (size 0x%x)" % (vm.basic_len, )) + write_libxc_pv_vcpu_basic(vcpu_id, basic) + + if vm.extd: + extd = rdexact(128) + info("Got VCPU extd (size 0x%x)" % (128, )) + write_libxc_pv_vcpu_extd(vcpu_id, extd) + + if vm.xsave_len: + mask, size = unpack_exact("QQ") + assert vm.xsave_len - 16 == size + + xsave = rdexact(size) + info("Got VCPU xsave (mask 0x%x, size 0x%x)" % (mask, size)) + write_libxc_pv_vcpu_xsave(vcpu_id, xsave) + + shinfo = rdexact(4096) + info("Got shinfo") + + write_record(libxc.REC_TYPE_shared_info, shinfo) + write_record(libxc.REC_TYPE_end, "") + + +def read_chunks(vm): + + hvm_params = [] + + while True: + + marker, = unpack_exact("=i") + if marker <= 0: + info("Chunk: %d - %s" % + (marker, legacy.chunk_type_to_str.get(marker, "unknown"))) + + if marker == legacy.CHUNK_end: + info(" End") + + if hvm_params: + write_libxc_hvm_params(hvm_params) + + return + + elif marker > 0: + + if marker > legacy.MAX_BATCH: + raise StreamError("Page batch (%d) exceeded MAX_BATCH (%d)" + % (marker, legacy.MAX_BATCH)) + pfns = unpack_ulongs(marker) + + # xc_domain_save() leaves many XEN_DOMCTL_PFINFO_XTAB records for + # sequences of pfns it cant map. Drop these. + pfns = [ x for x in pfns if x != 0xf0000000 ] + + if len(set(pfns)) != len(pfns): + raise StreamError("Duplicate pfns in batch") + + nr_pages = len([x for x in pfns if (x & 0xf0000000) < 0xd0000000]) + pages = rdexact(nr_pages * 4096) + + write_page_data(pfns, pages) + + elif marker == legacy.CHUNK_enable_verify_mode: + # For debugging purposes only. Will not be seen in real migration + raise RuntimeError("Unable to convert a debug stream") + + elif marker == legacy.CHUNK_vcpu_info: + max_id, = unpack_exact("i") + + if max_id > legacy.MAX_VCPU_ID: + raise StreamError("Vcpu max_id out of range: %d > %d" + % (max_id, legacy.MAX_VCPU_ID)) + + vm.max_vcpu_id = max_id + bitmap = unpack_exact("Q" * ((max_id/64) + 1)) + + for idx, word in enumerate(bitmap): + bit_idx = 0 + + while word > 0: + if word & 1: + vm.online_vcpu_map.append((idx * 64) + bit_idx) + + bit_idx += 1 + word >>= 1 + + info(" Vcpu info: max_id %d, online map %s" + % (vm.max_vcpu_id, vm.online_vcpu_map)) + + elif marker == legacy.CHUNK_hvm_ident_pt: + _, ident_pt = unpack_exact("=IQ") + info(" EPT Identity Pagetable: 0x%x" % (ident_pt, )) + hvm_params.extend([public.HVM_PARAM_IDENT_PT, ident_pt]) + + elif marker == legacy.CHUNK_hvm_vm86_tss: + _, vm86_tss = unpack_exact("=IQ") + info(" VM86 TSS: 0x%x" % (vm86_tss, )) + hvm_params.extend([public.HVM_PARAM_VM86_TSS, vm86_tss]) + + elif marker == legacy.CHUNK_tmem: + raise RuntimeError("todo") + + elif marker == legacy.CHUNK_tmem_extra: + raise RuntimeError("todo") + + elif marker == legacy.CHUNK_tsc_info: + mode, nsec, khz, incarn = unpack_exact("=IQII") + info(" TSC_INFO: mode %s, %d ns, %d khz, %d incarn" + % (mode, nsec, khz, incarn)) + write_libxc_tsc_info(mode, khz, nsec, incarn) + + elif marker == legacy.CHUNK_hvm_console_pfn: + _, console_pfn = unpack_exact("=IQ") + info(" Console pfn: 0x%x" % (console_pfn, )) + hvm_params.extend([public.HVM_PARAM_CONSOLE_PFN, console_pfn]) + + elif marker == legacy.CHUNK_last_checkpoint: + info(" Last Checkpoint") + # Nothing to do + + elif marker == legacy.CHUNK_hvm_acpi_ioports_location: + _, loc = unpack_exact("=IQ") + info(" ACPI ioport location: 0x%x" % (loc, )) + hvm_params.extend([public.HVM_PARAM_ACPI_IOPORTS_LOCATION, loc]) + + elif marker == legacy.CHUNK_hvm_viridian: + _, loc = unpack_exact("=IQ") + info(" Viridian location: 0x%x" % (loc, )) + hvm_params.extend([public.HVM_PARAM_VIRIDIAN, loc]) + + elif marker == legacy.CHUNK_compressed_data: + sz, = unpack_exact("I") + data = rdexact(sz) + info(" Compressed Data: sz 0x%x" % (sz, )) + raise RuntimeError("todo") + + elif marker == legacy.CHUNK_enable_compression: + raise RuntimeError("todo") + + elif marker == legacy.CHUNK_hvm_generation_id_addr: + _, genid_loc = unpack_exact("=IQ") + info(" Generation ID Address: 0x%x" % (genid_loc, )) + hvm_params.extend( + [public.HVM_PARAM_VM_GENERATION_ID_ADDR, genid_loc]) + + elif marker == legacy.CHUNK_hvm_paging_ring_pfn: + _, pfn = unpack_exact("=IQ") + info(" Paging ring pfn: 0x%x" % (pfn, )) + hvm_params.extend([public.HVM_PARAM_PAGING_RING_PFN, pfn]) + + elif marker == legacy.CHUNK_hvm_monitor_ring_pfn: + _, pfn = unpack_exact("=IQ") + info(" Monitor ring pfn: 0x%x" % (pfn, )) + hvm_params.extend([public.HVM_PARAM_MONITOR_RING_PFN, pfn]) + + elif marker == legacy.CHUNK_hvm_sharing_ring_pfn: + _, pfn = unpack_exact("=IQ") + info(" Sharing ring pfn: 0x%x" % (pfn, )) + hvm_params.extend([public.HVM_PARAM_SHARING_RING_PFN, pfn]) + + elif marker == legacy.CHUNK_toolstack: + sz, = unpack_exact("I") + + if sz: + data = rdexact(sz) + info(" Toolstack Data: sz 0x%x" % (sz, )) + + if vm.libxl: + vm.xenstore.append(data) + else: + info(" Discarding") + + elif marker == legacy.CHUNK_hvm_ioreq_server_pfn: + _, pfn = unpack_exact("=IQ") + info(" IOREQ server pfn: 0x%x" % (pfn, )) + hvm_params.extend([public.HVM_PARAM_IOREQ_SERVER_PFN, pfn]) + + elif marker == legacy.CHUNK_hvm_nr_ioreq_server_pages: + _, nr_pages = unpack_exact("=IQ") + info(" IOREQ server pages: %d" % (nr_pages, )) + hvm_params.extend( + [public.HVM_PARAM_NR_IOREQ_SERVER_PAGES, nr_pages]) + + else: + raise StreamError("Unrecognised chunk %d" % (marker,)) + +def read_hvm_tail(vm): + + io, bufio, store = unpack_exact("QQQ") + info("Magic pfns: 0x%x 0x%x 0x%x" % (io, bufio, store)) + write_libxc_hvm_params([public.HVM_PARAM_IOREQ_PFN, io, + public.HVM_PARAM_BUFIOREQ_PFN, bufio, + public.HVM_PARAM_STORE_PFN, store]) + + blobsz, = unpack_exact("I") + info("Got HVM Context (0x%x bytes)" % (blobsz, )) + blob = rdexact(blobsz) + + write_record(libxc.REC_TYPE_hvm_context, blob) + write_record(libxc.REC_TYPE_end, "") + + + +def read_qemu(vm): + + rawsig = rdexact(21) + sig, = unpack("21s", rawsig) + info("Qemu signature: %s" % (sig, )) + + if sig == "DeviceModelRecord0002": + rawsz = rdexact(4) + sz, = unpack("I", rawsz) + qdata = rdexact(sz) + + if vm.libxl: + write_libxl_emulator_context(qdata) + else: + stream_write(rawsig) + stream_write(rawsz) + stream_write(qdata) + + else: + raise RuntimeError("Unrecognised Qemu sig '%s'" % (sig, )) + + +def skip_xl_header(fmt): + """Skip over an xl header in the stream""" + + hdr = rdexact(len(xl.MAGIC)) + if hdr != xl.MAGIC: + raise StreamError("No xl header") + + byteorder, mflags, oflags, optlen = unpack_exact(xl.HEADER_FORMAT) + + if fmt == "libxl": + mflags |= xl.MANDATORY_FLAG_STREAMV2 + + opts = pack(xl.HEADER_FORMAT, byteorder, mflags, oflags, optlen) + + optdata = rdexact(optlen) + + info("Processed xl header") + + stream_write(hdr) + stream_write(opts) + stream_write(optdata) + +def read_legacy_stream(vm): + + try: + vm.p2m_size, = unpack_ulongs(1) + info("P2M Size: 0x%x" % (vm.p2m_size,)) + + if vm.libxl: + write_libxl_hdr() + write_libxl_libxc_context() + + write_libxc_ihdr() + write_libxc_dhdr() + + if pv: + read_pv_extended_info(vm) + read_pv_p2m_frames(vm) + + read_chunks(vm) + + if pv: + read_pv_tail(vm) + else: + read_hvm_tail(vm) + + if vm.libxl: + for rec in vm.xenstore: + write_libxl_xenstore_data(rec) + + if not pv and (vm.libxl or qemu): + read_qemu(vm) + + if vm.libxl: + write_libxl_end() + + except (IOError, StreamError): + err("Stream Error:") + err(traceback.format_exc()) + return 1 + + except RuntimeError: + err("Script Error:") + err(traceback.format_exc()) + err("Please fix me") + return 2 + return 0 + +def open_file_or_fd(val, mode): + """ + If 'val' looks like a decimal integer, open it as an fd. If not, try to + open it as a regular file. + """ + + fd = -1 + try: + # Does it look like an integer? + try: + fd = int(val, 10) + except ValueError: + pass + + # Try to open it... + if fd != -1: + return os.fdopen(fd, mode, 0) + else: + return open(val, mode, 0) + + except StandardError, e: + if fd != -1: + err("Unable to open fd %d: %s: %s" % + (fd, e.__class__.__name__, e)) + else: + err("Unable to open file '%s': %s: %s" % + (val, e.__class__.__name__, e)) + + raise SystemExit(1) + + +def main(): + from optparse import OptionParser + global fin, fout, twidth, pv, qemu, verbose + + # Change stdout to be line-buffered. + sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 1) + + parser = OptionParser(version = __version__, + usage = ("%prog [options] -i INPUT -o OUTPUT" + " -w WIDTH -g GUEST"), + description = + "Convert a legacy stream to a v2 stream") + + # Required options + parser.add_option("-i", "--in", dest = "fin", metavar = "", + help = "Legacy input to convert") + parser.add_option("-o", "--out", dest = "fout", metavar = "", + help = "v2 format output") + parser.add_option("-w", "--width", dest = "twidth", + metavar = "<32/64>", choices = ["32", "64"], + help = "Legacy toolstack bitness") + parser.add_option("-g", "--guest-type", dest = "gtype", + metavar = "", choices = ["pv", "hvm"], + help = "Type of guest in stream") + + # Optional options + parser.add_option("-f", "--format", dest = "format", + metavar = "", default = "libxc", + choices = ["libxc", "libxl"], + help = "Desired format of the outgoing stream " \ + "(defaults to libxc)") + parser.add_option("-v", "--verbose", action = "store_true", default = False, + help = "Summarise stream contents") + parser.add_option("-x", "--xl", action = "store_true", default = False, + help = ("Is an `xl` header present in the stream?" + " (default no)")) + parser.add_option("--skip-qemu", action = "store_true", default = False, + help = ("Skip processing of the qemu tail?" + " (default no)")) + parser.add_option("--syslog", action = "store_true", default = False, + help = "Log to syslog instead of stdout") + + opts, _ = parser.parse_args() + + if (opts.fin is None or opts.fout is None or + opts.twidth is None or opts.gtype is None): + + parser.print_help(sys.stderr) + raise SystemExit(1) + + if opts.syslog: + global log_to_syslog + + syslog.openlog("convert-legacy-stream", syslog.LOG_PID) + log_to_syslog = True + + fin = open_file_or_fd(opts.fin, "rb") + fout = open_file_or_fd(opts.fout, "wb") + twidth = int(opts.twidth) + pv = opts.gtype == "pv" + verbose = opts.verbose + if opts.skip_qemu: + qemu = False + + if opts.xl: + skip_xl_header(opts.format) + + rc = read_legacy_stream(VM(opts.format)) + fout.close() + + return rc + +if __name__ == "__main__": + try: + sys.exit(main()) + except SystemExit, e: + sys.exit(e.code) + except KeyboardInterrupt: + sys.exit(1) -- 1.7.10.4