1# 2# Copyright OpenEmbedded Contributors 3# 4# SPDX-License-Identifier: MIT 5# 6import os 7import socketserver 8import subprocess 9import time 10import urllib 11import pathlib 12 13from oeqa.core.decorator import OETestTag 14from oeqa.selftest.case import OESelftestTestCase 15from oeqa.utils.commands import bitbake, get_bb_var, runqemu 16 17 18class Debuginfod(OESelftestTestCase): 19 20 def wait_for_debuginfod(self, port): 21 """ 22 debuginfod takes time to scan the packages and requesting too early may 23 result in a test failure if the right packages haven't been scanned yet. 24 25 Request the metrics endpoint periodically and wait for there to be no 26 busy scanning threads. 27 28 Returns if debuginfod is ready, raises an exception if not within the 29 timeout. 30 """ 31 32 # Wait two minutes 33 countdown = 24 34 delay = 5 35 latest = None 36 37 while countdown: 38 self.logger.info("waiting...") 39 time.sleep(delay) 40 41 self.logger.info("polling server") 42 if self.debuginfod.poll(): 43 self.logger.info("server dead") 44 self.debuginfod.communicate() 45 self.fail("debuginfod terminated unexpectedly") 46 self.logger.info("server alive") 47 48 try: 49 with urllib.request.urlopen("http://localhost:%d/metrics" % port, timeout=10) as f: 50 for line in f.read().decode("ascii").splitlines(): 51 key, value = line.rsplit(" ", 1) 52 if key == "thread_busy{role=\"scan\"}": 53 latest = int(value) 54 self.logger.info("Waiting for %d scan jobs to finish" % latest) 55 if latest == 0: 56 return 57 except urllib.error.URLError as e: 58 # TODO: how to catch just timeouts? 59 self.logger.error(e) 60 61 countdown -= 1 62 63 raise TimeoutError("Cannot connect debuginfod, still %d scan jobs running" % latest) 64 65 def start_debuginfod(self): 66 # We assume that the caller has already bitbake'd elfutils-native:do_addto_recipe_sysroot 67 68 # Save some useful paths for later 69 native_sysroot = pathlib.Path(get_bb_var("RECIPE_SYSROOT_NATIVE", "elfutils-native")) 70 native_bindir = native_sysroot / "usr" / "bin" 71 self.debuginfod = native_bindir / "debuginfod" 72 self.debuginfod_find = native_bindir / "debuginfod-find" 73 74 cmd = [ 75 self.debuginfod, 76 "--verbose", 77 # In-memory database, this is a one-shot test 78 "--database=:memory:", 79 # Don't use all the host cores 80 "--concurrency=8", 81 "--connection-pool=8", 82 # Disable rescanning, this is a one-shot test 83 "--rescan-time=0", 84 "--groom-time=0", 85 get_bb_var("DEPLOY_DIR"), 86 ] 87 88 format = get_bb_var("PACKAGE_CLASSES").split()[0] 89 if format == "package_deb": 90 cmd.append("--scan-deb-dir") 91 elif format == "package_ipk": 92 cmd.append("--scan-deb-dir") 93 elif format == "package_rpm": 94 cmd.append("--scan-rpm-dir") 95 else: 96 self.fail("Unknown package class %s" % format) 97 98 # Find a free port. Racey but the window is small. 99 with socketserver.TCPServer(("localhost", 0), None) as s: 100 self.port = s.server_address[1] 101 cmd.append("--port=%d" % self.port) 102 103 self.logger.info(f"Starting server {cmd}") 104 self.debuginfod = subprocess.Popen(cmd, env={}) 105 self.wait_for_debuginfod(self.port) 106 107 108 def test_debuginfod_native(self): 109 """ 110 Test debuginfod outside of qemu, by building a package and looking up a 111 binary's debuginfo using elfutils-native. 112 """ 113 114 self.write_config(""" 115TMPDIR = "${TOPDIR}/tmp-debuginfod" 116DISTRO_FEATURES:append = " debuginfod" 117""") 118 bitbake("elfutils-native:do_addto_recipe_sysroot xz xz:do_package") 119 120 try: 121 self.start_debuginfod() 122 123 env = os.environ.copy() 124 env["DEBUGINFOD_URLS"] = "http://localhost:%d/" % self.port 125 126 pkgs = pathlib.Path(get_bb_var("PKGDEST", "xz")) 127 cmd = (self.debuginfod_find, "debuginfo", pkgs / "xz" / "usr" / "bin" / "xz.xz") 128 self.logger.info(f"Starting client {cmd}") 129 output = subprocess.check_output(cmd, env=env, text=True) 130 # This should be more comprehensive 131 self.assertIn("/.cache/debuginfod_client/", output) 132 finally: 133 self.debuginfod.kill() 134 135 @OETestTag("runqemu") 136 def test_debuginfod_qemu(self): 137 """ 138 Test debuginfod-find inside a qemu, talking to a debuginfod on the host. 139 """ 140 141 self.write_config(""" 142TMPDIR = "${TOPDIR}/tmp-debuginfod" 143DISTRO_FEATURES:append = " debuginfod" 144CORE_IMAGE_EXTRA_INSTALL += "elfutils xz" 145 """) 146 bitbake("core-image-minimal elfutils-native:do_addto_recipe_sysroot") 147 148 try: 149 self.start_debuginfod() 150 151 with runqemu("core-image-minimal", runqemuparams="nographic") as qemu: 152 cmd = "DEBUGINFOD_URLS=http://%s:%d/ debuginfod-find debuginfo /usr/bin/xz" % (qemu.server_ip, self.port) 153 self.logger.info(f"Starting client {cmd}") 154 status, output = qemu.run_serial(cmd) 155 # This should be more comprehensive 156 self.assertIn("/.cache/debuginfod_client/", output) 157 finally: 158 self.debuginfod.kill() 159